Skip to main content

loam_runtime/
view.rs

1use bytemuck::{Pod, Zeroable};
2use loam_math::hyperbolic::{
3    klein_to_poincare, poincare_to_klein, H3_DEPTH_ENVELOPE, H3_EYE_CHART_REACH,
4};
5use loam_math::{EuclideanR3, EuclideanR4, HyperbolicH3, Iso3, IsometryGroup, Space};
6use loam_shape::polytope::SectionScratch;
7use loam_shape::{LineMesh, TriangleMesh};
8use std::fmt;
9use std::ops::{Deref, DerefMut, Range};
10use std::sync::Arc;
11
12use crate::domain::{ChartPoint, ChartPose, DomainError, DomainId, DomainSpace, Pose};
13use crate::entity::Entity;
14use crate::session::{MaterialId, PreparedId, Stamp};
15use crate::store::{Cursor, RecordBuffer};
16
17pub(crate) type Vec3 = <EuclideanR3 as Space>::Point;
18pub(crate) type Vec4 = <EuclideanR4 as Space>::Point;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub struct ViewId(u32);
22
23impl ViewId {
24    pub(crate) fn new(index: usize) -> Self {
25        Self(index as u32)
26    }
27
28    pub fn index(self) -> usize {
29        self.0 as usize
30    }
31}
32
33impl fmt::Display for ViewId {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "view {}", self.0)
36    }
37}
38
39/// A slot and a generation; a later `place` that reuses the slot makes the earlier id stale.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub struct ImageSpaceId {
42    slot: u32,
43    generation: u32,
44}
45
46impl ImageSpaceId {
47    pub fn index(self) -> usize {
48        self.slot as usize
49    }
50}
51
52impl fmt::Display for ImageSpaceId {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "image space {} gen {}", self.slot, self.generation)
55    }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct ViewTarget {
60    pub view: ViewId,
61    pub image: ImageSpaceId,
62}
63
64/// An orthonormal frame in an image space; there is no `Mat4` in the view contract.
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub struct Eye {
67    pub position: [f32; 3],
68    pub right: [f32; 3],
69    pub up: [f32; 3],
70    pub forward: [f32; 3],
71    pub fov_y: f32,
72    pub aspect: f32,
73    pub near: f32,
74    pub far: f32,
75}
76
77impl Eye {
78    pub fn looking_at(position: [f32; 3], target: [f32; 3], up: [f32; 3]) -> Self {
79        let forward = (Vec3::from(target) - Vec3::from(position))
80            .try_normalize()
81            .unwrap_or(-Vec3::Z);
82        let right = forward
83            .cross(Vec3::from(up))
84            .try_normalize()
85            .unwrap_or(Vec3::X);
86        Self {
87            position,
88            right: right.to_array(),
89            up: right.cross(forward).to_array(),
90            forward: forward.to_array(),
91            ..Self::default()
92        }
93    }
94
95    fn forward_distance(&self, point: [f32; 3]) -> (Vec3, f32) {
96        let relative = Vec3::from(point) - Vec3::from(self.position);
97        (relative, relative.dot(Vec3::from(self.forward)))
98    }
99}
100
101impl Default for Eye {
102    fn default() -> Self {
103        Self {
104            position: [0.0; 3],
105            right: [1.0, 0.0, 0.0],
106            up: [0.0, 1.0, 0.0],
107            forward: [0.0, 0.0, -1.0],
108            fov_y: 60.0_f32.to_radians(),
109            aspect: 1.0,
110            near: 0.05,
111            far: 100.0,
112        }
113    }
114}
115
116pub struct ImageSpace {
117    pub eye: Eye,
118}
119
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct Rigid {
122    pub pose: Iso3,
123    pub scale: f32,
124}
125
126impl Rigid {
127    pub const IDENTITY: Self = Self {
128        pose: Iso3::IDENTITY,
129        scale: 1.0,
130    };
131
132    pub fn apply(&self, point: [f32; 3]) -> [f32; 3] {
133        EuclideanR3
134            .iso_apply(self.pose, Vec3::from(point) * self.scale)
135            .to_array()
136    }
137
138    /// `self` after `inner`; the inner translation is scaled by the outer scale, so two similarities compose exactly.
139    pub fn compose(&self, inner: &Rigid) -> Rigid {
140        Rigid {
141            pose: EuclideanR3.iso_compose(
142                self.pose,
143                Iso3 {
144                    rotation: inner.pose.rotation,
145                    translation: inner.pose.translation * self.scale,
146                },
147            ),
148            scale: self.scale * inner.scale,
149        }
150    }
151
152    pub fn inverse(&self) -> Rigid {
153        let inverse = EuclideanR3.iso_inverse(self.pose);
154        Rigid {
155            pose: Iso3 {
156                rotation: inverse.rotation,
157                translation: inverse.translation / self.scale,
158            },
159            scale: 1.0 / self.scale,
160        }
161    }
162
163    pub fn direction(&self, direction: [f32; 3]) -> [f32; 3] {
164        EuclideanR3
165            .iso_transport(self.pose, Vec3::ZERO, Vec3::from(direction))
166            .to_array()
167    }
168
169    pub fn ray(&self, ray: &ImageRay) -> ImageRay {
170        ImageRay {
171            origin: self.apply(ray.origin),
172            direction: self.direction(ray.direction),
173        }
174    }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq)]
178pub enum Placement {
179    Rigid(Rigid),
180    Nonlinear(&'static str),
181}
182
183impl Placement {
184    pub fn compose(&self, inner: &Placement) -> Placement {
185        match (self, inner) {
186            (Placement::Rigid(outer), Placement::Rigid(inner)) => {
187                Placement::Rigid(outer.compose(inner))
188            }
189            (Placement::Nonlinear(kind), _) | (_, Placement::Nonlinear(kind)) => {
190                Placement::Nonlinear(kind)
191            }
192        }
193    }
194
195    pub fn rigid(&self) -> Option<Rigid> {
196        match self {
197            Placement::Rigid(rigid) => Some(*rigid),
198            Placement::Nonlinear(_) => None,
199        }
200    }
201}
202
203#[derive(Clone, Copy)]
204struct Placed {
205    parent: ImageSpaceId,
206    placement: Placement,
207    generation: u32,
208    live: bool,
209}
210
211#[derive(Clone, Default)]
212pub struct ViewsSnapshot {
213    root_eye: Eye,
214    placed: Vec<Placed>,
215}
216
217pub struct Views {
218    root: ImageSpace,
219    placed: Vec<Placed>,
220}
221
222impl Views {
223    pub(crate) fn new() -> Self {
224        Self {
225            root: ImageSpace {
226                eye: Eye::default(),
227            },
228            placed: Vec::new(),
229        }
230    }
231
232    pub fn root(&self) -> ImageSpaceId {
233        ImageSpaceId {
234            slot: 0,
235            generation: 0,
236        }
237    }
238
239    pub fn root_mut(&mut self) -> &mut ImageSpace {
240        &mut self.root
241    }
242
243    pub fn get(&self, id: ImageSpaceId) -> Option<&ImageSpace> {
244        (id.slot == 0).then_some(&self.root)
245    }
246
247    pub fn get_mut(&mut self, id: ImageSpaceId) -> Option<&mut ImageSpace> {
248        (id.slot == 0).then_some(&mut self.root)
249    }
250
251    pub(crate) fn place(
252        &mut self,
253        parent: ImageSpaceId,
254        placement: Placement,
255    ) -> Option<ImageSpaceId> {
256        self.to_root(parent)?;
257        let free = self
258            .placed
259            .iter()
260            .position(|placed| !placed.live && placed.generation != u32::MAX);
261        let (slot, generation) = match free {
262            Some(slot) => {
263                let placed = self.placed.get_mut(slot)?;
264                placed.generation += 1;
265                placed.parent = parent;
266                placed.placement = placement;
267                placed.live = true;
268                (slot, placed.generation)
269            }
270            None => {
271                self.placed.push(Placed {
272                    parent,
273                    placement,
274                    generation: 0,
275                    live: true,
276                });
277                (self.placed.len() - 1, 0)
278            }
279        };
280        Some(ImageSpaceId {
281            slot: (slot + 1) as u32,
282            generation,
283        })
284    }
285
286    pub(crate) fn unplace(&mut self, id: ImageSpaceId) {
287        if let Some(placed) = self.slot_mut(id) {
288            placed.live = false;
289        }
290    }
291
292    pub fn placed(&self, id: ImageSpaceId) -> bool {
293        self.slot_of(id).is_some()
294    }
295
296    fn slot_of(&self, id: ImageSpaceId) -> Option<&Placed> {
297        let placed = self.placed.get(id.index().checked_sub(1)?)?;
298        (placed.live && placed.generation == id.generation).then_some(placed)
299    }
300
301    fn slot_mut(&mut self, id: ImageSpaceId) -> Option<&mut Placed> {
302        let placed = self.placed.get_mut(id.index().checked_sub(1)?)?;
303        (placed.generation == id.generation).then_some(placed)
304    }
305
306    /// The composed placement from `image` into the root, `None` for a space never placed, unplaced, or whose slot a later `place` reused.
307    pub fn to_root(&self, image: ImageSpaceId) -> Option<Placement> {
308        let mut composed = Placement::Rigid(Rigid::IDENTITY);
309        let mut current = image;
310        while current.slot != 0 {
311            let placed = self.slot_of(current)?;
312            composed = placed.placement.compose(&composed);
313            current = placed.parent;
314        }
315        Some(composed)
316    }
317
318    pub fn ray(&self, image: ImageSpaceId, ndc: [f32; 2]) -> Option<ImageRay> {
319        let root = self.root_ray(ndc)?;
320        Some(self.to_root(image)?.rigid()?.inverse().ray(&root))
321    }
322
323    fn root_ray(&self, ndc: [f32; 2]) -> Option<ImageRay> {
324        let eye = &self.root.eye;
325        let half = (eye.fov_y * 0.5).tan();
326        let direction = Vec3::from(eye.right) * (ndc[0] * half * eye.aspect)
327            + Vec3::from(eye.up) * (ndc[1] * half)
328            + Vec3::from(eye.forward);
329        Some(ImageRay {
330            origin: eye.position,
331            direction: direction.try_normalize()?.to_array(),
332        })
333    }
334
335    /// The root eye's projective depth of an image-space position; `None` nearer than its near plane.
336    pub fn depth(&self, point: [f32; 3]) -> Option<f32> {
337        let eye = &self.root.eye;
338        let (_, forward) = eye.forward_distance(point);
339        (forward >= eye.near).then(|| eye.near / forward)
340    }
341
342    pub fn ndc(&self, point: [f32; 3]) -> Option<[f32; 2]> {
343        let eye = &self.root.eye;
344        let (relative, forward) = eye.forward_distance(point);
345        if forward < eye.near {
346            return None;
347        }
348        let half = (eye.fov_y * 0.5).tan();
349        Some([
350            relative.dot(Vec3::from(eye.right)) / (forward * half * eye.aspect),
351            relative.dot(Vec3::from(eye.up)) / (forward * half),
352        ])
353    }
354
355    pub(crate) fn snapshot(&self) -> ViewsSnapshot {
356        ViewsSnapshot {
357            root_eye: self.root.eye,
358            placed: self.placed.clone(),
359        }
360    }
361
362    pub(crate) fn restore(&mut self, from: &ViewsSnapshot) {
363        self.root.eye = from.root_eye;
364        self.placed.clear();
365        self.placed.extend_from_slice(&from.placed);
366    }
367}
368
369#[derive(Clone, Copy, Debug, PartialEq)]
370pub struct ImageRay {
371    pub origin: [f32; 3],
372    pub direction: [f32; 3],
373}
374
375impl ImageRay {
376    pub fn at(&self, t: f32) -> [f32; 3] {
377        (Vec3::from(self.origin) + Vec3::from(self.direction) * t).to_array()
378    }
379}
380
381/// `direction` has unit metric length, so `exp(origin, direction * t)` lies at distance `t`.
382pub struct DomainRay<S: Space> {
383    pub origin: S::Point,
384    pub direction: S::Vector,
385}
386
387/// Distances from the viewer between which the map orders depth correctly.
388#[derive(Clone, Copy, Debug, PartialEq)]
389pub struct DepthEnvelope {
390    pub near: f32,
391    pub far: f32,
392}
393
394const UNBOUNDED: DepthEnvelope = DepthEnvelope {
395    near: 0.0,
396    far: f32::INFINITY,
397};
398
399/// Results stay stable for the same inputs; configuration changes replace the map through `ViewStyle::set_mapping`.
400pub trait ViewMapping<S: DomainSpace>: Send + Sync + 'static {
401    fn name(&self) -> &'static str;
402
403    fn image_point(&self, eye: &Pose<S>, point: S::Point) -> Option<[f32; 3]>;
404
405    fn image_local(
406        &self,
407        space: &S,
408        eye: &Pose<S>,
409        pose: &Pose<S>,
410        _relative: &S::Relative,
411        local: S::Point,
412    ) -> Option<[f32; 3]> {
413        self.image_point(eye, space.place(&space.prepare(pose), local).ok()?)
414    }
415
416    /// Emits an ordered image polyline; `None` breaks the line.
417    fn image_segment(
418        &self,
419        space: &S,
420        eye: &Pose<S>,
421        pose: &Pose<S>,
422        relative: &S::Relative,
423        segment: [S::Point; 2],
424        emit: &mut dyn FnMut(f32, Option<[f32; 3]>),
425    ) {
426        emit(
427            0.0,
428            self.image_local(space, eye, pose, relative, segment[0]),
429        );
430        emit(
431            1.0,
432            self.image_local(space, eye, pose, relative, segment[1]),
433        );
434    }
435
436    fn lift(&self, eye: &Pose<S>, ray: &ImageRay) -> Option<DomainRay<S>>;
437
438    fn section(&self, _eye: &Pose<S>, _pose: &Pose<S>) -> Option<SectionCut> {
439        None
440    }
441
442    /// True when `lift` recovers a domain ray from any image ray; a map without one refuses grabs and field bridges by name.
443    fn ray_lift(&self) -> bool {
444        true
445    }
446
447    fn image_radius(&self, _eye: &Pose<S>, _point: S::Point, radius: f32) -> f32 {
448        radius
449    }
450
451    fn depth_envelope(&self) -> DepthEnvelope;
452
453    fn depth_envelope_at(&self, _eye: &Pose<S>) -> DepthEnvelope {
454        self.depth_envelope()
455    }
456}
457
458pub struct ViewStyle<S: DomainSpace> {
459    pub enabled: bool,
460    pub edges: bool,
461    pub section_edges: bool,
462    pub section_faces: bool,
463    pub(crate) mapping: Arc<dyn ViewMapping<S>>,
464}
465
466impl<S: DomainSpace> ViewStyle<S> {
467    pub fn set_mapping(&mut self, mapping: impl ViewMapping<S>) -> &mut Self {
468        self.mapping = Arc::new(mapping);
469        self
470    }
471
472    pub fn mapping(&self) -> &dyn ViewMapping<S> {
473        self.mapping.as_ref()
474    }
475}
476
477impl<S: DomainSpace> Clone for ViewStyle<S> {
478    fn clone(&self) -> Self {
479        Self {
480            enabled: self.enabled,
481            edges: self.edges,
482            section_edges: self.section_edges,
483            section_faces: self.section_faces,
484            mapping: Arc::clone(&self.mapping),
485        }
486    }
487}
488
489pub struct ViewSpec<S: DomainSpace> {
490    pub(crate) image: ImageSpaceId,
491    pub(crate) eye: Entity,
492    pub(crate) subject: Option<Entity>,
493    pub(crate) style: ViewStyle<S>,
494}
495
496impl<S: DomainSpace> Clone for ViewSpec<S> {
497    fn clone(&self) -> Self {
498        Self {
499            image: self.image,
500            eye: self.eye,
501            subject: self.subject,
502            style: self.style.clone(),
503        }
504    }
505}
506
507impl<S: DomainSpace> ViewSpec<S> {
508    pub fn new(image: ImageSpaceId, eye: Entity, mapping: impl ViewMapping<S>) -> Self {
509        Self {
510            image,
511            eye,
512            subject: None,
513            style: ViewStyle {
514                enabled: true,
515                edges: true,
516                section_edges: true,
517                section_faces: true,
518                mapping: Arc::new(mapping),
519            },
520        }
521    }
522
523    pub fn subject(mut self, subject: Entity) -> Self {
524        self.subject = Some(subject);
525        self
526    }
527}
528
529impl<S: DomainSpace> Deref for ViewSpec<S> {
530    type Target = ViewStyle<S>;
531
532    fn deref(&self) -> &Self::Target {
533        &self.style
534    }
535}
536
537impl<S: DomainSpace> DerefMut for ViewSpec<S> {
538    fn deref_mut(&mut self) -> &mut Self::Target {
539        &mut self.style
540    }
541}
542
543#[derive(Clone, Copy, Debug, PartialEq)]
544pub struct SectionCut {
545    /// The cut's last chart coordinate in the eye frame, relative to the entity's origin.
546    pub offset: f32,
547    /// Multiplies a cut point's leading chart coordinates before the entity's own image position is added.
548    pub scale: f32,
549}
550
551pub struct Projection4 {
552    pub focal: f32,
553}
554
555impl Projection4 {
556    fn scale(&self, w: f32) -> Option<f32> {
557        let depth = self.focal - w;
558        (depth > 0.0).then(|| self.focal / depth)
559    }
560}
561
562impl ViewMapping<EuclideanR4> for Projection4 {
563    fn name(&self) -> &'static str {
564        "projection4"
565    }
566
567    fn image_point(
568        &self,
569        eye: &Pose<EuclideanR4>,
570        point: <EuclideanR4 as Space>::Point,
571    ) -> Option<[f32; 3]> {
572        let relative = eye_relative4(eye, point)?;
573        let scale = self.scale(relative.w)?;
574        Some((relative.truncate() * scale).to_array())
575    }
576
577    fn image_local(
578        &self,
579        space: &EuclideanR4,
580        _eye: &Pose<EuclideanR4>,
581        _pose: &Pose<EuclideanR4>,
582        relative: &<EuclideanR4 as DomainSpace>::Relative,
583        local: Vec4,
584    ) -> Option<[f32; 3]> {
585        let relative = space.place_relative(relative, local).ok()?;
586        let scale = self.scale(relative.w)?;
587        Some((relative.truncate() * scale).to_array())
588    }
589
590    fn lift(&self, _eye: &Pose<EuclideanR4>, _ray: &ImageRay) -> Option<DomainRay<EuclideanR4>> {
591        None
592    }
593
594    fn ray_lift(&self) -> bool {
595        false
596    }
597
598    fn image_radius(
599        &self,
600        eye: &Pose<EuclideanR4>,
601        point: <EuclideanR4 as Space>::Point,
602        radius: f32,
603    ) -> f32 {
604        eye_relative4(eye, point)
605            .and_then(|relative| self.scale(relative.w))
606            .map_or(radius, |scale| radius * scale)
607    }
608
609    fn depth_envelope(&self) -> DepthEnvelope {
610        UNBOUNDED
611    }
612}
613
614/// The hyperplane w = `w` of the eye's frame, with w dropped.
615pub struct Section4 {
616    pub w: f32,
617}
618
619impl ViewMapping<EuclideanR4> for Section4 {
620    fn name(&self) -> &'static str {
621        "section4"
622    }
623
624    fn image_point(
625        &self,
626        eye: &Pose<EuclideanR4>,
627        point: <EuclideanR4 as Space>::Point,
628    ) -> Option<[f32; 3]> {
629        Some(eye_relative4(eye, point)?.truncate().to_array())
630    }
631
632    fn image_local(
633        &self,
634        space: &EuclideanR4,
635        _eye: &Pose<EuclideanR4>,
636        _pose: &Pose<EuclideanR4>,
637        relative: &<EuclideanR4 as DomainSpace>::Relative,
638        local: Vec4,
639    ) -> Option<[f32; 3]> {
640        Some(
641            space
642                .place_relative(relative, local)
643                .ok()?
644                .truncate()
645                .to_array(),
646        )
647    }
648
649    fn section(&self, eye: &Pose<EuclideanR4>, pose: &Pose<EuclideanR4>) -> Option<SectionCut> {
650        Some(SectionCut {
651            offset: self.w - eye_relative4(eye, pose.point)?.w,
652            scale: 1.0,
653        })
654    }
655
656    fn lift(&self, eye: &Pose<EuclideanR4>, ray: &ImageRay) -> Option<DomainRay<EuclideanR4>> {
657        let direction = Vec3::from(ray.direction).try_normalize()?;
658        let origin = Vec3::from(ray.origin).extend(self.w);
659        Some(DomainRay {
660            origin: EuclideanR4.place(&EuclideanR4.prepare(eye), origin).ok()?,
661            direction: EuclideanR4.carry(eye, origin, direction.extend(0.0)).ok()?,
662        })
663    }
664
665    fn depth_envelope(&self) -> DepthEnvelope {
666        UNBOUNDED
667    }
668}
669
670fn eye_relative4(eye: &Pose<EuclideanR4>, point: Vec4) -> Option<Vec4> {
671    EuclideanR4.local(eye, point).ok()
672}
673
674pub struct Klein;
675
676impl ViewMapping<HyperbolicH3> for Klein {
677    fn name(&self) -> &'static str {
678        "klein"
679    }
680
681    fn image_point(
682        &self,
683        eye: &Pose<HyperbolicH3>,
684        point: <HyperbolicH3 as Space>::Point,
685    ) -> Option<[f32; 3]> {
686        // Cannon, Floyd, Kenyon, Parry, Hyperbolic Geometry, 1997, ยง7: Klein is the hyperboloid seen from the origin.
687        Some(poincare_to_klein(HyperbolicH3.local(eye, point).ok()?).to_array())
688    }
689
690    fn image_local(
691        &self,
692        space: &HyperbolicH3,
693        _eye: &Pose<HyperbolicH3>,
694        _pose: &Pose<HyperbolicH3>,
695        relative: &<HyperbolicH3 as DomainSpace>::Relative,
696        local: Vec3,
697    ) -> Option<[f32; 3]> {
698        Some(poincare_to_klein(space.place_relative(relative, local).ok()?).to_array())
699    }
700
701    fn lift(&self, eye: &Pose<HyperbolicH3>, ray: &ImageRay) -> Option<DomainRay<HyperbolicH3>> {
702        let origin = Vec3::from(ray.origin);
703        let direction = Vec3::from(ray.direction).try_normalize()?;
704        let inside = 1.0 - origin.length_squared();
705        if inside <= 0.0 {
706            return None;
707        }
708        let along = origin.dot(direction);
709        let exit = -along + (along * along + inside).sqrt();
710        let from = klein_to_poincare(origin);
711        let toward = klein_to_poincare(origin + direction * (exit * 0.5));
712        let length = HyperbolicH3.distance(from, toward);
713        if length <= 0.0 {
714            return None;
715        }
716        let unit = HyperbolicH3.log(from, toward) * (1.0 / length);
717        Some(DomainRay {
718            origin: HyperbolicH3.place(&HyperbolicH3.prepare(eye), from).ok()?,
719            direction: HyperbolicH3.carry(eye, from, unit).ok()?,
720        })
721    }
722
723    fn depth_envelope(&self) -> DepthEnvelope {
724        klein_depth_envelope(HyperbolicH3.chart_envelope())
725    }
726
727    fn depth_envelope_at(&self, eye: &Pose<HyperbolicH3>) -> DepthEnvelope {
728        klein_depth_envelope(HyperbolicH3.distance(Vec3::ZERO, eye.point))
729    }
730}
731
732pub fn klein_depth_envelope(eye_chart_distance: f32) -> DepthEnvelope {
733    DepthEnvelope {
734        near: 0.0,
735        far: (H3_EYE_CHART_REACH + H3_DEPTH_ENVELOPE - eye_chart_distance)
736            .clamp(0.0, H3_DEPTH_ENVELOPE),
737    }
738}
739
740pub struct Identity3;
741
742impl ViewMapping<EuclideanR3> for Identity3 {
743    fn name(&self) -> &'static str {
744        "identity3"
745    }
746
747    fn image_point(
748        &self,
749        eye: &Pose<EuclideanR3>,
750        point: <EuclideanR3 as Space>::Point,
751    ) -> Option<[f32; 3]> {
752        Some(EuclideanR3.local(eye, point).ok()?.to_array())
753    }
754
755    fn image_local(
756        &self,
757        space: &EuclideanR3,
758        _eye: &Pose<EuclideanR3>,
759        _pose: &Pose<EuclideanR3>,
760        relative: &<EuclideanR3 as DomainSpace>::Relative,
761        local: Vec3,
762    ) -> Option<[f32; 3]> {
763        Some(space.place_relative(relative, local).ok()?.to_array())
764    }
765
766    fn lift(&self, eye: &Pose<EuclideanR3>, ray: &ImageRay) -> Option<DomainRay<EuclideanR3>> {
767        let origin = Vec3::from(ray.origin);
768        let direction = Vec3::from(ray.direction).try_normalize()?;
769        Some(DomainRay {
770            origin: EuclideanR3.place(&EuclideanR3.prepare(eye), origin).ok()?,
771            direction: EuclideanR3.carry(eye, origin, direction).ok()?,
772        })
773    }
774
775    fn depth_envelope(&self) -> DepthEnvelope {
776        UNBOUNDED
777    }
778}
779
780pub(crate) fn ball_entry(along: f32, outside: f32) -> Option<f32> {
781    if outside <= 0.0 {
782        return Some(0.0);
783    }
784    let discriminant = along * along - outside;
785    if discriminant < 0.0 {
786        return None;
787    }
788    let entry = -along - discriminant.sqrt();
789    (entry >= 0.0).then_some(entry)
790}
791
792pub(crate) fn image_hit(ray: &ImageRay, center: [f32; 3], radius: f32) -> Option<f32> {
793    let offset = Vec3::from(ray.origin) - Vec3::from(center);
794    ball_entry(
795        offset.dot(Vec3::from(ray.direction)),
796        offset.length_squared() - radius * radius,
797    )
798}
799
800#[derive(Clone, Copy, Debug, PartialEq)]
801pub struct InstanceRecord {
802    pub entity: Entity,
803    pub geometry: PreparedId,
804    pub material: MaterialId,
805    pub pose: ChartPose,
806    pub image_point: [f32; 3],
807}
808
809/// Instance vertex layout of `loam-render`'s `line_raster.wgsl`.
810#[repr(C)]
811#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
812pub struct SegmentRecord {
813    pub start: [f32; 3],
814    pub _pad0: f32,
815    pub end: [f32; 3],
816    pub _pad1: f32,
817    pub start_color: [f32; 4],
818    pub end_color: [f32; 4],
819    pub width_px: f32,
820    pub _pad2: [f32; 3],
821}
822
823/// One point of a view in its image space with a radius in pixels; `loam-render`'s `PointPass` builds its mesh from these.
824#[repr(C)]
825#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
826pub struct PointRecord {
827    pub position: [f32; 3],
828    pub radius_px: f32,
829    pub color: [f32; 4],
830}
831
832#[repr(C)]
833#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
834pub struct TriangleRecord {
835    pub vertices: [[f32; 3]; 3],
836    pub color: [f32; 4],
837}
838
839#[derive(Default)]
840pub(crate) struct SectionScratchpad {
841    pub(crate) rotated: Vec<Vec4>,
842    pub(crate) cut: SectionScratch,
843    pub(crate) faces: TriangleMesh<3>,
844    pub(crate) perimeter: LineMesh<3>,
845}
846
847#[derive(Clone, Copy, Debug, PartialEq, Eq)]
848pub enum RefusalSource {
849    Instance,
850    Segment,
851    Section,
852}
853
854#[derive(Clone, Copy, Debug, PartialEq, Eq)]
855pub struct ViewRefusal {
856    pub entity: Entity,
857    pub error: DomainError,
858    pub source: RefusalSource,
859}
860
861#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
862pub struct ViewRefusals {
863    pub count: u32,
864    pub first: Option<ViewRefusal>,
865    pub last: Option<ViewRefusal>,
866}
867
868impl ViewRefusals {
869    pub(crate) fn record(&mut self, entity: Entity, error: DomainError, source: RefusalSource) {
870        let refusal = ViewRefusal {
871            entity,
872            error,
873            source,
874        };
875        self.count = self.count.saturating_add(1);
876        self.first.get_or_insert(refusal);
877        self.last = Some(refusal);
878    }
879}
880
881const NO_OUTPUT: u32 = u32::MAX;
882
883#[derive(Clone)]
884pub(crate) struct EntityOutput {
885    pub(crate) entity: Entity,
886    pub(crate) section_segments: Range<usize>,
887    pub(crate) line_segments: Range<usize>,
888    pub(crate) triangles: Range<usize>,
889    pub(crate) instance: bool,
890}
891
892#[derive(Default)]
893pub(crate) struct ViewOutputCache {
894    entries: Vec<EntityOutput>,
895    positions: Vec<u32>,
896}
897
898impl ViewOutputCache {
899    pub(crate) fn clear(&mut self) {
900        self.entries.clear();
901        self.positions.fill(NO_OUTPUT);
902    }
903
904    pub(crate) fn push(&mut self, output: EntityOutput) {
905        let slot = output.entity.key().slot() as usize;
906        if slot >= self.positions.len() {
907            self.positions.resize(slot + 1, NO_OUTPUT);
908        }
909        self.positions[slot] = self.entries.len() as u32;
910        self.entries.push(output);
911    }
912
913    pub(crate) fn get(&self, entity: Entity) -> Option<&EntityOutput> {
914        let position = *self.positions.get(entity.key().slot() as usize)?;
915        if position == NO_OUTPUT || self.entries[position as usize].entity != entity {
916            return None;
917        }
918        Some(&self.entries[position as usize])
919    }
920
921    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut EntityOutput> {
922        self.entries.iter_mut()
923    }
924}
925
926#[derive(Default)]
927pub struct ViewRecords {
928    pub instances: RecordBuffer<InstanceRecord>,
929    pub(crate) segments: Vec<SegmentRecord>,
930    pub(crate) triangles: Vec<TriangleRecord>,
931    pub(crate) refusals: ViewRefusals,
932    pub(crate) scratch: SectionScratchpad,
933    pub(crate) output: ViewOutputCache,
934    pub(crate) patch_segments: Vec<SegmentRecord>,
935    pub(crate) patch_triangles: Vec<TriangleRecord>,
936    pub(crate) changed: Vec<Entity>,
937    pub(crate) poses: Cursor,
938    pub(crate) attachments: Cursor,
939    pub(crate) revision: u32,
940    pub(crate) built: Stamp,
941    pub(crate) alpha: f32,
942    pub(crate) merged: Vec<Entity>,
943    pub(crate) generation: u64,
944}
945
946impl ViewRecords {
947    pub fn segments(&self) -> &[SegmentRecord] {
948        &self.segments
949    }
950
951    pub fn triangles(&self) -> &[TriangleRecord] {
952        &self.triangles
953    }
954
955    pub fn refusals(&self) -> ViewRefusals {
956        self.refusals
957    }
958
959    /// The publication that last rebuilt these records; a skipped rebuild keeps it.
960    pub fn built(&self) -> Stamp {
961        self.built
962    }
963}
964
965/// `hit` is the domain-space entry point when the view has a lift.
966#[derive(Clone, Copy, Debug, PartialEq)]
967pub struct Pick {
968    pub entity: Entity,
969    pub domain: DomainId,
970    pub view: ViewId,
971    pub image: ImageSpaceId,
972    pub image_point: [f32; 3],
973    pub depth: f32,
974    pub hit: Option<ChartPoint>,
975}
976
977#[derive(Clone, Copy, Debug, PartialEq, Eq)]
978pub struct ViewSummary {
979    pub name: &'static str,
980    pub eye: Entity,
981    pub image: ImageSpaceId,
982    pub ray_lift: bool,
983}