Skip to main content

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}