Skip to main content

damascene_core/scene/
data.rs

1//! `Scene3DData`: the backend-neutral payload of a `DrawOp::Scene3D`.
2//!
3//! Assembled fresh each frame from the El tree's marks (cheap — it holds
4//! `Arc`-cloned [geometry handles](crate::scene::GeometryHandle), not
5//! geometry copies) plus the resolved camera, light rig, and style. The
6//! backend walks these draw lists, uploads any geometry whose revision
7//! advanced, and renders. See `docs/SCENE3D_PLAN.md`.
8
9use glam::Mat4;
10
11use crate::scene::bounds::Aabb;
12use crate::scene::camera::ResolvedCamera;
13use crate::scene::geometry::{LinesHandle, MeshHandle, PointsHandle};
14use crate::scene::style::{LightRig, LineStyle, Material, PointStyle, SceneStyle};
15
16/// A mesh mark: geometry handle + object→world transform + material.
17#[derive(Clone, Debug)]
18pub struct MeshDraw {
19    pub geometry: MeshHandle,
20    pub transform: Mat4,
21    pub material: Material,
22}
23
24/// A point/scatter mark: geometry handle + transform + style (per-point
25/// colour is in the geometry).
26#[derive(Clone, Debug)]
27pub struct PointDraw {
28    pub geometry: PointsHandle,
29    pub transform: Mat4,
30    pub style: PointStyle,
31    /// Per-point text labels / hover tooltips. `None` = unlabelled. CPU-only
32    /// presentation (not uploaded); see [`PointLabels`](crate::scene::PointLabels).
33    pub labels: Option<crate::scene::labels::PointLabels>,
34}
35
36/// A line mark: geometry handle + transform + style (per-segment colour is
37/// in the geometry).
38#[derive(Clone, Debug)]
39pub struct LineDraw {
40    pub geometry: LinesHandle,
41    pub transform: Mat4,
42    pub style: LineStyle,
43}
44
45/// Everything a backend needs to render one scene, all backend-neutral.
46#[derive(Clone, Debug)]
47pub struct Scene3DData {
48    pub meshes: Vec<MeshDraw>,
49    pub points: Vec<PointDraw>,
50    pub lines: Vec<LineDraw>,
51    pub camera: ResolvedCamera,
52    pub lights: LightRig,
53    pub style: SceneStyle,
54    /// Whether the backend should capture this scene's depth buffer for
55    /// label occlusion. Set when the scene has scene-anchored labels (axis
56    /// labels today); lets label-free scenes skip the resolve + read-back
57    /// cost. See [`SceneDepthMap`](crate::scene::SceneDepthMap).
58    pub capture_depth: bool,
59}
60
61impl Scene3DData {
62    /// Combined world-space bounds of all marks — each handle's local
63    /// bounds transformed by its mark transform, unioned. Empty when there
64    /// is no geometry.
65    ///
66    /// Computed from the draw lists *before* assembling `Scene3DData`,
67    /// because the camera is resolved (auto-framed) against these bounds
68    /// and then stored in `camera`. Taking slices lets callers compute it
69    /// at that point.
70    pub fn content_bounds(meshes: &[MeshDraw], points: &[PointDraw], lines: &[LineDraw]) -> Aabb {
71        let mut bb = Aabb::EMPTY;
72        for m in meshes {
73            bb = bb.union(m.geometry.bounds().transformed(m.transform));
74        }
75        for p in points {
76            bb = bb.union(p.geometry.bounds().transformed(p.transform));
77        }
78        for l in lines {
79            bb = bb.union(l.geometry.bounds().transformed(l.transform));
80        }
81        bb
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::scene::geometry::{PointData, PointsHandle, ScenePoint};
89    use glam::Vec3;
90
91    #[test]
92    fn content_bounds_unions_and_applies_transforms() {
93        let pts = PointsHandle::new(PointData {
94            points: vec![
95                ScenePoint {
96                    position: Vec3::splat(-1.0),
97                    color: [1.0; 4],
98                },
99                ScenePoint {
100                    position: Vec3::splat(1.0),
101                    color: [1.0; 4],
102                },
103            ],
104        });
105        let draw = PointDraw {
106            geometry: pts,
107            transform: Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0)),
108            style: PointStyle::default(),
109            labels: None,
110        };
111        let bb = Scene3DData::content_bounds(&[], std::slice::from_ref(&draw), &[]);
112        assert!(bb.is_valid());
113        assert_eq!(bb.min, Vec3::new(4.0, -1.0, -1.0));
114        assert_eq!(bb.max, Vec3::new(6.0, 1.0, 1.0));
115    }
116
117    #[test]
118    fn content_bounds_empty_with_no_marks() {
119        assert!(!Scene3DData::content_bounds(&[], &[], &[]).is_valid());
120    }
121}