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}