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
9// Lock in full per-item documentation for this module (issue #73).
10#![warn(missing_docs)]
11
12use glam::Mat4;
13
14use crate::scene::bounds::Aabb;
15use crate::scene::camera::ResolvedCamera;
16use crate::scene::geometry::{LinesHandle, MeshHandle, PointsHandle};
17use crate::scene::style::{LightRig, LineStyle, Material, PointStyle, SceneStyle};
18
19/// A mesh mark: geometry handle + object→world transform + material.
20#[derive(Clone, Debug)]
21pub struct MeshDraw {
22    /// Shared handle to the mesh geometry (uploaded once per revision).
23    pub geometry: MeshHandle,
24    /// Object→world transform applied to the geometry.
25    pub transform: Mat4,
26    /// Surface material; alpha < 1 routes through the translucent path.
27    pub material: Material,
28}
29
30/// A point/scatter mark: geometry handle + transform + style (per-point
31/// colour is in the geometry).
32#[derive(Clone, Debug)]
33pub struct PointDraw {
34    /// Shared handle to the point geometry (uploaded once per revision).
35    pub geometry: PointsHandle,
36    /// Object→world transform applied to the points.
37    pub transform: Mat4,
38    /// Marker size, shape, and size mode shared by every point in the mark.
39    pub style: PointStyle,
40    /// Per-point text labels / hover tooltips. `None` = unlabelled. CPU-only
41    /// presentation (not uploaded); see [`PointLabels`](crate::scene::PointLabels).
42    pub labels: Option<crate::scene::labels::PointLabels>,
43    /// Presentation hint: these discs exist only to patch the GPU line
44    /// pipeline's butt-cap joins (one disc per polyline vertex, sized to
45    /// the line width). Vector backends that draw round joins natively —
46    /// the SVG bundle fallback — skip these draws entirely; they are
47    /// visually redundant there and dominate output size. `false` for
48    /// real data markers (scatter, chart3d points).
49    pub line_joins: bool,
50}
51
52/// A line mark: geometry handle + transform + style (per-segment colour is
53/// in the geometry).
54#[derive(Clone, Debug)]
55pub struct LineDraw {
56    /// Shared handle to the line geometry (uploaded once per revision).
57    pub geometry: LinesHandle,
58    /// Object→world transform applied to the segments.
59    pub transform: Mat4,
60    /// Width, pattern, and size mode shared by every segment in the mark.
61    pub style: LineStyle,
62}
63
64/// Everything a backend needs to render one scene, all backend-neutral.
65#[derive(Clone, Debug)]
66pub struct Scene3DData {
67    /// Mesh marks; opaque ones draw first, translucent ones back-to-front.
68    pub meshes: Vec<MeshDraw>,
69    /// Point/scatter marks, drawn after meshes so data is never veiled.
70    pub points: Vec<PointDraw>,
71    /// Line marks, drawn after meshes so data is never veiled.
72    pub lines: Vec<LineDraw>,
73    /// The resolved (auto-framed) camera for this frame.
74    pub camera: ResolvedCamera,
75    /// The key + hemispheric-ambient light rig shading lit materials.
76    pub lights: LightRig,
77    /// Scene-level styling: grid, background, MSAA, axis visibility.
78    pub style: SceneStyle,
79    /// Whether the backend should capture this scene's depth buffer for
80    /// label occlusion. Set when the scene has scene-anchored labels (axis
81    /// labels today); lets label-free scenes skip the resolve + read-back
82    /// cost. See [`SceneDepthMap`](crate::scene::SceneDepthMap).
83    pub capture_depth: bool,
84}
85
86impl Scene3DData {
87    /// Combined world-space bounds of all marks — each handle's local
88    /// bounds transformed by its mark transform, unioned. Empty when there
89    /// is no geometry.
90    ///
91    /// Computed from the draw lists *before* assembling `Scene3DData`,
92    /// because the camera is resolved (auto-framed) against these bounds
93    /// and then stored in `camera`. Taking slices lets callers compute it
94    /// at that point.
95    pub fn content_bounds(meshes: &[MeshDraw], points: &[PointDraw], lines: &[LineDraw]) -> Aabb {
96        let mut bb = Aabb::EMPTY;
97        for m in meshes {
98            bb = bb.union(m.geometry.bounds().transformed(m.transform));
99        }
100        for p in points {
101            bb = bb.union(p.geometry.bounds().transformed(p.transform));
102        }
103        for l in lines {
104            bb = bb.union(l.geometry.bounds().transformed(l.transform));
105        }
106        bb
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::scene::geometry::{PointData, PointsHandle, ScenePoint};
114    use glam::Vec3;
115
116    #[test]
117    fn content_bounds_unions_and_applies_transforms() {
118        let pts = PointsHandle::new(PointData {
119            points: vec![
120                ScenePoint {
121                    position: Vec3::splat(-1.0),
122                    color: [1.0; 4],
123                },
124                ScenePoint {
125                    position: Vec3::splat(1.0),
126                    color: [1.0; 4],
127                },
128            ],
129        });
130        let draw = PointDraw {
131            geometry: pts,
132            transform: Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0)),
133            style: PointStyle::default(),
134            labels: None,
135            line_joins: false,
136        };
137        let bb = Scene3DData::content_bounds(&[], std::slice::from_ref(&draw), &[]);
138        assert!(bb.is_valid());
139        assert_eq!(bb.min, Vec3::new(4.0, -1.0, -1.0));
140        assert_eq!(bb.max, Vec3::new(6.0, 1.0, 1.0));
141    }
142
143    #[test]
144    fn content_bounds_empty_with_no_marks() {
145        assert!(!Scene3DData::content_bounds(&[], &[], &[]).is_valid());
146    }
147}