Skip to main content

tesseract/
tesseract.rs

1use glam::Vec4;
2use loam::app::session::{launch, Orbit, SessionApp};
3use loam::math::{Bivector, Bivector4, EuclideanR4};
4use loam::runtime::host::{HostConfig, HostError};
5use loam::runtime::{
6    ActionId, Bindings, Command, Dispatch, DomainBuilder, DomainError, Instance, Key, LogCapacity,
7    Material, Outcome, Phase, Pose, PreparedGeometry, Projection4, Rejection, Session, SimConfig,
8    SpawnBundle, ViewSpec,
9};
10use loam::shape::polytope::Polytope4;
11
12const PAUSE: ActionId = ActionId(0);
13const RESET: ActionId = ActionId(1);
14const SPIN_RATE: f32 = 0.4;
15const FOCAL_DISTANCE: f32 = 2.0;
16const POLYTOPE_SCALE: f32 = 1.5;
17
18#[derive(Clone, Copy)]
19struct Spin {
20    omega: Bivector4,
21    paused: bool,
22}
23
24loam::runtime::stores! {
25    #[derive(Default)]
26    pub struct TesseractStores {
27        spin: Store<Spin>,
28        camera: Value<Orbit>,
29    }
30}
31
32fn build(
33    args: loam::app::args::Args,
34) -> Result<(Session<TesseractStores>, SessionApp<TesseractStores>), HostError> {
35    let mut session = Session::new(TesseractStores::default(), SimConfig::default());
36    let r4 = session
37        .register_domain(DomainBuilder::new("r4", EuclideanR4).tracked(LogCapacity::default()));
38    let topology = Polytope4::Tesseract.topology();
39    let edges = session.prepare(PreparedGeometry::edges_of(topology, POLYTOPE_SCALE));
40    let white = session.add_material(Material::lines([1.0, 1.0, 1.0, 0.95], 1.6));
41    let root = session.views().root();
42
43    session.dispatch(|d| -> Result<(), Rejection> {
44        let spin = Spin {
45            omega: Bivector4::basis(2) * SPIN_RATE,
46            paused: false,
47        };
48        d.spawn(
49            SpawnBundle::new()
50                .at(r4, Pose::at(Vec4::ZERO))
51                .instance(Instance::new(edges, white))
52                .row(spin),
53        )?;
54        let eye = Pose::at(Vec4::W * FOCAL_DISTANCE);
55        let eye = d.spawn(SpawnBundle::new().at(r4, eye))?;
56        let projection = Projection4 {
57            focal: FOCAL_DISTANCE,
58        };
59        d.domains
60            .typed(r4)?
61            .add_view(ViewSpec::new(root, eye, projection))?;
62        Ok(())
63    })?;
64
65    session.system(
66        Phase::Dispatch,
67        "actions",
68        |ctx: loam::runtime::Ctx<'_, TesseractStores>| {
69            if ctx.input.pressed(PAUSE) {
70                ctx.commands
71                    .try_app_fn("pause", |d: &mut Dispatch<'_, TesseractStores>| {
72                        for (_, spin) in d.app.spin.iter_mut() {
73                            spin.paused = !spin.paused;
74                        }
75                        Ok(Outcome::Done)
76                    });
77            }
78            if ctx.input.pressed(RESET) {
79                ctx.commands.submit(Command::Reset);
80            }
81            Ok(())
82        },
83    );
84
85    let mut orbit = Orbit::around([0.0; 3], 5.0);
86    orbit.pitch = -0.15;
87    session.app.camera.set(orbit);
88    loam::app::session::orbit(&mut session, |app: &mut TesseractStores| {
89        app.camera.get_mut()
90    });
91
92    session.system(
93        Phase::Simulation,
94        "spin",
95        move |ctx: loam::runtime::Ctx<'_, TesseractStores>| -> Result<(), DomainError> {
96            let r4 = ctx.domains.typed(r4)?;
97            for (entity, spin) in ctx.app.spin.iter() {
98                if spin.paused {
99                    continue;
100                }
101                if let Some(pose) = r4.poses().get(entity) {
102                    let frame = ((spin.omega * ctx.step.dt).exp() * pose.frame).normalize();
103                    r4.set_frame(entity, frame)?;
104                }
105            }
106            Ok(())
107        },
108    );
109
110    session.set_initial()?;
111    let bindings = Bindings::new()
112        .key(Key::Letter('t'), PAUSE)
113        .key(Key::Space, PAUSE)
114        .key(Key::Letter('r'), RESET);
115    let app =
116        SessionApp::with_args(HostConfig::new("tesseract", bindings), args).recover_on_fault(RESET);
117    Ok((session, app))
118}
119
120fn main() -> Result<(), HostError> {
121    launch(build)
122}