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, mut node: SceneNode) -> NodeId {
337 let id = self.next_id;
338 self.next_id += 1;
339
340 node.id = id;
341
342 if let Some(parent_id) = node.parent {
344 if let Some(parent) = self.nodes.get_mut(&parent_id) {
345 parent.children.push(id);
346 }
347 } else {
348 self.root_nodes.push(id);
349 }
350
351 self.nodes.insert(id, node);
352 self.bounds_dirty = true;
353 id
354 }
355
356 pub fn remove_node(&mut self, id: NodeId) -> bool {
358 let (parent_id, children) = if let Some(node) = self.nodes.get(&id) {
360 (node.parent, node.children.clone())
361 } else {
362 return false;
363 };
364
365 if let Some(parent_id) = parent_id {
367 if let Some(parent) = self.nodes.get_mut(&parent_id) {
368 parent.children.retain(|&child_id| child_id != id);
369 }
370 } else {
371 self.root_nodes.retain(|&root_id| root_id != id);
372 }
373
374 for child_id in children {
376 self.remove_node(child_id);
377 }
378
379 self.nodes.remove(&id);
380 self.bounds_dirty = true;
381 true
382 }
383
384 pub fn get_node(&self, id: NodeId) -> Option<&SceneNode> {
386 self.nodes.get(&id)
387 }
388
389 pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut SceneNode> {
391 if self.nodes.contains_key(&id) {
392 self.bounds_dirty = true;
393 }
394 self.nodes.get_mut(&id)
395 }
396
397 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut SceneNode)) {
400 for node in self.nodes.values_mut() {
401 visit(node);
402 }
403 self.bounds_dirty = true;
404 }
405
406 pub fn update_transforms(&mut self, root_transform: Mat4) {
408 for &root_id in &self.root_nodes.clone() {
409 self.update_node_transform(root_id, root_transform);
410 }
411 }
412
413 fn update_node_transform(&mut self, node_id: NodeId, parent_transform: Mat4) {
414 if let Some(node) = self.nodes.get_mut(&node_id) {
415 let world_transform = parent_transform * node.transform;
416
417 if let Some(render_data) = &node.render_data {
419 let local_bounds = BoundingBox::from_points(
420 &render_data
421 .vertices
422 .iter()
423 .map(|v| Vec3::from_array(v.position))
424 .collect::<Vec<_>>(),
425 );
426 node.bounds = local_bounds.transform(&world_transform);
427 }
428
429 let children = node.children.clone();
431 for child_id in children {
432 self.update_node_transform(child_id, world_transform);
433 }
434 }
435 }
436
437 pub fn world_bounds(&mut self) -> BoundingBox {
439 if self.bounds_dirty {
440 self.update_world_bounds();
441 }
442 self.world_bounds
443 }
444
445 fn update_world_bounds(&mut self) {
446 self.world_bounds = BoundingBox::default();
447
448 for node in self.nodes.values() {
449 if node.visible {
450 self.world_bounds.expand_by_box(&node.bounds);
451 }
452 }
453
454 self.bounds_dirty = false;
455 }
456
457 pub fn set_camera_position(&mut self, position: Vec3) {
459 self.camera_position = position;
460 self.update_lod();
461 }
462
463 fn update_lod(&mut self) {
465 for node in self.nodes.values_mut() {
466 if !node.lod_levels.is_empty() {
467 let distance = node.bounds.center().distance(self.camera_position);
468
469 let mut lod_index = node.lod_levels.len() - 1;
471 for (i, lod) in node.lod_levels.iter().enumerate() {
472 if distance <= lod.distance {
473 lod_index = i;
474 break;
475 }
476 }
477
478 node.current_lod = lod_index;
479 }
480 }
481 }
482
483 pub fn get_visible_nodes(&self) -> Vec<&SceneNode> {
485 self.nodes
486 .values()
487 .filter(|node| {
488 node.visible && node.render_data.is_some() && self.is_node_in_frustum(node)
489 })
490 .collect()
491 }
492
493 fn is_node_in_frustum(&self, node: &SceneNode) -> bool {
494 if let Some(ref frustum) = self.frustum {
496 frustum.intersects_box(&node.bounds)
497 } else {
498 true
499 }
500 }
501
502 pub fn set_frustum(&mut self, frustum: Frustum) {
504 self.frustum = Some(frustum);
505 }
506
507 pub fn clear(&mut self) {
509 self.nodes.clear();
510 self.root_nodes.clear();
511 self.bounds_dirty = true;
512 }
513
514 pub fn statistics(&self) -> SceneStatistics {
516 let visible_nodes = self.nodes.values().filter(|n| n.visible).count();
517 let total_vertices: usize = self
518 .nodes
519 .values()
520 .filter_map(|n| n.render_data.as_ref())
521 .map(|rd| rd.vertices.len())
522 .sum();
523 let total_triangles: usize = self
524 .nodes
525 .values()
526 .filter_map(|n| n.render_data.as_ref())
527 .filter(|rd| rd.pipeline_type == PipelineType::Triangles)
528 .map(|rd| {
529 rd.indices
530 .as_ref()
531 .map_or(rd.vertices.len() / 3, |i| i.len() / 3)
532 })
533 .sum();
534
535 SceneStatistics {
536 total_nodes: self.nodes.len(),
537 visible_nodes,
538 total_vertices,
539 total_triangles,
540 }
541 }
542}
543
544#[derive(Debug, Clone)]
546pub struct Frustum {
547 pub planes: [Plane; 6], }
549
550impl Frustum {
551 pub fn from_view_proj(view_proj: Mat4) -> Self {
552 let m = view_proj.to_cols_array_2d();
553
554 let planes = [
556 Plane::new(
558 m[0][3] + m[0][0],
559 m[1][3] + m[1][0],
560 m[2][3] + m[2][0],
561 m[3][3] + m[3][0],
562 ),
563 Plane::new(
565 m[0][3] - m[0][0],
566 m[1][3] - m[1][0],
567 m[2][3] - m[2][0],
568 m[3][3] - m[3][0],
569 ),
570 Plane::new(
572 m[0][3] + m[0][1],
573 m[1][3] + m[1][1],
574 m[2][3] + m[2][1],
575 m[3][3] + m[3][1],
576 ),
577 Plane::new(
579 m[0][3] - m[0][1],
580 m[1][3] - m[1][1],
581 m[2][3] - m[2][1],
582 m[3][3] - m[3][1],
583 ),
584 Plane::new(
586 m[0][3] + m[0][2],
587 m[1][3] + m[1][2],
588 m[2][3] + m[2][2],
589 m[3][3] + m[3][2],
590 ),
591 Plane::new(
593 m[0][3] - m[0][2],
594 m[1][3] - m[1][2],
595 m[2][3] - m[2][2],
596 m[3][3] - m[3][2],
597 ),
598 ];
599
600 Self { planes }
601 }
602
603 pub fn intersects_box(&self, bbox: &BoundingBox) -> bool {
604 for plane in &self.planes {
605 if plane.distance_to_box(bbox) > 0.0 {
606 return false; }
608 }
609 true }
611}
612
613#[derive(Debug, Clone, Copy)]
615pub struct Plane {
616 pub normal: Vec3,
617 pub distance: f32,
618}
619
620impl Plane {
621 pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
622 let normal = Vec3::new(a, b, c);
623 let length = normal.length();
624
625 Self {
626 normal: normal / length,
627 distance: d / length,
628 }
629 }
630
631 pub fn distance_to_point(&self, point: Vec3) -> f32 {
632 self.normal.dot(point) + self.distance
633 }
634
635 pub fn distance_to_box(&self, bbox: &BoundingBox) -> f32 {
636 let positive_vertex = Vec3::new(
638 if self.normal.x >= 0.0 {
639 bbox.max.x
640 } else {
641 bbox.min.x
642 },
643 if self.normal.y >= 0.0 {
644 bbox.max.y
645 } else {
646 bbox.min.y
647 },
648 if self.normal.z >= 0.0 {
649 bbox.max.z
650 } else {
651 bbox.min.z
652 },
653 );
654
655 self.distance_to_point(positive_vertex)
656 }
657}
658
659#[derive(Debug, Clone)]
661pub struct SceneStatistics {
662 pub total_nodes: usize,
663 pub visible_nodes: usize,
664 pub total_vertices: usize,
665 pub total_triangles: usize,
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn test_bounding_box_creation() {
674 let points = vec![
675 Vec3::new(-1.0, -1.0, -1.0),
676 Vec3::new(1.0, 1.0, 1.0),
677 Vec3::new(0.0, 0.0, 0.0),
678 ];
679
680 let bbox = BoundingBox::from_points(&points);
681 assert_eq!(bbox.min, Vec3::new(-1.0, -1.0, -1.0));
682 assert_eq!(bbox.max, Vec3::new(1.0, 1.0, 1.0));
683 assert_eq!(bbox.center(), Vec3::ZERO);
684 }
685
686 #[test]
687 fn test_scene_node_hierarchy() {
688 let mut scene = Scene::new();
689
690 let parent_node = SceneNode {
691 id: 0,
692 name: "Parent".to_string(),
693 transform: Mat4::IDENTITY,
694 visible: true,
695 cast_shadows: true,
696 receive_shadows: true,
697 axes_index: 0,
698 parent: None,
699 children: Vec::new(),
700 render_data: None,
701 bounds: BoundingBox::default(),
702 lod_levels: Vec::new(),
703 current_lod: 0,
704 };
705
706 let parent_id = scene.add_node(parent_node);
707
708 let child_node = SceneNode {
709 id: 0,
710 name: "Child".to_string(),
711 transform: Mat4::from_translation(Vec3::new(1.0, 0.0, 0.0)),
712 visible: true,
713 cast_shadows: true,
714 receive_shadows: true,
715 axes_index: 0,
716 parent: Some(parent_id),
717 children: Vec::new(),
718 render_data: None,
719 bounds: BoundingBox::default(),
720 lod_levels: Vec::new(),
721 current_lod: 0,
722 };
723
724 let child_id = scene.add_node(child_node);
725
726 let parent = scene.get_node(parent_id).unwrap();
728 assert!(parent.children.contains(&child_id));
729
730 let child = scene.get_node(child_id).unwrap();
731 assert_eq!(child.parent, Some(parent_id));
732 }
733}