polytope_playground/
display.rs1use 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}