1use std::rc::Rc;
4
5use sim_kernel::{Error, Expr, Result};
6use sim_lib_scene::{Anchor, AnchorSpace, Transform3};
7use sim_lib_view_device::{DeviceProfile, EncodedScene, GlassesClass, LocalAdapter, glasses_class};
8use sim_value::{access, build};
9
10use crate::PoseView;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct Reprojector {
19 pub max_predict_ms: u64,
21}
22
23impl Reprojector {
24 pub fn new(max_predict_ms: u64) -> Self {
26 Self { max_predict_ms }
27 }
28}
29
30impl LocalAdapter for Reprojector {
31 type State = PoseView;
32
33 fn adapt(
34 &self,
35 scene: &EncodedScene,
36 state: &Self::State,
37 profile: &DeviceProfile,
38 ) -> Result<Rc<Expr>> {
39 if glasses_class(profile) != Some(GlassesClass::Stereo6Dof) {
40 return Ok(scene.shared());
41 }
42 expect_scene_kind(scene.expr(), "spatial")?;
43 let left = project_eye(scene.expr(), state, self.max_predict_ms, Eye::Left)?;
44 let right = project_eye(scene.expr(), state, self.max_predict_ms, Eye::Right)?;
45 Ok(Rc::new(stereo_scene(
46 left,
47 right,
48 state,
49 self.max_predict_ms,
50 )))
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55enum Eye {
56 Left,
57 Right,
58}
59
60impl Eye {
61 fn name(self) -> &'static str {
62 match self {
63 Self::Left => "left",
64 Self::Right => "right",
65 }
66 }
67
68 fn sign(self) -> f64 {
69 match self {
70 Self::Left => -1.0,
71 Self::Right => 1.0,
72 }
73 }
74}
75
76fn project_eye(scene: &Expr, state: &PoseView, max_predict_ms: u64, eye: Eye) -> Result<Expr> {
77 let children = spatial_children(scene)?;
78 let projected = children
79 .iter()
80 .filter_map(|child| project_child(child, state, max_predict_ms, eye).transpose())
81 .collect::<Result<Vec<_>>>()?;
82 Ok(sim_lib_scene::data_map(vec![
83 ("eye", build::sym(eye.name())),
84 ("children", build::list(projected)),
85 ]))
86}
87
88fn project_child(
89 child: &Expr,
90 state: &PoseView,
91 max_predict_ms: u64,
92 eye: Eye,
93) -> Result<Option<Expr>> {
94 if scene_kind(child).as_deref() != Some("panel") {
95 return Ok(Some(child.clone()));
96 }
97 let anchor = Anchor::from_expr(access::required(child, "anchor", "scene/panel")?)?;
98 let transform = Transform3::from_expr(access::required(child, "transform", "scene/panel")?)?;
99 let projected = project_transform(&anchor, &transform, state, max_predict_ms, eye);
100 if !visible_in_frustum(anchor.space, &projected) {
101 return Ok(None);
102 }
103 Ok(Some(projected_panel(child, &anchor, projected, eye)))
104}
105
106fn projected_panel(source: &Expr, anchor: &Anchor, transform: Transform3, eye: Eye) -> Expr {
107 let id = access::field_str(source, "id").unwrap_or("panel");
108 let body = access::field(source, "body").cloned().unwrap_or(Expr::Nil);
109 sim_lib_scene::node(
110 "panel",
111 vec![
112 ("id", build::text(format!("{id}:{}", eye.name()))),
113 ("source-panel", build::text(id)),
114 ("eye", build::sym(eye.name())),
115 ("anchor-rule", build::sym(anchor_rule(anchor.space))),
116 ("body", body),
117 ("anchor", anchor.to_expr()),
118 ("transform", transform.to_expr()),
119 ],
120 )
121}
122
123fn project_transform(
124 anchor: &Anchor,
125 transform: &Transform3,
126 state: &PoseView,
127 max_predict_ms: u64,
128 eye: Eye,
129) -> Transform3 {
130 let mut out = transform.clone();
131 let eye_offset = eye.sign() * state.inter_eye_m / 2.0;
132 match anchor.space {
133 AnchorSpace::Head => {
134 out.translate_m[0] += eye_offset;
135 }
136 AnchorSpace::World => {
137 out.translate_m[0] -= state.translation_m[0];
138 out.translate_m[1] -= state.translation_m[1];
139 out.translate_m[2] -= state.translation_m[2];
140 out.translate_m[0] += state.clamped_yaw_rad(max_predict_ms).sin() * depth(&out);
141 out.translate_m[0] += eye_offset;
142 }
143 AnchorSpace::Screen => {}
144 AnchorSpace::Body | AnchorSpace::Device => {
145 out.translate_m[0] += eye_offset * 0.5;
146 }
147 }
148 out
149}
150
151fn visible_in_frustum(space: AnchorSpace, transform: &Transform3) -> bool {
152 if matches!(space, AnchorSpace::Head | AnchorSpace::Screen) {
153 return true;
154 }
155 let z = transform.translate_m[2];
156 if z > 0.05 {
157 return false;
158 }
159 let distance = depth(transform);
160 let max_x = distance * (52.0_f64.to_radians() / 2.0).tan();
161 transform.translate_m[0].abs() <= max_x
162}
163
164fn depth(transform: &Transform3) -> f64 {
165 transform.translate_m[2].abs().max(1.0)
166}
167
168fn anchor_rule(space: AnchorSpace) -> &'static str {
169 match space {
170 AnchorSpace::Head => "head-locked",
171 AnchorSpace::World => "world-locked",
172 AnchorSpace::Screen => "screen-locked",
173 AnchorSpace::Body => "body-relative",
174 AnchorSpace::Device => "device-relative",
175 }
176}
177
178fn stereo_scene(left: Expr, right: Expr, state: &PoseView, max_predict_ms: u64) -> Expr {
179 sim_lib_scene::node(
180 "stereo",
181 vec![
182 ("left-eye", left),
183 ("right-eye", right),
184 ("sample-seq", build::uint(state.sample_seq)),
185 (
186 "predict-ms",
187 build::uint(state.clamped_predict_ms(max_predict_ms)),
188 ),
189 ("age-ms", build::uint(state.age_ms)),
190 ],
191 )
192}
193
194fn spatial_children(scene: &Expr) -> Result<&[Expr]> {
195 match access::required(scene, "children", "scene/spatial")? {
196 Expr::List(children) => Ok(children),
197 _ => Err(Error::HostError(
198 "scene/spatial children must be a list".to_owned(),
199 )),
200 }
201}
202
203fn expect_scene_kind(scene: &Expr, expected: &str) -> Result<()> {
204 match scene_kind(scene).as_deref() {
205 Some(kind) if kind == expected => Ok(()),
206 _ => Err(Error::HostError(format!("expected scene/{expected}"))),
207 }
208}
209
210fn scene_kind(expr: &Expr) -> Option<String> {
211 let kind = sim_lib_scene::node_kind(expr)?;
212 (kind.namespace.as_deref() == Some(sim_lib_scene::SCENE_NAMESPACE))
213 .then(|| kind.name.to_string())
214}