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(|_| XRJointSpace::new(global, session, id, joint, hand_joint, CanGc::note()))
139        });
140        reflect_dom_object(
141            Box::new(XRHand::new_inherited(source, &spaces)),
142            global,
143            can_gc,
144        )
145    }
146}
147
148impl XRHandMethods<crate::DomTypeHolder> for XRHand {
149    /// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
150    fn Size(&self) -> u32 {
151        XRHandJoint::Pinky_finger_tip as u32 + 1
152    }
153
154    /// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
155    fn Get(&self, joint_name: XRHandJoint) -> DomRoot<XRJointSpace> {
156        let joint = JOINT_SPACE_MAP
157            .iter()
158            .find(|&&(key, _)| key == joint_name)
159            .map(|&(_, joint)| joint)
160            .expect("Invalid joint name");
161        self.spaces
162            .get(joint)
163            .map(|j| DomRoot::from_ref(&**j))
164            .expect("Failed to get joint pose")
165    }
166}
167
168/// A wrapper to work around a crown error—Root<T> has a crown annotation on it that is not present
169/// on the Iterable::Value associated type. The absence is harmless in this case.
170pub(crate) struct ValueWrapper(pub DomRoot<XRJointSpace>);
171
172impl ToJSValConvertible for ValueWrapper {
173    #[expect(unsafe_code)]
174    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
175        unsafe { self.0.to_jsval(cx, rval) }
176    }
177}
178
179impl Iterable for XRHand {
180    type Key = XRHandJoint;
181    type Value = ValueWrapper;
182
183    fn get_iterable_length(&self) -> u32 {
184        JOINT_SPACE_MAP.len() as u32
185    }
186
187    fn get_value_at_index(&self, n: u32) -> ValueWrapper {
188        let joint = JOINT_SPACE_MAP[n as usize].1;
189        self.spaces
190            .get(joint)
191            .map(|j| ValueWrapper(DomRoot::from_ref(&**j)))
192            .expect("Failed to get joint pose")
193    }
194
195    fn get_key_at_index(&self, n: u32) -> XRHandJoint {
196        JOINT_SPACE_MAP[n as usize].0
197    }
198}