Skip to main content

polytope_playground/
mode.rs

1use loam::math::{Bivector4, EuclideanR4, Plane4, Rotor4};
2use loam::runtime::{
3    Dispatch, DomainHandle, Domains, EdgeShading, Entity, Instance, Pose, Rejection, SpawnBundle,
4};
5
6use crate::catalog::ShapeEntry;
7use crate::consts::BASE_ROTATION_RATE;
8use crate::strip::{Strip, MAX_CELLS, MAX_T_EXTENT, MIN_CELLS, MIN_T_EXTENT};
9use crate::toy;
10use crate::{Card, Playground, Toy};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub(crate) enum Mode {
14    #[default]
15    Rotate,
16    Toybox,
17}
18
19impl Mode {
20    pub(crate) const ALL: [Mode; 2] = [Mode::Rotate, Mode::Toybox];
21
22    pub(crate) fn name(self) -> &'static str {
23        match self {
24            Mode::Rotate => "rotate",
25            Mode::Toybox => "toybox",
26        }
27    }
28
29    pub(crate) fn from_token(token: &str) -> Option<Self> {
30        Mode::ALL.into_iter().find(|mode| mode.name() == token)
31    }
32}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub(crate) struct Spin {
36    pub(crate) planes: [bool; 6],
37    pub(crate) rate: f32,
38    pub(crate) running: bool,
39}
40
41impl Default for Spin {
42    fn default() -> Self {
43        Self {
44            planes: [false, false, true, false, false, false],
45            rate: 1.0,
46            running: false,
47        }
48    }
49}
50
51impl Spin {
52    pub(crate) fn omega(&self) -> Bivector4 {
53        Plane4::ALL
54            .into_iter()
55            .enumerate()
56            .filter(|(index, _)| self.planes[*index])
57            .fold(Bivector4::ZERO, |sum, (_, plane)| {
58                sum + plane.unit_bivector() * (BASE_ROTATION_RATE * self.rate)
59            })
60    }
61}
62
63pub(crate) fn set_mode(
64    dispatch: &mut Dispatch<'_, Playground>,
65    domain: DomainHandle<EuclideanR4>,
66    cards: &[Card],
67    mode: Mode,
68) -> Result<(), Rejection> {
69    let held = *dispatch.app.mode.get();
70    if held == mode {
71        return Ok(());
72    }
73    match (held, mode) {
74        (_, Mode::Toybox) => toy::populate(dispatch, domain, cards)?,
75        (Mode::Toybox, _) => toy::clear(dispatch, domain)?,
76        _ => {}
77    }
78    dispatch.app.strip.get_mut().on = false;
79    dispatch.app.display.get_mut().single = false;
80    dispatch.app.active.set(0);
81    dispatch.app.mode.set(mode);
82    dispatch.app.slice.set(0.0);
83    dispatch.app.spin.get_mut().running = false;
84    Ok(())
85}
86
87pub(crate) fn reset(
88    dispatch: &mut Dispatch<'_, Playground>,
89    domain: DomainHandle<EuclideanR4>,
90    cards: &[Card],
91) -> Result<(), Rejection> {
92    if *dispatch.app.mode.get() == Mode::Toybox {
93        toy::reset(dispatch, domain, cards)?;
94    } else {
95        let r4 = dispatch.domains.typed(domain)?;
96        for (entity, slot) in dispatch.app.slots.iter() {
97            if r4.poses().contains(entity) {
98                r4.set_pose(entity, Pose::at(slot.rest))?;
99            }
100        }
101    }
102    dispatch.app.time.set(0.0);
103    dispatch.app.angles.set([0.0; 6]);
104    dispatch.app.spin.set(Spin::default());
105    dispatch.app.slice.set(0.0);
106    Ok(())
107}
108
109pub(crate) fn throw_toy(
110    dispatch: &mut Dispatch<'_, Playground>,
111    domain: DomainHandle<EuclideanR4>,
112    entity: Entity,
113    pointer: [f32; 3],
114) -> Result<(), Rejection> {
115    if !dispatch.app.toys.contains(entity) {
116        return Ok(());
117    }
118    let rope = *dispatch.app.rope.get();
119    let physics = dispatch
120        .domains
121        .typed(domain)?
122        .physics_mut()
123        .ok_or(Rejection::Unsupported("the domain has no physics"))?;
124    toy::release(physics, entity, pointer, rope).map_err(Rejection::Edit)
125}
126
127pub(crate) fn toggle_plane(
128    dispatch: &mut Dispatch<'_, Playground>,
129    plane: usize,
130) -> Result<(), Rejection> {
131    let held = *dispatch
132        .app
133        .spin
134        .get()
135        .planes
136        .get(plane)
137        .ok_or(Rejection::Unsupported("no such plane"))?;
138    let elapsed = *dispatch.app.time.get() * BASE_ROTATION_RATE;
139    let angle = dispatch
140        .app
141        .angles
142        .get_mut()
143        .get_mut(plane)
144        .ok_or(Rejection::Unsupported("no such plane"))?;
145    *angle += if held { elapsed } else { -elapsed };
146    dispatch.app.spin.get_mut().planes[plane] = !held;
147    Ok(())
148}
149
150pub(crate) fn turn_row(
151    app: &Playground,
152    domains: &mut Domains,
153    domain: DomainHandle<EuclideanR4>,
154    rotor: Rotor4,
155) -> Result<(), Rejection> {
156    if *app.mode.get() != Mode::Rotate {
157        return Err(Rejection::Unsupported("rotation handles belong to Rotate"));
158    }
159    let entities = app.slots.iter().map(|(entity, _)| entity);
160    let r4 = domains.typed(domain)?;
161    for entity in entities {
162        if let Some(mut pose) = r4.poses().get(entity).copied() {
163            pose.frame = (rotor * pose.frame).normalize();
164            r4.set_pose(entity, pose)?;
165        }
166    }
167    Ok(())
168}
169
170pub(crate) fn set_shape(
171    dispatch: &mut Dispatch<'_, Playground>,
172    domain: DomainHandle<EuclideanR4>,
173    slot: usize,
174    entry: ShapeEntry,
175    card: Card,
176) -> Result<(), Rejection> {
177    let held = dispatch
178        .app
179        .slots
180        .iter()
181        .find(|(_, held)| held.index == slot)
182        .map(|(entity, held)| (entity, *held))
183        .ok_or(Rejection::Unsupported("no such slot"))?;
184    let (entity, mut row) = held;
185    if row.entry == entry {
186        return Ok(());
187    }
188    let is_toy = dispatch.app.toys.contains(entity);
189    let polytope = if is_toy {
190        Some(
191            entry
192                .collider_polytope()
193                .ok_or(Rejection::Unsupported("the toy has no collider"))?,
194        )
195    } else {
196        None
197    };
198    let geometry = if is_toy {
199        Some(
200            card.toy_geometry
201                .ok_or(Rejection::Unsupported("the toy has no prepared geometry"))?,
202        )
203    } else {
204        card.geometry
205    };
206    let frame = dispatch
207        .domains
208        .typed(domain)?
209        .poses()
210        .get(entity)
211        .map_or(Rotor4::IDENTITY, |pose| pose.frame);
212    let pose = polytope.map_or_else(
213        || Pose {
214            point: row.rest,
215            frame,
216        },
217        |polytope| toy::pose_at(polytope, row.rest.x),
218    );
219    row.entry = entry;
220    row.rest = pose.point;
221    dispatch.despawn(entity)?;
222    let mut bundle = SpawnBundle::new().at(domain, pose).row(row);
223    if is_toy {
224        bundle = bundle.row(Toy {
225            rest_anchor: pose.point,
226            rest_time: 0.0,
227        });
228    }
229    if let Some(geometry) = geometry {
230        let display = *dispatch.app.display.get();
231        let mode = *dispatch.app.color.get();
232        let shading = card
233            .shades
234            .map_or(EdgeShading::Material, |shades| shades.of(mode));
235        bundle = bundle.instance(
236            Instance::new(geometry, card.material)
237                .shaded(shading)
238                .sectioned(card.cut)
239                .line_style(display.wireframe_width_px, display.wireframe_opacity),
240        );
241    }
242    let spawned = dispatch.spawn(bundle)?;
243    if let Some(polytope) = polytope {
244        toy::add_body(dispatch, domain, spawned, polytope, pose)?;
245    }
246    Ok(())
247}
248
249pub(crate) fn set_strip(app: &mut Playground, mut strip: Strip) -> Result<(), Rejection> {
250    if strip.on && *app.mode.get() == Mode::Toybox {
251        return Err(Rejection::Unsupported("filmstrip belongs to Rotate"));
252    }
253    if !strip.t_extent.is_finite() {
254        return Err(Rejection::Unsupported("the t extent is not finite"));
255    }
256    if !strip.w && !strip.t {
257        return Err(Rejection::Unsupported("the strip needs one axis"));
258    }
259    strip.count_w = strip.count_w.clamp(MIN_CELLS, MAX_CELLS);
260    strip.count_t = strip.count_t.clamp(MIN_CELLS, MAX_CELLS);
261    strip.t_extent = strip.t_extent.clamp(MIN_T_EXTENT, MAX_T_EXTENT);
262    app.strip.set(strip);
263    Ok(())
264}