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}