Skip to main content

polytope_playground/
display.rs

1use loam::math::{Bivector, EuclideanR4, Plane4, Rotor, Rotor4};
2use loam::runtime::{Dispatch, DomainError, DomainHandle, Domains, Rejection};
3
4use crate::consts::BASE_ROTATION_RATE;
5use crate::mode::Mode;
6use crate::Playground;
7
8pub(crate) const DEFAULT_WIREFRAME_WIDTH_PX: f32 = 1.8;
9
10pub(crate) const MAX_WIREFRAME_WIDTH_PX: f32 = 16.0;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub(crate) enum Surface {
14    #[default]
15    Raster,
16    Sdf,
17    Off,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq)]
21pub(crate) struct Display {
22    pub(crate) surface: Surface,
23    pub(crate) wireframe: bool,
24    pub(crate) section_perimeter: bool,
25    pub(crate) wireframe_width_px: f32,
26    pub(crate) wireframe_opacity: f32,
27    pub(crate) single: bool,
28}
29
30impl Default for Display {
31    fn default() -> Self {
32        Self {
33            surface: Surface::Raster,
34            wireframe: false,
35            section_perimeter: true,
36            wireframe_width_px: DEFAULT_WIREFRAME_WIDTH_PX,
37            wireframe_opacity: 1.0,
38            single: false,
39        }
40    }
41}
42
43pub(crate) fn set(app: &mut Playground, display: Display) -> Result<(), Rejection> {
44    if !display.wireframe_width_px.is_finite()
45        || display.wireframe_width_px <= 0.0
46        || display.wireframe_width_px > MAX_WIREFRAME_WIDTH_PX
47    {
48        return Err(Rejection::Unsupported(
49            "wireframe width must be finite and in (0, 16]",
50        ));
51    }
52    if !display.wireframe_opacity.is_finite()
53        || display.wireframe_opacity <= 0.0
54        || display.wireframe_opacity > 1.0
55    {
56        return Err(Rejection::Unsupported(
57            "wireframe opacity must be finite and in (0, 1]",
58        ));
59    }
60    app.display.set(display);
61    Ok(())
62}
63
64pub(crate) fn rotor_at(app: &Playground, time: f32) -> Rotor4 {
65    match *app.mode.get() {
66        Mode::Rotate => {
67            let mut rotor = Rotor4::IDENTITY;
68            for (index, plane) in Plane4::ALL.into_iter().enumerate() {
69                let angle = app.angles.get()[index]
70                    + if app.spin.get().planes[index] {
71                        time * BASE_ROTATION_RATE
72                    } else {
73                        0.0
74                    };
75                rotor = (plane.unit_bivector() * angle).exp() * rotor;
76            }
77            rotor.normalize()
78        }
79        Mode::Toybox => Rotor4::IDENTITY,
80    }
81}
82
83fn apply_turn(
84    app: &Playground,
85    domains: &mut Domains,
86    domain: DomainHandle<EuclideanR4>,
87    turn: Rotor4,
88) -> Result<(), DomainError> {
89    let r4 = domains.typed(domain)?;
90    for (entity, _) in app.slots.iter() {
91        if let Some(mut pose) = r4.poses().get(entity).copied() {
92            pose.frame = (turn * pose.frame).normalize();
93            r4.set_pose(entity, pose)?;
94        }
95    }
96    Ok(())
97}
98
99pub(crate) fn seek(
100    app: &mut Playground,
101    domains: &mut Domains,
102    domain: DomainHandle<EuclideanR4>,
103    time: f32,
104) -> Result<(), DomainError> {
105    if !time.is_finite() || time < 0.0 {
106        return Err(DomainError::Unsupported(
107            "playback time must be finite and nonnegative",
108        ));
109    }
110    if *app.mode.get() == Mode::Toybox {
111        return Err(DomainError::Unsupported("playback belongs to Rotate"));
112    }
113    let before = rotor_at(app, *app.time.get());
114    let after = rotor_at(app, time);
115    apply_turn(app, domains, domain, after * before.inverse())?;
116    app.time.set(time);
117    Ok(())
118}
119
120pub(crate) fn set_plane_angle(
121    dispatch: &mut Dispatch<'_, Playground>,
122    domain: DomainHandle<EuclideanR4>,
123    plane: usize,
124    angle: f32,
125) -> Result<(), Rejection> {
126    if plane >= 6 || !angle.is_finite() {
127        return Err(Rejection::Unsupported("invalid plane angle"));
128    }
129    let time = *dispatch.app.time.get();
130    let before = rotor_at(dispatch.app, time);
131    let advancing = dispatch.app.spin.get().planes[plane];
132    dispatch.app.angles.get_mut()[plane] = angle
133        - if advancing {
134            time * BASE_ROTATION_RATE
135        } else {
136            0.0
137        };
138    let after = rotor_at(dispatch.app, time);
139    apply_turn(
140        dispatch.app,
141        dispatch.domains,
142        domain,
143        after * before.inverse(),
144    )
145    .map_err(Rejection::Domain)
146}