1use crate::core::renderer::{PipelineType, Vertex};
7use bytemuck::{Pod, Zeroable};
8use glam::{Mat4, Vec3, Vec4};
9use std::collections::HashMap;
10use std::sync::Arc;
11
12pub type NodeId = u64;
14
15#[derive(Debug, Clone)]
17pub struct SceneNode {
18 pub id: NodeId,
19 pub name: String,
20 pub transform: Mat4,
21 pub visible: bool,
22 pub cast_shadows: bool,
23 pub receive_shadows: bool,
24
25 pub axes_index: usize,
27
28 pub parent: Option<NodeId>,
30 pub children: Vec<NodeId>,
31
32 pub render_data: Option<RenderData>,
34
35 pub bounds: BoundingBox,
37
38 pub lod_levels: Vec<LodLevel>,
40 pub current_lod: usize,
41}
42
43#[derive(Debug, Clone)]
45pub struct RenderData {
46 pub pipeline_type: PipelineType,
47 pub vertices: Vec<Vertex>,
48 pub indices: Option<Vec<u32>>,
49 pub gpu_vertices: Option<GpuVertexBuffer>,
50 pub bounds: Option<BoundingBox>,
54 pub material: Material,
55 pub draw_calls: Vec<DrawCall>,
56 pub image: Option<ImageData>,
58}
59
60impl RenderData {
61 pub fn vertex_count(&self) -> usize {
62 if !self.vertices.is_empty() {
63 self.vertices.len()
64 } else if let Some(buffer) = &self.gpu_vertices {
65 buffer.vertex_count
66 } else {
67 0
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct GpuVertexBuffer {
75 pub buffer: Arc<wgpu::Buffer>,
76 pub vertex_count: usize,
77 pub indirect: Option<GpuIndirectDraw>,
82}
83
84impl GpuVertexBuffer {
85 pub fn new(buffer: Arc<wgpu::Buffer>, vertex_count: usize) -> Self {
86 Self {
87 buffer,
88 vertex_count,
89 indirect: None,
90 }
91 }
92
93 pub fn with_indirect(
94 buffer: Arc<wgpu::Buffer>,
95 vertex_count: usize,
96 indirect_args: Arc<wgpu::Buffer>,
97 ) -> Self {
98 Self {
99 buffer,
100 vertex_count,
101 indirect: Some(GpuIndirectDraw {
102 args: indirect_args,
103 offset: 0,
104 }),
105 }
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct GpuIndirectDraw {
112 pub args: Arc<wgpu::Buffer>,
113 pub offset: u64,
114}
115
116#[repr(C)]
121#[derive(Clone, Copy, Debug, Pod, Zeroable)]
122pub struct DrawIndirectArgsRaw {
123 pub vertex_count: u32,
124 pub instance_count: u32,
125 pub first_vertex: u32,
126 pub first_instance: u32,
127}
128
129#[derive(Debug, Clone)]
131pub enum ImageData {
132 Rgba8 {
134 width: u32,
135 height: u32,
136 data: Vec<u8>,
137 },
138}
139
140#[derive(Debug, Clone)]
142pub struct Material {
143 pub albedo: Vec4,
144 pub roughness: f32,
145 pub metallic: f32,
146 pub emissive: Vec4,
147 pub alpha_mode: AlphaMode,
148 pub double_sided: bool,
149}
150
151impl Default for Material {
152 fn default() -> Self {
153 Self {
154 albedo: Vec4::new(1.0, 1.0, 1.0, 1.0),
155 roughness: 0.5,
156 metallic: 0.0,
157 emissive: Vec4::ZERO,
158 alpha_mode: AlphaMode::Opaque,
159 double_sided: false,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum AlphaMode {
167 Opaque,
168 Mask { cutoff: u8 },
169 Blend,
170}
171
172#[derive(Debug, Clone)]
174pub struct LodLevel {
175 pub distance: f32,
176 pub vertex_count: usize,
177 pub index_count: Option<usize>,
178 pub simplification_ratio: f32,
179}
180
181#[derive(Debug, Clone)]
183pub struct DrawCall {
184 pub vertex_offset: usize,
185 pub vertex_count: usize,
186 pub index_offset: Option<usize>,
187 pub index_count: Option<usize>,
188 pub instance_count: usize,
189}
190
191#[derive(Debug, Clone, Copy)]
193pub struct BoundingBox {
194 pub min: Vec3,
195 pub max: Vec3,
196}
197
198impl Default for BoundingBox {
199 fn default() -> Self {
200 Self {
201 min: Vec3::splat(f32::INFINITY),
202 max: Vec3::splat(f32::NEG_INFINITY),
203 }
204 }
205}
206
207impl BoundingBox {
208 pub fn new(min: Vec3, max: Vec3) -> Self {
209 Self { min, max }
210 }
211
212 pub fn from_points(points: &[Vec3]) -> Self {
213 if points.is_empty() {
214 return Self::default();
215 }
216
217 let mut min = points[0];
218 let mut max = points[0];
219
220 for &point in points.iter().skip(1) {
221 min = min.min(point);
222 max = max.max(point);
223 }
224
225 Self { min, max }
226 }
227
228 pub fn center(&self) -> Vec3 {
229 (self.min + self.max) / 2.0
230 }
231
232 pub fn size(&self) -> Vec3 {
233 self.max - self.min
234 }
235
236 pub fn expand(&mut self, point: Vec3) {
237 self.min = self.min.min(point);
238 self.max = self.max.max(point);
239 }
240
241 pub fn expand_by_box(&mut self, other: &BoundingBox) {
242 self.min = self.min.min(other.min);
243 self.max = self.max.max(other.max);
244 }
245
246 pub fn transform(&self, transform: &Mat4) -> Self {
247 let corners = [
248 Vec3::new(self.min.x, self.min.y, self.min.z),
249 Vec3::new(self.max.x, self.min.y, self.min.z),
250 Vec3::new(self.min.x, self.max.y, self.min.z),
251 Vec3::new(self.max.x, self.max.y, self.min.z),
252 Vec3::new(self.min.x, self.min.y, self.max.z),
253 Vec3::new(self.max.x, self.min.y, self.max.z),
254 Vec3::new(self.min.x, self.max.y, self.max.z),
255 Vec3::new(self.max.x, self.max.y, self.max.z),
256 ];
257
258 let transformed_corners: Vec<Vec3> = corners
259 .iter()
260 .map(|&corner| (*transform * corner.extend(1.0)).truncate())
261 .collect();
262
263 Self::from_points(&transformed_corners)
264 }
265
266 pub fn intersects(&self, other: &BoundingBox) -> bool {
267 self.min.x <= other.max.x
268 && self.max.x >= other.min.x
269 && self.min.y <= other.max.y
270 && self.max.y >= other.min.y
271 && self.min.z <= other.max.z
272 && self.max.z >= other.min.z
273 }
274
275 pub fn contains_point(&self, point: Vec3) -> bool {
276 point.x >= self.min.x
277 && point.x <= self.max.x
278 && point.y >= self.min.y
279 && point.y <= self.max.y
280 && point.z >= self.min.z
281 && point.z <= self.max.z
282 }
283
284 pub fn union(&self, other: &BoundingBox) -> BoundingBox {
286 BoundingBox {
287 min: Vec3::new(
288 self.min.x.min(other.min.x),
289 self.min.y.min(other.min.y),
290 self.min.z.min(other.min.z),
291 ),
292 max: Vec3::new(
293 self.max.x.max(other.max.x),
294 self.max.y.max(other.max.y),
295 self.max.z.max(other.max.z),
296 ),
297 }
298 }
299}
300
301pub struct Scene {
303 nodes: HashMap<NodeId, SceneNode>,
304 root_nodes: Vec<NodeId>,
305 next_id: NodeId,
306
307 world_bounds: BoundingBox,
309 bounds_dirty: bool,
310
311 frustum: Option<Frustum>,
313 camera_position: Vec3,
314}
315
316impl Default for Scene {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322impl Scene {
323 pub fn new() -> Self {
324 Self {
325 nodes: HashMap::new(),
326 root_nodes: Vec::new(),
327 next_id: 1,
328 world_bounds: BoundingBox::default(),
329 bounds_dirty: true,
330 frustum: None,
331 camera_position: Vec3::ZERO,
332 }
333 }
334
335 pub fn add_node(&mut self, node: SceneNode) -> NodeId {
337 while self.nodes.contains_key(&self.next_id) {
338 self.next_id = self.next_id.checked_add(1).unwrap_or(1);
339 }
340 let id = self.next_id;
341 self.next_id = self.next_id.checked_add(1).unwrap_or(1);
342
343 self.insert_node_with_id(id, node)
344 }
345
346 pub fn add_node_preserving_id(&mut self, node: SceneNode) -> NodeId {
351 let id = node.id;
352 if id != 0 && !self.nodes.contains_key(&id) {
353 return self.insert_node_with_id(id, node);
354 }
355 self.add_node(node)
356 }
357
358 fn insert_node_with_id(&mut self, id: NodeId, mut node: SceneNode) -> NodeId {
359 node.id = id;
360
361 if let Some(parent_id) = node.parent {
363 if let Some(parent) = self.nodes.get_mut(&parent_id) {
364 parent.children.push(id);
365 }
366 } else {
367 self.root_nodes.push(id);
368 }
369
370 self.nodes.insert(id, node);
371 self.bounds_dirty = true;
372 id
373 }
374
375 pub fn remove_node(&mut self, id: NodeId) -> bool {
377 let (parent_id, children) = if let Some(node) = self.nodes.get(&id) {
379 (node.parent, node.children.clone())
380 } else {
381 return false;
382 };
383
384 if let Some(parent_id) = parent_id {
386 if let Some(parent) = self.nodes.get_mut(&parent_id) {
387 parent.children.retain(|&child_id| child_id != id);
388 }
389 } else {
390 self.root_nodes.retain(|&root_id| root_id != id);
391 }
392
393 for child_id in children {
395 self.remove_node(child_id);
396 }
397
398 self.nodes.remove(&id);
399 self.bounds_dirty = true;
400 true
401 }
402
403 pub fn get_node(&self, id: NodeId) -> Option<&SceneNode> {
405 self.nodes.get(&id)
406 }
407
408 pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut SceneNode> {
410 if self.nodes.contains_key(&id) {
411 self.bounds_dirty = true;
412 }
413 self.nodes.get_mut(&id)
414 }
415
416 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut SceneNode)) {
419 for node in self.nodes.values_mut() {
420 visit(node);
421 }
422 self.bounds_dirty = true;
423 }
424
425 pub fn update_transforms(&mut self, root_transform: Mat4) {
427 for &root_id in &self.root_nodes.clone() {
428 self.update_node_transform(root_id, root_transform);
429 }
430 }
431
432 fn update_node_transform(&mut self, node_id: NodeId, parent_transform: Mat4) {
433 if let Some(node) = self.nodes.get_mut(&node_id) {
434 let world_transform = parent_transform * node.transform;
435
436 if let Some(render_data) = &node.render_data {
438 let local_bounds = BoundingBox::from_points(
439 &render_data
440 .vertices
441 .iter()
442 .map(|v| Vec3::from_array(v.position))
443 .collect::<Vec<_>>(),
444 );
445 node.bounds = local_bounds.transform(&world_transform);
446 }
447
448 let children = node.children.clone();
450 for child_id in children {
451 self.update_node_transform(child_id, world_transform);
452 }
453 }
454 }
455
456 pub fn world_bounds(&mut self) -> BoundingBox {
458 if self.bounds_dirty {
459 self.update_world_bounds();
460 }
461 self.world_bounds
462 }
463
464 fn update_world_bounds(&mut self) {
465 self.world_bounds = BoundingBox::default();
466
467 for node in self.nodes.values() {
468 if node.visible {
469 self.world_bounds.expand_by_box(&node.bounds);
470 }
471 }
472
473 self.bounds_dirty = false;
474 }
475
476 pub fn set_camera_position(&mut self, position: Vec3) {
478 self.camera_position = position;
479 self.update_lod();
480 }
481
482 fn update_lod(&mut self) {
484 for node in self.nodes.values_mut() {
485 if !node.lod_levels.is_empty() {
486 let distance = node.bounds.center().distance(self.camera_position);
487
488 let mut lod_index = node.lod_levels.len() - 1;
490 for (i, lod) in node.lod_levels.iter().enumerate() {
491 if distance <= lod.distance {
492 lod_index = i;
493 break;
494 }
495 }
496
497 node.current_lod = lod_index;
498 }
499 }
500 }
501
502 pub fn get_visible_nodes(&self) -> Vec<&SceneNode> {
504 self.nodes
505 .values()
506 .filter(|node| {
507 node.visible && node.render_data.is_some() && self.is_node_in_frustum(node)
508 })
509 .collect()
510 }
511
512 fn is_node_in_frustum(&self, node: &SceneNode) -> bool {
513 if let Some(ref frustum) = self.frustum {
515 frustum.intersects_box(&node.bounds)
516 } else {
517 true
518 }
519 }
520
521 pub fn set_frustum(&mut self, frustum: Frustum) {
523 self.frustum = Some(frustum);
524 }
525
526 pub fn clear(&mut self) {
528 self.nodes.clear();
529 self.root_nodes.clear();
530 self.bounds_dirty = true;
531 }
532
533 pub fn statistics(&self) -> SceneStatistics {
535 let visible_nodes = self.nodes.values().filter(|n| n.visible).count();
536 let total_vertices: usize = self
537 .nodes
538 .values()
539 .filter_map(|n| n.render_data.as_ref())
540 .map(|rd| rd.vertices.len())
541 .sum();
542 let total_triangles: usize = self
543 .nodes
544 .values()
545 .filter_map(|n| n.render_data.as_ref())
546 .filter(|rd| rd.pipeline_type == PipelineType::Triangles)
547 .map(|rd| {
548 rd.indices
549 .as_ref()
550 .map_or(rd.vertices.len() / 3, |i| i.len() / 3)
551 })
552 .sum();
553
554 SceneStatistics {
555 total_nodes: self.nodes.len(),
556 visible_nodes,
557 total_vertices,
558 total_triangles,
559 }
560 }
561}
562
563#[derive(Debug, Clone)]
565pub struct Frustum {
566 pub planes: [Plane; 6], }
568
569impl Frustum {
570 pub fn from_view_proj(view_proj: Mat4) -> Self {
571 let m = view_proj.to_cols_array_2d();
572
573 let planes = [
575 Plane::new(
577 m[0][3] + m[0][0],
578 m[1][3] + m[1][0],
579 m[2][3] + m[2][0],
580 m[3][3] + m[3][0],
581 ),
582 Plane::new(
584 m[0][3] - m[0][0],
585 m[1][3] - m[1][0],
586 m[2][3] - m[2][0],
587 m[3][3] - m[3][0],
588 ),
589 Plane::new(
591 m[0][3] + m[0][1],
592 m[1][3] + m[1][1],
593 m[2][3] + m[2][1],
594 m[3][3] + m[3][1],
595 ),
596 Plane::new(
598 m[0][3] - m[0][1],
599 m[1][3] - m[1][1],
600 m[2][3] - m[2][1],
601 m[3][3] - m[3][1],
602 ),
603 Plane::new(
605 m[0][3] + m[0][2],
606 m[1][3] + m[1][2],
607 m[2][3] + m[2][2],
608 m[3][3] + m[3][2],
609 ),
610 Plane::new(
612 m[0][3] - m[0][2],
613 m[1][3] - m[1][2],
614 m[2][3] - m[2][2],
615 m[3][3] - m[3][2],
616 ),
617 ];
618
619 Self { planes }
620 }
621
622 pub fn intersects_box(&self, bbox: &BoundingBox) -> bool {
623 for plane in &self.planes {
624 if plane.distance_to_box(bbox) > 0.0 {
625 return false; }
627 }
628 true }
630}
631
632#[derive(Debug, Clone, Copy)]
634pub struct Plane {
635 pub normal: Vec3,
636 pub distance: f32,
637}
638
639impl Plane {
640 pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
641 let normal = Vec3::new(a, b, c);
642 let length = normal.length();
643
644 Self {
645 normal: normal / length,
646 distance: d / length,
647 }
648 }
649
650 pub fn distance_to_point(&self, point: Vec3) -> f32 {
651 self.normal.dot(point) + self.distance
652 }
653
654 pub fn distance_to_box(&self, bbox: &BoundingBox) -> f32 {
655 let positive_vertex = Vec3::new(
657 if self.normal.x >= 0.0 {
658 bbox.max.x
659 } else {
660 bbox.min.x
661 },
662 if self.normal.y >= 0.0 {
663 bbox.max.y
664 } else {
665 bbox.min.y
666 },
667 if self.normal.z >= 0.0 {
668 bbox.max.z
669 } else {
670 bbox.min.z
671 },
672 );
673
674 self.distance_to_point(positive_vertex)
675 }
676}
677
678#[derive(Debug, Clone)]
680pub struct SceneStatistics {
681 pub total_nodes: usize,
682 pub visible_nodes: usize,
683 pub total_vertices: usize,
684 pub total_triangles: usize,
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690
691 #[test]
692 fn test_bounding_box_creation() {
693 let points = vec![
694 Vec3::new(-1.0, -1.0, -1.0),
695 Vec3::new(1.0, 1.0, 1.0),
696 Vec3::new(0.0, 0.0, 0.0),
697 ];
698
699 let bbox = BoundingBox::from_points(&points);
700 assert_eq!(bbox.min, Vec3::new(-1.0, -1.0, -1.0));
701 assert_eq!(bbox.max, Vec3::new(1.0, 1.0, 1.0));
702 assert_eq!(bbox.center(), Vec3::ZERO);
703 }
704
705 #[test]
706 fn test_scene_node_hierarchy() {
707 let mut scene = Scene::new();
708
709 let parent_node = SceneNode {
710 id: 0,
711 name: "Parent".to_string(),
712 transform: Mat4::IDENTITY,
713 visible: true,
714 cast_shadows: true,
715 receive_shadows: true,
716 axes_index: 0,
717 parent: None,
718 children: Vec::new(),
719 render_data: None,
720 bounds: BoundingBox::default(),
721 lod_levels: Vec::new(),
722 current_lod: 0,
723 };
724
725 let parent_id = scene.add_node(parent_node);
726
727 let child_node = SceneNode {
728 id: 0,
729 name: "Child".to_string(),
730 transform: Mat4::from_translation(Vec3::new(1.0, 0.0, 0.0)),
731 visible: true,
732 cast_shadows: true,
733 receive_shadows: true,
734 axes_index: 0,
735 parent: Some(parent_id),
736 children: Vec::new(),
737 render_data: None,
738 bounds: BoundingBox::default(),
739 lod_levels: Vec::new(),
740 current_lod: 0,
741 };
742
743 let child_id = scene.add_node(child_node);
744
745 let parent = scene.get_node(parent_id).unwrap();
747 assert!(parent.children.contains(&child_id));
748
749 let child = scene.get_node(child_id).unwrap();
750 assert_eq!(child.parent, Some(parent_id));
751 }
752
753 #[test]
754 fn scene_can_preserve_explicit_node_ids() {
755 let mut scene = Scene::new();
756 let explicit_id = 42;
757
758 let node = SceneNode {
759 id: explicit_id,
760 name: "Stable".to_string(),
761 transform: Mat4::IDENTITY,
762 visible: true,
763 cast_shadows: false,
764 receive_shadows: false,
765 axes_index: 0,
766 parent: None,
767 children: Vec::new(),
768 render_data: None,
769 bounds: BoundingBox::default(),
770 lod_levels: Vec::new(),
771 current_lod: 0,
772 };
773
774 let inserted_id = scene.add_node_preserving_id(node);
775
776 assert_eq!(inserted_id, explicit_id);
777 assert!(scene.get_node(explicit_id).is_some());
778
779 let generated_id = scene.add_node(SceneNode {
780 id: explicit_id,
781 name: "Generated".to_string(),
782 transform: Mat4::IDENTITY,
783 visible: true,
784 cast_shadows: false,
785 receive_shadows: false,
786 axes_index: 0,
787 parent: None,
788 children: Vec::new(),
789 render_data: None,
790 bounds: BoundingBox::default(),
791 lod_levels: Vec::new(),
792 current_lod: 0,
793 });
794
795 assert_ne!(generated_id, explicit_id);
796 assert!(scene.get_node(generated_id).is_some());
797 }
798}