Skip to main content

damascene_core/scene/
spec.rs

1//! `SceneSpec`: the author-facing description a [`chart3d`](crate::tree::chart3d)
2//! element carries until [`draw_ops`](crate::draw_ops) resolves it into a
3//! [`Scene3DData`](crate::scene::Scene3DData).
4//!
5//! A small builder so apps describe a scene declaratively:
6//!
7//! ```ignore
8//! use damascene_core::prelude::*;
9//! use damascene_core::scene::SceneSpec;
10//!
11//! let scene = SceneSpec::new()
12//!     .points(scatter_handle)        // PointsHandle, default style
13//!     .mesh(model_handle)            // MeshHandle, default material
14//!     .grid(GridPlanes::XZ);
15//! let el = chart3d(scene).key("scene");
16//! ```
17//!
18//! Marks are added with `points` / `mesh` / `lines` (default style) or the
19//! `add_*` variants for a fully-specified draw (transform, material,
20//! per-mark style). Per-point and per-segment colour live in the geometry
21//! handles, not here.
22
23use glam::Mat4;
24
25use crate::color::Color;
26use crate::scene::axes::{Axes, AxisKind};
27use crate::scene::camera::{CameraControls, CameraState, Focus, Framing};
28use crate::scene::data::{LineDraw, MeshDraw, PointDraw};
29use crate::scene::geometry::{LinesHandle, MeshHandle, PointsHandle};
30use crate::scene::style::{GridPlanes, LightRig, LineStyle, Material, PointStyle, SceneStyle};
31
32/// Declarative scene description: the marks plus styling. Resolved into a
33/// [`Scene3DData`](crate::scene::Scene3DData) at draw time (the camera is
34/// auto-framed against the marks' combined bounds, or taken from
35/// [`SceneSpec::camera`] when set).
36#[derive(Clone, Debug, Default)]
37pub struct SceneSpec {
38    pub meshes: Vec<MeshDraw>,
39    pub points: Vec<PointDraw>,
40    pub lines: Vec<LineDraw>,
41    pub lights: LightRig,
42    pub style: SceneStyle,
43    /// App-supplied camera pose. With [`Framing::Manual`] it is used
44    /// verbatim; with `Auto`/`Fit` its orbit angles seed the framed view.
45    /// `None` uses a default three-quarter pose.
46    pub camera: Option<CameraState>,
47    /// How the camera relates to the data bounds. Defaults to
48    /// [`Framing::Auto`] (fit, then free).
49    pub framing: Framing,
50    /// Declarative focus request. Changing it animates the camera to the
51    /// new viewpoint (under `Auto`/`Fit`). `None` leaves framing/gestures
52    /// in charge.
53    pub focus: Option<Focus>,
54    /// Pointer navigation scheme (default [`CameraControls::Orbit`]).
55    pub controls: CameraControls,
56    /// Axis tick/title labels. `None` (the default) draws no labels;
57    /// `Some` projects labels through the camera at draw time. Independent
58    /// of [`SceneStyle::show_axes`], which draws the axis *lines*.
59    pub axes: Option<Axes>,
60}
61
62impl SceneSpec {
63    pub fn new() -> Self {
64        Self::default()
65    }
66
67    /// Add a point/scatter mark with the default style.
68    pub fn points(mut self, handle: PointsHandle) -> Self {
69        self.points.push(PointDraw {
70            geometry: handle,
71            transform: Mat4::IDENTITY,
72            style: PointStyle::default(),
73            labels: None,
74        });
75        self
76    }
77
78    /// Add a point mark with an explicit style.
79    pub fn points_styled(mut self, handle: PointsHandle, style: PointStyle) -> Self {
80        self.points.push(PointDraw {
81            geometry: handle,
82            transform: Mat4::IDENTITY,
83            style,
84            labels: None,
85        });
86        self
87    }
88
89    /// Add a point mark with per-point labels / hover tooltips.
90    pub fn points_labeled(
91        mut self,
92        handle: PointsHandle,
93        style: PointStyle,
94        labels: crate::scene::labels::PointLabels,
95    ) -> Self {
96        self.points.push(PointDraw {
97            geometry: handle,
98            transform: Mat4::IDENTITY,
99            style,
100            labels: Some(labels),
101        });
102        self
103    }
104
105    /// Add a fully-specified point mark (transform + style).
106    pub fn add_points(mut self, draw: PointDraw) -> Self {
107        self.points.push(draw);
108        self
109    }
110
111    /// Add a mesh mark with the default material.
112    pub fn mesh(mut self, handle: MeshHandle) -> Self {
113        self.meshes.push(MeshDraw {
114            geometry: handle,
115            transform: Mat4::IDENTITY,
116            material: Material::default(),
117        });
118        self
119    }
120
121    /// Add a mesh mark with an explicit material.
122    pub fn mesh_with(mut self, handle: MeshHandle, material: Material) -> Self {
123        self.meshes.push(MeshDraw {
124            geometry: handle,
125            transform: Mat4::IDENTITY,
126            material,
127        });
128        self
129    }
130
131    /// Add a fully-specified mesh mark (transform + material).
132    pub fn add_mesh(mut self, draw: MeshDraw) -> Self {
133        self.meshes.push(draw);
134        self
135    }
136
137    /// Add a line mark with the default style.
138    pub fn lines(mut self, handle: LinesHandle) -> Self {
139        self.lines.push(LineDraw {
140            geometry: handle,
141            transform: Mat4::IDENTITY,
142            style: Default::default(),
143        });
144        self
145    }
146
147    /// Add a line mark with an explicit style.
148    pub fn lines_styled(mut self, handle: LinesHandle, style: LineStyle) -> Self {
149        self.lines.push(LineDraw {
150            geometry: handle,
151            transform: Mat4::IDENTITY,
152            style,
153        });
154        self
155    }
156
157    /// Add a fully-specified line mark (transform + style).
158    pub fn add_lines(mut self, draw: LineDraw) -> Self {
159        self.lines.push(draw);
160        self
161    }
162
163    /// Set which planes carry the reference grid.
164    pub fn grid(mut self, planes: GridPlanes) -> Self {
165        self.style.grid.planes = planes;
166        self
167    }
168
169    /// Turn the reference grid off.
170    pub fn no_grid(mut self) -> Self {
171        self.style.grid.planes = GridPlanes::NONE;
172        self
173    }
174
175    /// Clip one axis to an explicit world `[min, max]`, overriding the
176    /// symmetric grid `extent` for that axis alone. The grid plane lines, the
177    /// axis line, and the axis's ticks/title all honour the bound; the other
178    /// axes stay symmetric. Useful for a naturally one-sided axis — e.g. CIE
179    /// L\* ∈ [0, 100], which should never draw a negative half.
180    pub fn axis_bounds(mut self, axis: AxisKind, min: f32, max: f32) -> Self {
181        let b = &mut self.style.grid.bounds;
182        match axis {
183            AxisKind::X => b.x = Some((min, max)),
184            AxisKind::Y => b.y = Some((min, max)),
185            AxisKind::Z => b.z = Some((min, max)),
186        }
187        self
188    }
189
190    /// Fill the scene background (default leaves it transparent).
191    pub fn background(mut self, color: Color) -> Self {
192        self.style.background = Some(color);
193        self
194    }
195
196    /// Replace the whole style block.
197    pub fn style(mut self, style: SceneStyle) -> Self {
198        self.style = style;
199        self
200    }
201
202    /// Replace the light rig.
203    pub fn lights(mut self, rig: LightRig) -> Self {
204        self.lights = rig;
205        self
206    }
207
208    /// Supply an explicit camera pose. With [`Framing::Manual`] it is the
209    /// authoritative pose; otherwise its orbit angles seed the framed view.
210    pub fn camera(mut self, state: CameraState) -> Self {
211        self.camera = Some(state);
212        self
213    }
214
215    /// Set how the camera relates to the data bounds (default
216    /// [`Framing::Auto`]).
217    pub fn framing(mut self, framing: Framing) -> Self {
218        self.framing = framing;
219        self
220    }
221
222    /// Request the camera animate to a [`Focus`]. Changing the value
223    /// across rebuilds triggers a smooth move; passing the same value is a
224    /// no-op.
225    pub fn focus(mut self, focus: Focus) -> Self {
226        self.focus = Some(focus);
227        self
228    }
229
230    /// Choose the pointer navigation scheme (default
231    /// [`CameraControls::Orbit`]).
232    pub fn controls(mut self, controls: CameraControls) -> Self {
233        self.controls = controls;
234        self
235    }
236
237    /// Enable axis labels with the given configuration.
238    pub fn axes(mut self, axes: Axes) -> Self {
239        self.axes = Some(axes);
240        self
241    }
242
243    /// Enable axis labels with default styling and the three titles set.
244    pub fn axis_titles(
245        mut self,
246        x: impl Into<String>,
247        y: impl Into<String>,
248        z: impl Into<String>,
249    ) -> Self {
250        self.axes = Some(Axes::titles(x, y, z));
251        self
252    }
253}