Skip to main content

script/dom/webxr/
xrframe.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::gc::CustomAutoRooterGuard;
10use js::typedarray::Float32Array;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12use webxr_api::{Frame, LayerId, SubImages};
13
14use crate::dom::bindings::codegen::Bindings::XRFrameBinding::XRFrameMethods;
15use crate::dom::bindings::error::Error;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::num::Finite;
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::window::Window;
21use crate::dom::xrhittestresult::XRHitTestResult;
22use crate::dom::xrhittestsource::XRHitTestSource;
23use crate::dom::xrjointpose::XRJointPose;
24use crate::dom::xrjointspace::XRJointSpace;
25use crate::dom::xrpose::XRPose;
26use crate::dom::xrreferencespace::XRReferenceSpace;
27use crate::dom::xrsession::{ApiPose, XRSession};
28use crate::dom::xrspace::XRSpace;
29use crate::dom::xrviewerpose::XRViewerPose;
30
31#[dom_struct]
32pub(crate) struct XRFrame {
33    reflector_: Reflector,
34    session: Dom<XRSession>,
35    #[no_trace]
36    data: Frame,
37    active: Cell<bool>,
38    animation_frame: Cell<bool>,
39}
40
41impl XRFrame {
42    fn new_inherited(session: &XRSession, data: Frame) -> XRFrame {
43        XRFrame {
44            reflector_: Reflector::new(),
45            session: Dom::from_ref(session),
46            data,
47            active: Cell::new(false),
48            animation_frame: Cell::new(false),
49        }
50    }
51
52    pub(crate) fn new(
53        cx: &mut JSContext,
54        window: &Window,
55        session: &XRSession,
56        data: Frame,
57    ) -> DomRoot<XRFrame> {
58        reflect_dom_object_with_cx(Box::new(XRFrame::new_inherited(session, data)), window, cx)
59    }
60
61    /// <https://immersive-web.github.io/webxr/#xrframe-active>
62    pub(crate) fn set_active(&self, active: bool) {
63        self.active.set(active);
64    }
65
66    /// <https://immersive-web.github.io/webxr/#xrframe-animationframe>
67    pub(crate) fn set_animation_frame(&self, animation_frame: bool) {
68        self.animation_frame.set(animation_frame);
69    }
70
71    pub(crate) fn get_pose(&self, space: &XRSpace) -> Option<ApiPose> {
72        space.get_pose(&self.data)
73    }
74
75    pub(crate) fn get_sub_images(&self, layer_id: LayerId) -> Option<&SubImages> {
76        self.data
77            .sub_images
78            .iter()
79            .find(|sub_images| sub_images.layer_id == layer_id)
80    }
81}
82
83impl XRFrameMethods<crate::DomTypeHolder> for XRFrame {
84    /// <https://immersive-web.github.io/webxr/#dom-xrframe-session>
85    fn Session(&self) -> DomRoot<XRSession> {
86        DomRoot::from_ref(&self.session)
87    }
88
89    /// <https://www.w3.org/TR/webxr/#dom-xrframe-predicteddisplaytime>
90    fn PredictedDisplayTime(&self) -> Finite<f64> {
91        // TODO: If inline, return the same value
92        // as the timestamp passed to XRFrameRequestCallback
93        Finite::new(self.data.predicted_display_time)
94            .expect("Failed to create predictedDisplayTime")
95    }
96
97    /// <https://immersive-web.github.io/webxr/#dom-xrframe-getviewerpose>
98    fn GetViewerPose(
99        &self,
100        cx: &mut JSContext,
101        reference: &XRReferenceSpace,
102    ) -> Result<Option<DomRoot<XRViewerPose>>, Error> {
103        if self.session != reference.upcast::<XRSpace>().session() {
104            return Err(Error::InvalidState(None));
105        }
106
107        if !self.active.get() || !self.animation_frame.get() {
108            return Err(Error::InvalidState(None));
109        }
110
111        let to_base = if let Some(to_base) = reference.get_base_transform(&self.data) {
112            to_base
113        } else {
114            return Ok(None);
115        };
116        let viewer_pose = if let Some(pose) = self.data.pose.as_ref() {
117            pose
118        } else {
119            return Ok(None);
120        };
121        Ok(Some(XRViewerPose::new(
122            cx,
123            self.global().as_window(),
124            &self.session,
125            to_base,
126            viewer_pose,
127        )))
128    }
129
130    /// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
131    fn GetPose(
132        &self,
133        cx: &mut JSContext,
134        space: &XRSpace,
135        base_space: &XRSpace,
136    ) -> Result<Option<DomRoot<XRPose>>, Error> {
137        if self.session != space.session() || self.session != base_space.session() {
138            return Err(Error::InvalidState(None));
139        }
140        if !self.active.get() {
141            return Err(Error::InvalidState(None));
142        }
143        let space = if let Some(space) = self.get_pose(space) {
144            space
145        } else {
146            return Ok(None);
147        };
148        let base_space = if let Some(r) = self.get_pose(base_space) {
149            r
150        } else {
151            return Ok(None);
152        };
153        let pose = space.then(&base_space.inverse());
154        Ok(Some(XRPose::new(cx, self.global().as_window(), pose)))
155    }
156
157    /// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
158    fn GetJointPose(
159        &self,
160        cx: &mut JSContext,
161        space: &XRJointSpace,
162        base_space: &XRSpace,
163    ) -> Result<Option<DomRoot<XRJointPose>>, Error> {
164        if self.session != space.upcast::<XRSpace>().session() ||
165            self.session != base_space.session()
166        {
167            return Err(Error::InvalidState(None));
168        }
169        if !self.active.get() {
170            return Err(Error::InvalidState(None));
171        }
172        let joint_frame = if let Some(frame) = space.frame(&self.data) {
173            frame
174        } else {
175            return Ok(None);
176        };
177        let base_space = if let Some(r) = self.get_pose(base_space) {
178            r
179        } else {
180            return Ok(None);
181        };
182        let pose = joint_frame.pose.then(&base_space.inverse());
183        Ok(Some(XRJointPose::new(
184            cx,
185            self.global().as_window(),
186            pose.cast_unit(),
187            Some(joint_frame.radius),
188        )))
189    }
190
191    /// <https://immersive-web.github.io/hit-test/#dom-xrframe-gethittestresults>
192    fn GetHitTestResults(
193        &self,
194        cx: &mut JSContext,
195        source: &XRHitTestSource,
196    ) -> Vec<DomRoot<XRHitTestResult>> {
197        self.data
198            .hit_test_results
199            .iter()
200            .filter(|r| r.id == source.id())
201            .map(|r| XRHitTestResult::new(cx, self.global().as_window(), *r, self))
202            .collect()
203    }
204
205    /// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-filljointradii>
206    fn FillJointRadii(
207        &self,
208        joint_spaces: Vec<DomRoot<XRJointSpace>>,
209        mut radii: CustomAutoRooterGuard<Float32Array>,
210    ) -> Result<bool, Error> {
211        if !self.active.get() {
212            return Err(Error::InvalidState(None));
213        }
214
215        for joint_space in &joint_spaces {
216            if self.session != joint_space.upcast::<XRSpace>().session() {
217                return Err(Error::InvalidState(None));
218            }
219        }
220
221        if joint_spaces.len() > radii.len() {
222            return Err(Error::Type(
223                c"Length of radii does not match length of joint spaces".to_owned(),
224            ));
225        }
226
227        let mut radii_vec = radii.to_vec();
228        let mut all_valid = true;
229        radii_vec.iter_mut().enumerate().for_each(|(i, radius)| {
230            if let Some(joint_frame) = joint_spaces
231                .get(i)
232                .and_then(|joint_space| joint_space.frame(&self.data))
233            {
234                *radius = joint_frame.radius;
235            } else {
236                all_valid = false;
237            }
238        });
239
240        if !all_valid {
241            radii_vec.fill(f32::NAN);
242        }
243
244        radii.update(&radii_vec);
245
246        Ok(all_valid)
247    }
248
249    /// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-fillposes>
250    fn FillPoses(
251        &self,
252        spaces: Vec<DomRoot<XRSpace>>,
253        base_space: &XRSpace,
254        mut transforms: CustomAutoRooterGuard<Float32Array>,
255    ) -> Result<bool, Error> {
256        if !self.active.get() {
257            return Err(Error::InvalidState(None));
258        }
259
260        for space in &spaces {
261            if self.session != space.session() {
262                return Err(Error::InvalidState(None));
263            }
264        }
265
266        if self.session != base_space.session() {
267            return Err(Error::InvalidState(None));
268        }
269
270        if spaces.len() * 16 > transforms.len() {
271            return Err(Error::Type(
272                c"Transforms array length does not match 16 * spaces length".to_owned(),
273            ));
274        }
275
276        let mut transforms_vec = transforms.to_vec();
277        let mut all_valid = true;
278        spaces.iter().enumerate().for_each(|(i, space)| {
279            let Some(joint_pose) = self.get_pose(space) else {
280                all_valid = false;
281                return;
282            };
283            let Some(base_pose) = self.get_pose(base_space) else {
284                all_valid = false;
285                return;
286            };
287            let pose = joint_pose.then(&base_pose.inverse());
288            let elements = pose.to_transform();
289            let elements_arr = elements.to_array();
290            transforms_vec[i * 16..(i + 1) * 16].copy_from_slice(&elements_arr);
291        });
292
293        if !all_valid {
294            transforms_vec.fill(f32::NAN);
295        }
296
297        transforms.update(&transforms_vec);
298
299        Ok(all_valid)
300    }
301}