cranpose_ui_graphics/space/scene.rs
1use std::sync::Arc;
2
3use super::{
4 eye::EyePlace,
5 math::{Mat4, Vec3},
6 mesh::{Mesh, MeshHit},
7};
8use crate::{Color, Point, Rect, Size, transform::ProjectiveTransform};
9
10/// How much nearer than its focal length a camera sees: points closer to the
11/// eye than `focal_length * NEAR_PLANE` are cut away. It is the w at which
12/// [`ProjectiveTransform::front_polygon`] cuts a perspective map.
13pub const NEAR_PLANE: f32 = crate::transform::NEAR_W;
14
15/// A pinhole camera that shows a 3D scene in a composable's bounds.
16///
17/// The camera looks along its own z axis with its x to the right and y down.
18/// A point at `(x, y, z)` in the camera's frame shows at
19/// `center + (x, y) * focal_length / z`, where `center` is the middle of the
20/// bounds.
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Camera {
23 /// Maps scene space into the camera's frame.
24 pub view: Mat4,
25 /// The distance from the eye to the image, in logical pixels: the
26 /// distance at which one scene unit shows as one pixel.
27 pub focal_length: f32,
28}
29
30impl Camera {
31 /// A camera at `eye` that looks at `target`. `down` is the scene
32 /// direction that shows as down; it need not be square to the view.
33 pub fn look_at(eye: Vec3, target: Vec3, down: Vec3, focal_length: f32) -> Self {
34 let forward = (target - eye)
35 .normalized()
36 .unwrap_or(Vec3::new(0.0, 0.0, 1.0));
37 let right = down
38 .cross(forward)
39 .normalized()
40 .unwrap_or(Vec3::new(1.0, 0.0, 0.0));
41 let down = forward.cross(right);
42 let row = |axis: Vec3| [axis.x, axis.y, axis.z, -axis.dot(eye)];
43 Self {
44 view: Mat4::from_rows([row(right), row(down), row(forward), [0.0, 0.0, 0.0, 1.0]]),
45 focal_length,
46 }
47 }
48
49 /// The focal length that shows `vertical_fov` radians across `height`
50 /// pixels.
51 pub fn focal_length_for(height: f32, vertical_fov: f32) -> f32 {
52 height * 0.5 / (vertical_fov * 0.5).tan()
53 }
54
55 /// The eye's position in scene space.
56 pub fn eye(&self) -> Vec3 {
57 self.view
58 .inverse()
59 .map_or(Vec3::ZERO, |inverse| inverse.transform_point(Vec3::ZERO))
60 }
61
62 /// The camera-frame depth below which the camera sees nothing.
63 pub fn near(&self) -> f32 {
64 self.focal_length * NEAR_PLANE
65 }
66
67 /// Where `point` shows in bounds of `size`; `None` when it lies nearer
68 /// than [`Self::near`] or behind the eye.
69 pub fn project(&self, point: Vec3, size: Size) -> Option<Point> {
70 let seen = self.view.transform_point(point);
71 (seen.z >= self.near()).then(|| {
72 let scale = self.focal_length / seen.z;
73 Point::new(
74 size.width * 0.5 + seen.x * scale,
75 size.height * 0.5 + seen.y * scale,
76 )
77 })
78 }
79
80 /// The map from the scene this camera sees into the frame in which
81 /// `window`, a rect on the frame's plane, is a doorway into the scene:
82 /// the camera stands in the doorway's middle, looking along the frame's
83 /// z, with its x and y along the frame's, a scene unit to a frame unit.
84 /// What the camera sees lies behind the doorway.
85 pub fn doorway(&self, window: Rect) -> Mat4 {
86 self.view.then(&Mat4::translation(Vec3::new(
87 window.x + window.width * 0.5,
88 window.y + window.height * 0.5,
89 0.0,
90 )))
91 }
92
93 /// The ray from the eye through `point` of bounds of `size`, in scene
94 /// space, with a direction that reaches depth one in the camera's frame
95 /// at distance one.
96 pub fn ray(&self, point: Point, size: Size) -> Option<Ray> {
97 let inverse = self.view.inverse()?;
98 let through = Vec3::new(
99 (point.x - size.width * 0.5) / self.focal_length,
100 (point.y - size.height * 0.5) / self.focal_length,
101 1.0,
102 );
103 Some(Ray {
104 origin: inverse.transform_point(Vec3::ZERO),
105 direction: inverse.transform_vector(through),
106 })
107 }
108}
109
110/// A half line in scene space.
111#[derive(Clone, Copy, Debug, PartialEq)]
112pub struct Ray {
113 /// Where the ray starts.
114 pub origin: Vec3,
115 /// Which way the ray goes; distances along the ray count in its length.
116 pub direction: Vec3,
117}
118
119/// A light from one direction, with an even part that reaches every side.
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct Light {
122 /// The direction the light travels, in scene space.
123 pub direction: Vec3,
124 /// How bright a surface the light does not reach is, from 0 to 1: the
125 /// side of a lit surface turned away from the light, and any surface in
126 /// shadow.
127 pub ambient: f32,
128 /// Whether the surfaces that cast shadows cast them on the scene's
129 /// surfaces.
130 pub shadows: bool,
131 /// The angle the light spans as the scene sees it, in radians: 0 for a
132 /// point far away, whose shadows have hard edges. A light of some size
133 /// casts shadows that are sharp where a caster meets a surface and soft
134 /// farther from it, as the sun's 0.0093 does and a wide lamp's more.
135 pub size: f32,
136}
137
138impl Light {
139 /// How bright a surface is, from `ambient` to 1, where its side that
140 /// faces the viewer has `normal`: the light reaches only a side that
141 /// faces it.
142 pub fn brightness(&self, normal: Vec3) -> f32 {
143 let facing = match (self.direction.normalized(), normal.normalized()) {
144 (Some(direction), Some(normal)) => (-normal.dot(direction)).max(0.0),
145 _ => 0.0,
146 };
147 self.ambient + (1.0 - self.ambient) * facing
148 }
149
150 /// The map from scene space to a shadow map of this light that holds
151 /// `casters`: x and y from -1 to 1 across the map, and depth from 0 at
152 /// the caster nearest the light to 1 at the farthest. A point farther
153 /// along the light than a caster's depth there lies in its shadow.
154 /// `None` without casters, or for a light with no direction.
155 pub fn shadow_projection<'a>(
156 &self,
157 casters: impl IntoIterator<Item = &'a MeshSurface>,
158 ) -> Option<Mat4> {
159 const MARGIN: f32 = 0.02;
160 let forward = self.direction.normalized()?;
161 let hint = if forward.y.abs() < 0.9 {
162 Vec3::new(0.0, 1.0, 0.0)
163 } else {
164 Vec3::new(1.0, 0.0, 0.0)
165 };
166 let right = hint.cross(forward).normalized()?;
167 let up = forward.cross(right);
168 let view = Mat4::from_rows([
169 [right.x, right.y, right.z, 0.0],
170 [up.x, up.y, up.z, 0.0],
171 [forward.x, forward.y, forward.z, 0.0],
172 [0.0, 0.0, 0.0, 1.0],
173 ]);
174 let mut low = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
175 let mut high = -low;
176 for caster in casters {
177 let (caster_low, caster_high) = caster.mesh.bounds_in(&caster.transform.then(&view));
178 (low, high) = (low.min(caster_low), high.max(caster_high));
179 }
180 let extent = high - low;
181 if !(extent.x.is_finite() && extent.y.is_finite() && extent.z.is_finite()) {
182 return None;
183 }
184 let pad = |extent: f32| (extent * MARGIN).max(1.0);
185 let (low, high) = (
186 low - Vec3::new(pad(extent.x), pad(extent.y), pad(extent.z)),
187 high + Vec3::new(pad(extent.x), pad(extent.y), pad(extent.z)),
188 );
189 let extent = high - low;
190 let fit = Mat4::from_rows([
191 [2.0 / extent.x, 0.0, 0.0, -1.0 - 2.0 * low.x / extent.x],
192 [0.0, 2.0 / extent.y, 0.0, -1.0 - 2.0 * low.y / extent.y],
193 [0.0, 0.0, 1.0 / extent.z, -low.z / extent.z],
194 [0.0, 0.0, 0.0, 1.0],
195 ]);
196 Some(view.then(&fit))
197 }
198}
199
200/// A scene's camera and light, which a `scene3d` modifier shows its
201/// children's mesh surfaces through.
202///
203/// A scene inside another scene's space is a doorway into a space of its
204/// own: the rect its node takes in its frame opens onto that space, which
205/// lies behind it, placed so the doorway stands where the inner scene's
206/// camera stands (see [`Camera::doorway`]). The window's eye sees through
207/// doorways from the space it stands in, as deep as they show on screen,
208/// and walks through them (see [`EyePlace`]).
209#[derive(Clone, Debug, PartialEq)]
210pub struct Scene3d {
211 /// The camera the scene is seen through. For a scene behind a doorway,
212 /// the one that stands in the doorway; for the window's scene with an
213 /// [`eye`](Self::eye), the eye's, in the space the eye stands in.
214 pub camera: Camera,
215 /// The light on the scene's lit surfaces, or `None` for none.
216 pub light: Option<Light>,
217 /// For the window's scene, where its eye stands among the spaces behind
218 /// the scene's doorways and where the renderer leaves what the eye can
219 /// bump into; `None` for a camera in the scene's own space that walks
220 /// through nothing. A scene behind a doorway leaves it out.
221 pub eye: Option<EyePlace>,
222}
223
224/// A composable's content drawn on a mesh placed in a scene.
225#[derive(Clone, Debug, PartialEq)]
226pub struct MeshSurface {
227 /// The mesh the content is drawn on.
228 pub mesh: Arc<Mesh>,
229 /// Places the mesh in the scene.
230 pub transform: Mat4,
231 /// How the content meets the scene's light and what lies behind it.
232 pub material: Material,
233 /// Whether the surface casts a shadow from the scene's light. Every lit
234 /// and unlit surface takes the shadows that fall on it.
235 pub casts_shadow: bool,
236 /// Whether an eye that walks into the surface (see
237 /// [`EyeWorld::walk`](super::EyeWorld::walk)) stops there. A doorway in
238 /// the surface's content lets it through.
239 pub solid: bool,
240}
241
242/// How a surface's content meets the scene's light and what lies behind
243/// it.
244#[derive(Clone, Debug, PartialEq)]
245pub enum Material {
246 /// Shaded by the angle the light falls at, and darkened where a shadow
247 /// falls on it.
248 Lit,
249 /// The content as it is, as a screen shows it, darkened only where a
250 /// shadow falls on it.
251 Unlit,
252 /// The content at its full colour, as fire or a lamp shows: neither the
253 /// light nor a shadow changes it.
254 Emissive,
255 /// Adds the content's colour to what lies behind it, as light does: a
256 /// halo, a flame or a spark. It hides nothing, and surfaces nearer the
257 /// eye hide it.
258 Glow,
259 /// The scene behind shows through, bent by the surface's curve, with the
260 /// light's highlight on it and the content drawn over it. Glass shows
261 /// the surfaces that are not glass behind it.
262 Glass(Glass),
263 /// A shape an app's shader finds inside the mesh, ray by ray, as the
264 /// camera sees it, with the depth of what each ray meets: the mesh only
265 /// bounds it. A volume casts no shadow.
266 Volume(Volume),
267 /// Coloured by an app's shader from the content, the light and the eye
268 /// at each point of the surface: a reflection, a sheen, a normal of its
269 /// own. Until the shader builds, and if it never does, the surface is
270 /// [`Lit`](Self::Lit).
271 Shaded(Shaded),
272}
273
274/// An app's shader that colours a surface: each pixel the mesh covers asks
275/// it for its colour from the content there, the light and the eye.
276///
277/// `shader` is WGSL that defines
278///
279/// ```text
280/// fn shade(surface: ShadedSurface) -> vec4<f32>
281/// ```
282///
283/// and returns the colour, premultiplied. `ShadedSurface` holds, in the
284/// mesh's own coordinates: `color`, the content's colour there,
285/// premultiplied; `position`; `normal`, of length one, on the side the eye
286/// sees; `to_eye` and `to_light`, of length one, toward the eye and toward
287/// the light, which is zero without a light; `lit`, how much of the light
288/// reaches the point, from 0 in full shadow to 1; and `ambient`, the
289/// light's ambient share, 1 without a light. `shaded_param(index)` reads
290/// `params[index]`. The renderer puts
291/// [`MESH_SHADED_PRELUDE_WGSL`](crate::framework_shaders::MESH_SHADED_PRELUDE_WGSL),
292/// which declares them, before it.
293#[derive(Clone, Debug)]
294pub struct Shaded {
295 /// The app's WGSL. Keep one `Arc` per shader: the renderer builds a
296 /// pipeline for each.
297 pub shader: Arc<str>,
298 /// The values the shader reads.
299 pub params: [f32; Shaded::PARAMS],
300}
301
302impl Shaded {
303 /// How many values a surface's shader reads.
304 pub const PARAMS: usize = 32;
305}
306
307impl PartialEq for Shaded {
308 fn eq(&self, other: &Self) -> bool {
309 Arc::ptr_eq(&self.shader, &other.shader) && self.params == other.params
310 }
311}
312
313/// An app's shader that draws inside a mesh: each pixel the mesh covers
314/// asks it what the camera's ray through the pixel meets.
315///
316/// `shader` is WGSL that defines
317///
318/// ```text
319/// fn volume(eye: vec3<f32>, direction: vec3<f32>, entry: f32, exit: f32, footprint: f32) -> VolumeHit
320/// ```
321///
322/// in the mesh's own coordinates: the eye; the ray's direction, of length
323/// one; how far along the ray it enters the mesh's bounds, zero for an eye
324/// inside them, and leaves the mesh; and how wide a pixel is, in radians.
325/// It returns a `VolumeHit`: the colour, premultiplied, nothing at an alpha
326/// of zero; and the point the ray met, which sets the pixel's depth.
327/// `volume_param(index)` reads `params[index]`, `volume_content(uv)` the
328/// surface's content and `volume_light()` the scene's light, in the mesh's
329/// coordinates. The renderer puts
330/// [`MESH_VOLUME_PRELUDE_WGSL`](crate::framework_shaders::MESH_VOLUME_PRELUDE_WGSL),
331/// which declares them, before it. The mesh's normals must point out of it.
332#[derive(Clone, Debug)]
333pub struct Volume {
334 /// The app's WGSL. Keep one `Arc` per shader: the renderer builds a
335 /// pipeline for each.
336 pub shader: Arc<str>,
337 /// The values the shader reads.
338 pub params: [f32; Volume::PARAMS],
339}
340
341impl Volume {
342 /// How many values a volume's shader reads.
343 pub const PARAMS: usize = 32;
344}
345
346impl PartialEq for Volume {
347 fn eq(&self, other: &Self) -> bool {
348 Arc::ptr_eq(&self.shader, &other.shader) && self.params == other.params
349 }
350}
351
352/// How glass bends and tints the scene behind it.
353#[derive(Clone, Copy, Debug, PartialEq)]
354pub struct Glass {
355 /// The refractive index: 1 bends nothing, window glass is 1.5 and
356 /// diamond 2.4.
357 pub index: f32,
358 /// How far a ray travels inside the glass, in scene units: a ball's
359 /// diameter for a solid ball. A ray bends entering the glass and again
360 /// leaving it, as through a solid ball, so a thick ball shows the scene
361 /// behind it upside down.
362 pub thickness: f32,
363 /// The colour the glass tints what shows through it; white for clear
364 /// glass.
365 pub tint: Color,
366}
367
368impl Glass {
369 /// Clear window glass a ray crosses for `thickness` scene units.
370 pub const fn clear(thickness: f32) -> Self {
371 Self {
372 index: 1.5,
373 thickness,
374 tint: Color(1.0, 1.0, 1.0, 1.0),
375 }
376 }
377}
378
379impl MeshSurface {
380 /// The content of a composable of `size` on the mesh: the content point
381 /// where the ray meets the mesh, the triangle it meets, and how far along
382 /// the ray.
383 pub fn cast(&self, ray: Ray, size: Size) -> Option<SurfaceHit> {
384 let inverse = self.transform.inverse()?;
385 let hit = self.mesh.cast(
386 inverse.transform_point(ray.origin),
387 inverse.transform_vector(ray.direction),
388 )?;
389 Some(SurfaceHit::of(hit, size))
390 }
391
392 /// The content point of a composable of `size` where the ray meets the
393 /// plane of `triangle`: a drag that leaves the mesh's edge goes on along
394 /// it.
395 pub fn cast_plane(&self, ray: Ray, triangle: u32, size: Size) -> Option<Point> {
396 let inverse = self.transform.inverse()?;
397 let uv = self.mesh.cast_plane(
398 inverse.transform_point(ray.origin),
399 inverse.transform_vector(ray.direction),
400 triangle,
401 )?;
402 Some(content_point(uv, size))
403 }
404
405 /// The projective map that carries the content of a composable of
406 /// `size` from the mesh to the bounds of `viewport` that `camera` shows.
407 /// It is exact for a flat mesh; for a curved one it follows the plane
408 /// that touches the mesh at the content's middle. It sends the part
409 /// nearer than the camera's near plane to w below [`NEAR_PLANE`], which
410 /// [`ProjectiveTransform::front_polygon`] cuts away. `None` for content
411 /// of no size, or for a mesh with no texture plane.
412 pub fn content_transform(
413 &self,
414 camera: &Camera,
415 viewport: Size,
416 size: Size,
417 ) -> Option<ProjectiveTransform> {
418 if !(size.width > 0.0 && size.height > 0.0) {
419 return None;
420 }
421 let plane = self.mesh.texture_plane()?;
422 let to_camera = self.transform.then(&camera.view);
423 let origin = to_camera.transform_point(plane.origin);
424 let right = to_camera.transform_vector(plane.u_axis) * (1.0 / size.width);
425 let down = to_camera.transform_vector(plane.v_axis) * (1.0 / size.height);
426 let (center_x, center_y) = (viewport.width * 0.5, viewport.height * 0.5);
427 let focal_length = camera.focal_length;
428 let column = |v: Vec3| {
429 [
430 v.x + center_x * v.z / focal_length,
431 v.y + center_y * v.z / focal_length,
432 v.z / focal_length,
433 ]
434 };
435 let [a, b, c] = [column(right), column(down), column(origin)];
436 Some(ProjectiveTransform::from_rows([
437 [a[0], b[0], c[0]],
438 [a[1], b[1], c[1]],
439 [a[2], b[2], c[2]],
440 ]))
441 }
442
443 /// The map from the content space of a composable of `size` on the mesh
444 /// into the scene: x and y in the content's pixels from its top left, z
445 /// into the surface, away from the side it shows its content to, a unit
446 /// as long as a content pixel. A mesh surface inside this content is
447 /// placed in that space. It is exact for a flat mesh; for a curved one it
448 /// follows the plane that touches the mesh at the content's middle.
449 /// `None` for content of no size, or for a mesh with no texture plane.
450 pub fn content_to_scene(&self, size: Size) -> Option<Mat4> {
451 if !(size.width > 0.0 && size.height > 0.0) {
452 return None;
453 }
454 let plane = self.mesh.texture_plane()?;
455 let right = self.transform.transform_vector(plane.u_axis) * (1.0 / size.width);
456 let down = self.transform.transform_vector(plane.v_axis) * (1.0 / size.height);
457 let into = right.cross(down).normalized()? * (right.length() * down.length()).sqrt();
458 Some(Mat4::from_axes(
459 right,
460 down,
461 into,
462 self.transform.transform_point(plane.origin),
463 ))
464 }
465
466 /// This surface with its transform followed by `to_scene`: a surface
467 /// inside another's content, placed in the scene through the other's
468 /// [`content_to_scene`](Self::content_to_scene). Glass keeps its
469 /// thickness in the units of the space it is placed from.
470 pub fn placed_by(&self, to_scene: &Mat4) -> Self {
471 let material = match &self.material {
472 Material::Glass(glass) => Material::Glass(Glass {
473 thickness: glass.thickness * to_scene.length_scale(),
474 ..*glass
475 }),
476 material => material.clone(),
477 };
478 Self {
479 mesh: Arc::clone(&self.mesh),
480 transform: self.transform.then(to_scene),
481 material,
482 casts_shadow: self.casts_shadow,
483 solid: self.solid,
484 }
485 }
486
487 /// Where the content point `point` of a composable of `size` lies in the
488 /// scene.
489 pub fn point_at(&self, point: Point, size: Size) -> Option<Vec3> {
490 let uv = [
491 point.x / size.width.max(f32::MIN_POSITIVE),
492 point.y / size.height.max(f32::MIN_POSITIVE),
493 ];
494 self.mesh
495 .point_at(uv)
496 .map(|position| self.transform.transform_point(position))
497 }
498}
499
500/// Where a ray meets a [`MeshSurface`].
501#[derive(Clone, Copy, Debug, PartialEq)]
502pub struct SurfaceHit {
503 /// How far along the ray, in units of its direction.
504 pub distance: f32,
505 /// The mesh triangle the ray meets.
506 pub triangle: u32,
507 /// The content point at the hit.
508 pub point: Point,
509}
510
511impl SurfaceHit {
512 fn of(hit: MeshHit, size: Size) -> Self {
513 Self {
514 distance: hit.distance,
515 triangle: hit.triangle,
516 point: content_point(hit.uv, size),
517 }
518 }
519}
520
521fn content_point([u, v]: [f32; 2], size: Size) -> Point {
522 Point::new(u * size.width, v * size.height)
523}
524
525/// Where a layer sits in 3D, as its graphics layer carries it.
526///
527/// Inside a scene, every layer takes a frame from its parent's: its place
528/// in its parent's layout, turned into 3D on the parent's plane, with z
529/// away from the side the content shows to. A layer that carries a space
530/// changes the frame its descendants take.
531#[derive(Clone, Debug, PartialEq)]
532pub enum LayerSpace {
533 /// The layer shows a scene: its children's frames are the scene's
534 /// space. Inside another scene, the layer's rect is a doorway into it.
535 Scene(Scene3d),
536 /// The layer's content is drawn on a mesh placed in the layer's frame,
537 /// and its children's frames lie in that content.
538 Surface(MeshSurface),
539 /// Moves the frames of the layer's descendants in its own frame, as a
540 /// group of 3D objects moves together. The layer's own draws stay where
541 /// they would be without it.
542 Transform(Mat4),
543}