script/dom/webxr/
xrhand.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 js::jsapi::JSContext;
7use js::rust::MutableHandleValue;
8use webxr_api::{FingerJoint, Hand, Joint};
9
10use crate::dom::bindings::codegen::Bindings::XRHandBinding::{XRHandJoint, XRHandMethods};
11use crate::dom::bindings::conversions::ToJSValConvertible;
12use crate::dom::bindings::iterable::Iterable;
13use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
14use crate::dom::bindings::root::{Dom, DomRoot};
15use crate::dom::globalscope::GlobalScope;
16use crate::dom::xrinputsource::XRInputSource;
17use crate::dom::xrjointspace::XRJointSpace;
18use crate::script_runtime::CanGc;
19
20const JOINT_SPACE_MAP: [(XRHandJoint, Joint); 25] = [
21    (XRHandJoint::Wrist, Joint::Wrist),
22    (XRHandJoint::Thumb_metacarpal, Joint::ThumbMetacarpal),
23    (
24        XRHandJoint::Thumb_phalanx_proximal,
25        Joint::ThumbPhalanxProximal,
26    ),
27    (XRHandJoint::Thumb_phalanx_distal, Joint::ThumbPhalanxDistal),
28    (XRHandJoint::Thumb_tip, Joint::ThumbPhalanxTip),
29    (
30        XRHandJoint::Index_finger_metacarpal,
31        Joint::Index(FingerJoint::Metacarpal),
32    ),
33    (
34        XRHandJoint::Index_finger_phalanx_proximal,
35        Joint::Index(FingerJoint::PhalanxProximal),
36    ),
37    (XRHandJoint::Index_finger_phalanx_intermediate, {
38        Joint::Index(FingerJoint::PhalanxIntermediate)
39    }),
40    (
41        XRHandJoint::Index_finger_phalanx_distal,
42        Joint::Index(FingerJoint::PhalanxDistal),
43    ),
44    (
45        XRHandJoint::Index_finger_tip,
46        Joint::Index(FingerJoint::PhalanxTip),
47    ),
48    (
49        XRHandJoint::Middle_finger_metacarpal,
50        Joint::Middle(FingerJoint::Metacarpal),
51    ),
52    (
53        XRHandJoint::Middle_finger_phalanx_proximal,
54        Joint::Middle(FingerJoint::PhalanxProximal),
55    ),
56    (XRHandJoint::Middle_finger_phalanx_intermediate, {
57        Joint::Middle(FingerJoint::PhalanxIntermediate)
58    }),
59    (
60        XRHandJoint::Middle_finger_phalanx_distal,
61        Joint::Middle(FingerJoint::PhalanxDistal),
62    ),
63    (
64        XRHandJoint::Middle_finger_tip,
65        Joint::Middle(FingerJoint::PhalanxTip),
66    ),
67    (
68        XRHandJoint::Ring_finger_metacarpal,
69        Joint::Ring(FingerJoint::Metacarpal),
70    ),
71    (
72        XRHandJoint::Ring_finger_phalanx_proximal,
73        Joint::Ring(FingerJoint::PhalanxProximal),
74    ),
75    (XRHandJoint::Ring_finger_phalanx_intermediate, {
76        Joint::Ring(FingerJoint::PhalanxIntermediate)
77    }),
78    (
79        XRHandJoint::Ring_finger_phalanx_distal,
80        Joint::Ring(FingerJoint::PhalanxDistal),
81    ),
82    (
83        XRHandJoint::Ring_finger_tip,
84        Joint::Ring(FingerJoint::PhalanxTip),
85    ),
86    (
87        XRHandJoint::Pinky_finger_metacarpal,
88        Joint::Little(FingerJoint::Metacarpal),
89    ),
90    (
91        XRHandJoint::Pinky_finger_phalanx_proximal,
92        Joint::Little(FingerJoint::PhalanxProximal),
93    ),
94    (XRHandJoint::Pinky_finger_phalanx_intermediate, {
95        Joint::Little(FingerJoint::PhalanxIntermediate)
96    }),
97    (
98        XRHandJoint::Pinky_finger_phalanx_distal,
99        Joint::Little(FingerJoint::PhalanxDistal),
100    ),
101    (
102        XRHandJoint::Pinky_finger_tip,
103        Joint::Little(FingerJoint::PhalanxTip),
104    ),
105];
106
107#[dom_struct]
108pub(crate) struct XRHand {
109    reflector_: Reflector,
110    source: Dom<XRInputSource>,
111    #[custom_trace]
112    spaces: Hand<Dom<XRJointSpace>>,
113}
114
115impl XRHand {
116    fn new_inherited(source: &XRInputSource, spaces: &Hand<DomRoot<XRJointSpace>>) -> XRHand {
117        XRHand {
118            reflector_: Reflector::new(),
119            source: Dom::from_ref(source),
120            spaces: spaces.map(|j, _| j.as_ref().map(|j| Dom::from_ref(&**j))),
121        }
122    }
123
124    pub(crate) fn new(
125        global: &GlobalScope,
126        source: &XRInputSource,
127        support: Hand<()>,
128        can_gc: CanGc,
129    ) -> DomRoot<XRHand> {
130        let id = source.id();
131        let session = source.session();
132        let spaces = support.map(|field, joint| {
133            let hand_joint = JOINT_SPACE_MAP
134                .iter()
135                .find(|&&(_, value)| value == joint)
136                .map(|&(hand_joint, _)| hand_joint)
137                .expect("Invalid joint name");
138            field.map(|_| {
139                XRJointSpace::new(
140                    global,
141                    session,
142                    id,
143                    joint,
144                    hand_joint,
145                    CanGc::deprecated_note(),
146                )
147            })
148        });
149        reflect_dom_object(
150            Box::new(XRHand::new_inherited(source, &spaces)),
151            global,
152            can_gc,
153        )
154    }
155}
156
157impl XRHandMethods<crate::DomTypeHolder> for XRHand {
158    /// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
159    fn Size(&self) -> u32 {
160        XRHandJoint::Pinky_finger_tip as u32 + 1
161    }
162
163    /// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
164    fn Get(&self, joint_name: XRHandJoint) -> DomRoot<XRJointSpace> {
165        let joint = JOINT_SPACE_MAP
166            .iter()
167            .find(|&&(key, _)| key == joint_name)
168            .map(|&(_, joint)| joint)
169            .expect("Invalid joint name");
170        self.spaces
171            .get(joint)
172            .map(|j| DomRoot::from_ref(&**j))
173            .expect("Failed to get joint pose")
174    }
175}
176
177/// A wrapper to work around a crown error—Root<T> has a crown annotation on it that is not present
178/// on the Iterable::Value associated type. The absence is harmless in this case.
179pub(crate) struct ValueWrapper(pub DomRoot<XRJointSpace>);
180
181impl ToJSValConvertible for ValueWrapper {
182    #[expect(unsafe_code)]
183    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
184        unsafe { self.0.to_jsval(cx, rval) }
185    }
186}
187
188impl Iterable for XRHand {
189    type Key = XRHandJoint;
190    type Value = ValueWrapper;
191
192    fn get_iterable_length(&self) -> u32 {
193        JOINT_SPACE_MAP.len() as u32
194    }
195
196    fn get_value_at_index(&self, n: u32) -> ValueWrapper {
197        let joint = JOINT_SPACE_MAP[n as usize].1;
198        self.spaces
199            .get(joint)
200            .map(|j| ValueWrapper(DomRoot::from_ref(&**j)))
201            .expect("Failed to get joint pose")
202    }
203
204    fn get_key_at_index(&self, n: u32) -> XRHandJoint {
205        JOINT_SPACE_MAP[n as usize].0
206    }
207}