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>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user