script/dom/webxr/
xrray.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 dom_struct::dom_struct;
6use euclid::{Angle, RigidTransform3D, Rotation3D, Vector3D};
7use js::rust::HandleObject;
8use js::typedarray::{Float32, Float32Array};
9use webxr_api::{ApiSpace, Ray};
10
11use crate::dom::bindings::buffer_source::HeapBufferSource;
12use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
13use crate::dom::bindings::codegen::Bindings::XRRayBinding::{XRRayDirectionInit, XRRayMethods};
14use crate::dom::bindings::error::{Error, Fallible};
15use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::dompointreadonly::DOMPointReadOnly;
18use crate::dom::window::Window;
19use crate::dom::xrrigidtransform::XRRigidTransform;
20use crate::script_runtime::{CanGc, JSContext};
21
22#[dom_struct]
23pub(crate) struct XRRay {
24    reflector_: Reflector,
25    #[ignore_malloc_size_of = "defined in webxr"]
26    #[no_trace]
27    ray: Ray<ApiSpace>,
28    #[ignore_malloc_size_of = "defined in mozjs"]
29    matrix: HeapBufferSource<Float32>,
30}
31
32impl XRRay {
33    fn new_inherited(ray: Ray<ApiSpace>) -> XRRay {
34        XRRay {
35            reflector_: Reflector::new(),
36            ray,
37            matrix: HeapBufferSource::default(),
38        }
39    }
40
41    fn new(
42        window: &Window,
43        proto: Option<HandleObject>,
44        ray: Ray<ApiSpace>,
45        can_gc: CanGc,
46    ) -> DomRoot<XRRay> {
47        reflect_dom_object_with_proto(Box::new(XRRay::new_inherited(ray)), window, proto, can_gc)
48    }
49
50    pub(crate) fn ray(&self) -> Ray<ApiSpace> {
51        self.ray
52    }
53}
54
55impl XRRayMethods<crate::DomTypeHolder> for XRRay {
56    /// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray>
57    fn Constructor(
58        window: &Window,
59        proto: Option<HandleObject>,
60        can_gc: CanGc,
61        origin: &DOMPointInit,
62        direction: &XRRayDirectionInit,
63    ) -> Fallible<DomRoot<Self>> {
64        if origin.w != 1.0 {
65            return Err(Error::Type("Origin w coordinate must be 1".into()));
66        }
67        if *direction.w != 0.0 {
68            return Err(Error::Type("Direction w coordinate must be 0".into()));
69        }
70        if *direction.x == 0.0 && *direction.y == 0.0 && *direction.z == 0.0 {
71            return Err(Error::Type(
72                "Direction vector cannot have zero length".into(),
73            ));
74        }
75
76        let origin = Vector3D::new(origin.x as f32, origin.y as f32, origin.z as f32);
77        let direction = Vector3D::new(
78            *direction.x as f32,
79            *direction.y as f32,
80            *direction.z as f32,
81        )
82        .normalize();
83
84        Ok(Self::new(window, proto, Ray { origin, direction }, can_gc))
85    }
86
87    /// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray-transform>
88    fn Constructor_(
89        window: &Window,
90        proto: Option<HandleObject>,
91        can_gc: CanGc,
92        transform: &XRRigidTransform,
93    ) -> Fallible<DomRoot<Self>> {
94        let transform = transform.transform();
95        let origin = transform.translation;
96        let direction = transform
97            .rotation
98            .transform_vector3d(Vector3D::new(0., 0., -1.));
99
100        Ok(Self::new(window, proto, Ray { origin, direction }, can_gc))
101    }
102
103    /// <https://immersive-web.github.io/hit-test/#dom-xrray-origin>
104    fn Origin(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
105        DOMPointReadOnly::new(
106            &self.global(),
107            self.ray.origin.x as f64,
108            self.ray.origin.y as f64,
109            self.ray.origin.z as f64,
110            1.,
111            can_gc,
112        )
113    }
114
115    /// <https://immersive-web.github.io/hit-test/#dom-xrray-direction>
116    fn Direction(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
117        DOMPointReadOnly::new(
118            &self.global(),
119            self.ray.direction.x as f64,
120            self.ray.direction.y as f64,
121            self.ray.direction.z as f64,
122            0.,
123            can_gc,
124        )
125    }
126
127    /// <https://immersive-web.github.io/hit-test/#dom-xrray-matrix>
128    fn Matrix(&self, _cx: JSContext, can_gc: CanGc) -> Float32Array {
129        // https://immersive-web.github.io/hit-test/#xrray-obtain-the-matrix
130        if !self.matrix.is_initialized() {
131            // Step 1
132            let z = Vector3D::new(0., 0., -1.);
133            // Step 2
134            let axis = z.cross(self.ray.direction);
135            // Step 3
136            let cos_angle = z.dot(self.ray.direction);
137            // Step 4
138            let rotation = if cos_angle > -1. && cos_angle < 1. {
139                Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
140            } else if cos_angle == -1. {
141                let axis = Vector3D::new(1., 0., 0.);
142                Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
143            } else {
144                Rotation3D::identity()
145            };
146            // Step 5
147            let translation = self.ray.origin;
148            // Step 6
149            // According to the spec all matrices are column-major,
150            // however euclid uses row vectors so we use .to_array()
151            let arr = RigidTransform3D::new(rotation, translation)
152                .to_transform()
153                .to_array();
154            self.matrix
155                .set_data(_cx, &arr, can_gc)
156                .expect("Failed to set matrix data on XRRAy.")
157        }
158
159        self.matrix
160            .get_typed_array()
161            .expect("Failed to get matrix from XRRay.")
162    }
163}