Skip to main content

engvis_core/
scene.rs

1use glam::Affine3A;
2use crate::light::LightingEnvironment;
3use crate::material::PbrMaterial;
4use crate::mesh::Mesh;
5use crate::aabb::Aabb;
6
7/// A scene node with transform
8#[derive(Debug)]
9pub struct SceneNode {
10    pub name: String,
11    pub local_transform: Affine3A,
12    pub mesh_index: Option<usize>,
13    pub children: Vec<SceneNode>,
14    pub visible: bool,
15}
16
17/// The top-level scene containing all data
18#[derive(Debug)]
19pub struct Scene {
20    pub meshes: Vec<Mesh>,
21    pub materials: Vec<PbrMaterial>,
22    pub nodes: Vec<SceneNode>,
23    pub lighting: LightingEnvironment,
24}
25
26impl Default for Scene {
27    fn default() -> Self {
28        Self {
29            meshes: Vec::new(),
30            materials: vec![PbrMaterial::default()],
31            nodes: Vec::new(),
32            lighting: LightingEnvironment::default(),
33        }
34    }
35}
36
37// ── Convenience constructors ─────────────────────────────────
38impl Scene {
39    /// Create a scene with a single mesh, one default material, and one node.
40    pub fn single_mesh(name: impl Into<String>, mesh: Mesh, material: PbrMaterial) -> Self {
41        let material_index = 0;
42        let mut mesh = mesh;
43        for sub in &mut mesh.sub_meshes {
44            sub.material_index = material_index;
45        }
46        Self {
47            meshes: vec![mesh],
48            materials: vec![material],
49            nodes: vec![SceneNode {
50                name: name.into(),
51                local_transform: Affine3A::IDENTITY,
52                mesh_index: Some(0),
53                children: Vec::new(),
54                visible: true,
55            }],
56            lighting: LightingEnvironment::default(),
57        }
58    }
59
60    /// Compute the world-space bounding box of the scene by traversing all nodes.
61    pub fn compute_aabb(&self) -> Aabb {
62        let mut out = Aabb::empty();
63        for node in &self.nodes {
64            out = out.union(&self.compute_node_aabb(node, Affine3A::IDENTITY));
65        }
66        out
67    }
68
69    fn compute_node_aabb(&self, node: &SceneNode, parent_transform: Affine3A) -> Aabb {
70        if !node.visible {
71            return Aabb::empty();
72        }
73        let world = parent_transform * node.local_transform;
74       let mut out = Aabb::empty();
75       if let Some(mesh_idx) = node.mesh_index {
76           if let Some(mesh) = self.meshes.get(mesh_idx) {
77               out = Aabb::from_transformed_aabb(&mesh.aabb, &world);
78           }
79       }
80        for child in &node.children {
81            out = out.union(&self.compute_node_aabb(child, world));
82        }
83        out
84    }
85
86    /// Use default lighting (two directional + ambient).
87    pub fn with_default_lighting(self) -> Self {
88        Self {
89            lighting: LightingEnvironment::default(),
90            ..self
91        }
92    }
93
94    /// Replace the lighting environment.
95    pub fn with_lighting(mut self, lighting: LightingEnvironment) -> Self {
96        self.lighting = lighting;
97        self
98    }
99}