Files
Oxide/engine/src/render/renderable.rs
T
Homer Simpson f56a1eea3b Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history:
engine, editor, physics, script, examples, tests, docs, and assets.
Relicensed from GPLv3 to MIT and updated repo URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 20:41:02 +02:00

135 lines
4.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Renderable scene components: [`MeshRenderer`] and [`PrimitiveShape`].
//!
//! A [`MeshRenderer`] is the component that makes a scene entity show up in the
//! 3D viewport: it pairs a mesh source with a [`Material`]. Stage 4 ships the
//! built-in [`PrimitiveShape`] source (cube/sphere/plane) — lightweight and
//! serializable, so the editor (and later scripts/AI agents) can author what an
//! entity renders. Imported meshes attach later via a mesh-asset handle.
use serde::{Deserialize, Serialize};
use super::{Material, Mesh};
use crate::math::{Aabb, Vec3};
/// A built-in primitive mesh an entity can render.
///
/// This names a shape rather than embedding vertex data, so it stays tiny,
/// serializable, and cheap to edit; the renderer resolves it to a (cached)
/// [`Mesh`]/GPU buffer.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Default,
Serialize,
Deserialize,
crate::reflect::ReflectEnum,
)]
pub enum PrimitiveShape {
/// Unit cube centered at the origin.
#[default]
Cube,
/// Unit-radius UV sphere.
Sphere,
/// A 1×1 ground plane on the XZ axes, facing `+Y`.
Plane,
}
impl PrimitiveShape {
/// All shapes, for building caches / editor menus.
pub const ALL: [PrimitiveShape; 3] = [
PrimitiveShape::Cube,
PrimitiveShape::Sphere,
PrimitiveShape::Plane,
];
/// A human-readable label.
pub fn label(self) -> &'static str {
match self {
PrimitiveShape::Cube => "Cube",
PrimitiveShape::Sphere => "Sphere",
PrimitiveShape::Plane => "Plane",
}
}
/// Builds the CPU [`Mesh`] for this shape.
pub fn mesh(self) -> Mesh {
match self {
PrimitiveShape::Cube => Mesh::cube(),
PrimitiveShape::Sphere => Mesh::uv_sphere(1.0, 32, 16),
PrimitiveShape::Plane => Mesh::plane(1.0),
}
}
/// The object-space bounds of this shape, without building a mesh — used for
/// ray-picking and culling.
pub fn local_bounds(self) -> Aabb {
let half = match self {
PrimitiveShape::Cube => Vec3::splat(0.5),
PrimitiveShape::Sphere => Vec3::ONE,
PrimitiveShape::Plane => Vec3::new(0.5, 0.0, 0.5),
};
Aabb::from_center_half_extents(Vec3::ZERO, half)
}
}
/// Component: what an entity renders.
///
/// Attach to a scene entity (via the ECS) to make it appear in a forward pass.
/// Stage 4 sources the mesh from a [`PrimitiveShape`]; the [`Material`] is
/// edited in the inspector. Both are serializable, supporting the engine's
/// dual-editable (editor + script/AI) component goal.
#[derive(
Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, crate::reflect::Reflect,
)]
pub struct MeshRenderer {
/// The mesh to draw.
pub shape: PrimitiveShape,
/// The surface material.
pub material: Material,
}
impl MeshRenderer {
/// A renderer for `shape` with the default material.
pub fn new(shape: PrimitiveShape) -> Self {
Self {
shape,
material: Material::default(),
}
}
/// A renderer for `shape` with an explicit `material`.
pub fn with_material(shape: PrimitiveShape, material: Material) -> Self {
Self { shape, material }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::Color;
#[test]
fn every_shape_builds_a_nonempty_mesh() {
for shape in PrimitiveShape::ALL {
let mesh = shape.mesh();
assert!(!mesh.vertices.is_empty(), "{shape:?} has no vertices");
assert!(mesh.triangle_count() > 0, "{shape:?} has no triangles");
}
}
#[test]
fn mesh_renderer_round_trips_through_ron() {
let mr = MeshRenderer::with_material(
PrimitiveShape::Sphere,
Material::metal(Color::rgb(0.2, 0.4, 0.8), 0.25),
);
let ron = ron::to_string(&mr).unwrap();
let back: MeshRenderer = ron::from_str(&ron).unwrap();
assert_eq!(mr, back);
}
}