1use std::cell::Cell;
6use std::rc::Rc;
7
8use base::generic_channel::GenericSender;
9use dom_struct::dom_struct;
10use euclid::{Point2D, Point3D, Rect, RigidTransform3D, Rotation3D, Size2D, Transform3D, Vector3D};
11use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
12use webxr_api::{
13 EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion,
14 MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility,
15};
16
17use crate::conversions::Convert;
18use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
19use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{
20 FakeXRBoundsPoint, FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit,
21 FakeXRViewInit, FakeXRWorldInit,
22};
23use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit;
24use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
25 XRHandedness, XRTargetRayMode,
26};
27use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState;
28use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::refcounted::TrustedPromise;
31use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::fakexrinputcontroller::{FakeXRInputController, init_to_mock_buttons};
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::promise::Promise;
36use crate::script_runtime::CanGc;
37
38#[dom_struct]
39pub(crate) struct FakeXRDevice {
40 reflector: Reflector,
41 #[no_trace]
42 sender: GenericSender<MockDeviceMsg>,
43 #[ignore_malloc_size_of = "defined in webxr-api"]
44 #[no_trace]
45 next_input_id: Cell<InputId>,
46}
47
48impl FakeXRDevice {
49 pub(crate) fn new_inherited(sender: GenericSender<MockDeviceMsg>) -> FakeXRDevice {
50 FakeXRDevice {
51 reflector: Reflector::new(),
52 sender,
53 next_input_id: Cell::new(InputId(0)),
54 }
55 }
56
57 pub(crate) fn new(
58 global: &GlobalScope,
59 sender: GenericSender<MockDeviceMsg>,
60 can_gc: CanGc,
61 ) -> DomRoot<FakeXRDevice> {
62 reflect_dom_object(
63 Box::new(FakeXRDevice::new_inherited(sender)),
64 global,
65 can_gc,
66 )
67 }
68
69 pub(crate) fn disconnect(&self, sender: ProfileGenericCallback<()>) {
70 let _ = self.sender.send(MockDeviceMsg::Disconnect(sender));
71 }
72}
73
74pub(crate) fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> {
75 if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 {
76 return Err(Error::Type("Incorrectly sized array".into()));
77 }
78
79 let mut proj = [0.; 16];
80 let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect();
81 proj.copy_from_slice(&v);
82 let projection = Transform3D::from_array(proj);
83
84 let transform = get_origin(&view.viewOffset)?.inverse();
86
87 let size = Size2D::new(view.resolution.width, view.resolution.height);
88 let origin = match view.eye {
89 XREye::Right => Point2D::new(size.width, 0),
90 _ => Point2D::zero(),
91 };
92 let viewport = Rect::new(origin, size);
93
94 let fov = view.fieldOfView.as_ref().map(|fov| {
95 (
96 fov.leftDegrees.to_radians(),
97 fov.rightDegrees.to_radians(),
98 fov.upDegrees.to_radians(),
99 fov.downDegrees.to_radians(),
100 )
101 });
102
103 Ok(MockViewInit {
104 projection,
105 transform,
106 viewport,
107 fov,
108 })
109}
110
111pub(crate) fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> {
112 match views.len() {
113 1 => Ok(MockViewsInit::Mono(view(&views[0])?)),
114 2 => {
115 let (left, right) = match (views[0].eye, views[1].eye) {
116 (XREye::Left, XREye::Right) => (&views[0], &views[1]),
117 (XREye::Right, XREye::Left) => (&views[1], &views[0]),
118 _ => return Err(Error::NotSupported(None)),
119 };
120 Ok(MockViewsInit::Stereo(view(left)?, view(right)?))
121 },
122 _ => Err(Error::NotSupported(None)),
123 }
124}
125
126pub(crate) fn get_origin<T, U>(
127 origin: &FakeXRRigidTransformInit,
128) -> Fallible<RigidTransform3D<f32, T, U>> {
129 if origin.position.len() != 3 || origin.orientation.len() != 4 {
130 return Err(Error::Type("Incorrectly sized array".into()));
131 }
132 let p = Vector3D::new(
133 *origin.position[0],
134 *origin.position[1],
135 *origin.position[2],
136 );
137 let o = Rotation3D::unit_quaternion(
138 *origin.orientation[0],
139 *origin.orientation[1],
140 *origin.orientation[2],
141 *origin.orientation[3],
142 );
143
144 Ok(RigidTransform3D::new(o, p))
145}
146
147pub(crate) fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> {
148 Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast()
149}
150
151pub(crate) fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
152 let regions = world
153 .hitTestRegions
154 .iter()
155 .map(|region| {
156 let ty = region.type_.convert();
157 let faces = region
158 .faces
159 .iter()
160 .map(|face| {
161 if face.vertices.len() != 3 {
162 return Err(Error::Type(
163 "Incorrectly sized array for triangle list".into(),
164 ));
165 }
166
167 Ok(Triangle {
168 first: get_point(&face.vertices[0]),
169 second: get_point(&face.vertices[1]),
170 third: get_point(&face.vertices[2]),
171 })
172 })
173 .collect::<Fallible<Vec<_>>>()?;
174 Ok(MockRegion { faces, ty })
175 })
176 .collect::<Fallible<Vec<_>>>()?;
177
178 Ok(MockWorld { regions })
179}
180
181impl Convert<EntityType> for FakeXRRegionType {
182 fn convert(self) -> EntityType {
183 match self {
184 FakeXRRegionType::Point => EntityType::Point,
185 FakeXRRegionType::Plane => EntityType::Plane,
186 FakeXRRegionType::Mesh => EntityType::Mesh,
187 }
188 }
189}
190
191impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
192 fn SetViews(
194 &self,
195 views: Vec<FakeXRViewInit>,
196 _secondary_views: Option<Vec<FakeXRViewInit>>,
197 ) -> Fallible<()> {
198 let _ = self
199 .sender
200 .send(MockDeviceMsg::SetViews(get_views(&views)?));
201 Ok(())
203 }
204
205 fn SetViewerOrigin(
207 &self,
208 origin: &FakeXRRigidTransformInit,
209 _emulated_position: bool,
210 ) -> Fallible<()> {
211 let _ = self
212 .sender
213 .send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?)));
214 Ok(())
215 }
216
217 fn ClearViewerOrigin(&self) {
219 let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None));
220 }
221
222 fn ClearFloorOrigin(&self) {
224 let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None));
225 }
226
227 fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> {
229 let _ = self
230 .sender
231 .send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?)));
232 Ok(())
233 }
234
235 fn ClearWorld(&self) {
237 let _ = self.sender.send(MockDeviceMsg::ClearWorld);
238 }
239
240 fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> {
242 let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?));
243 Ok(())
244 }
245
246 fn SimulateVisibilityChange(&self, v: XRVisibilityState) {
248 let v = match v {
249 XRVisibilityState::Visible => Visibility::Visible,
250 XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred,
251 XRVisibilityState::Hidden => Visibility::Hidden,
252 };
253 let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v));
254 }
255
256 fn SimulateInputSourceConnection(
258 &self,
259 init: &FakeXRInputSourceInit,
260 ) -> Fallible<DomRoot<FakeXRInputController>> {
261 let id = self.next_input_id.get();
262 self.next_input_id.set(InputId(id.0 + 1));
263
264 let handedness = init.handedness.convert();
265 let target_ray_mode = init.targetRayMode.convert();
266
267 let pointer_origin = Some(get_origin(&init.pointerOrigin)?);
268
269 let grip_origin = if let Some(ref g) = init.gripOrigin {
270 Some(get_origin(g)?)
271 } else {
272 None
273 };
274
275 let profiles = init.profiles.iter().cloned().map(String::from).collect();
276
277 let mut supported_buttons = vec![];
278 if let Some(ref buttons) = init.supportedButtons {
279 supported_buttons.extend(init_to_mock_buttons(buttons));
280 }
281
282 let source = InputSource {
283 handedness,
284 target_ray_mode,
285 id,
286 supports_grip: true,
287 profiles,
288 hand_support: None,
289 };
290
291 let init = MockInputInit {
292 source,
293 pointer_origin,
294 grip_origin,
295 supported_buttons,
296 };
297
298 let global = self.global();
299 let _ = self.sender.send(MockDeviceMsg::AddInputSource(init));
300
301 let controller =
302 FakeXRInputController::new(&global, self.sender.clone(), id, CanGc::note());
303
304 Ok(controller)
305 }
306
307 fn Disconnect(&self, can_gc: CanGc) -> Rc<Promise> {
309 let global = self.global();
310 let p = Promise::new(&global, can_gc);
311 let mut trusted = Some(TrustedPromise::new(p.clone()));
312 let task_source = global
313 .task_manager()
314 .dom_manipulation_task_source()
315 .to_sendable();
316
317 let callback =
318 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |_| {
319 let trusted = trusted
320 .take()
321 .expect("disconnect callback called multiple times");
322 task_source.queue(trusted.resolve_task(()));
323 })
324 .expect("Could not create callback");
325 self.disconnect(callback);
326 p
327 }
328
329 fn SetBoundsGeometry(&self, bounds_coodinates: Vec<FakeXRBoundsPoint>) -> Fallible<()> {
331 if bounds_coodinates.len() < 3 {
332 return Err(Error::Type(
333 "Bounds geometry must contain at least 3 points".into(),
334 ));
335 }
336 let coords = bounds_coodinates
337 .iter()
338 .map(|coord| {
339 let x = *coord.x.unwrap() as f32;
340 let y = *coord.z.unwrap() as f32;
341 Point2D::new(x, y)
342 })
343 .collect();
344 let _ = self.sender.send(MockDeviceMsg::SetBoundsGeometry(coords));
345 Ok(())
346 }
347
348 fn SimulateResetPose(&self) {
350 let _ = self.sender.send(MockDeviceMsg::SimulateResetPose);
351 }
352}
353
354impl Convert<Handedness> for XRHandedness {
355 fn convert(self) -> Handedness {
356 match self {
357 XRHandedness::None => Handedness::None,
358 XRHandedness::Left => Handedness::Left,
359 XRHandedness::Right => Handedness::Right,
360 }
361 }
362}
363
364impl Convert<TargetRayMode> for XRTargetRayMode {
365 fn convert(self) -> TargetRayMode {
366 match self {
367 XRTargetRayMode::Gaze => TargetRayMode::Gaze,
368 XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
369 XRTargetRayMode::Screen => TargetRayMode::Screen,
370 XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
371 }
372 }
373}