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