diff options
Diffstat (limited to 'components/script/dom/fakexrdevice.rs')
-rw-r--r-- | components/script/dom/fakexrdevice.rs | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/components/script/dom/fakexrdevice.rs b/components/script/dom/fakexrdevice.rs new file mode 100644 index 00000000000..811a7e01b42 --- /dev/null +++ b/components/script/dom/fakexrdevice.rs @@ -0,0 +1,331 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{ + FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit, FakeXRViewInit, + FakeXRWorldInit, +}; +use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit; +use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{ + XRHandedness, XRTargetRayMode, +}; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState; +use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::refcounted::TrustedPromise; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::fakexrinputcontroller::FakeXRInputController; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use euclid::{Point2D, Rect, Size2D}; +use euclid::{Point3D, RigidTransform3D, Rotation3D, Transform3D, Vector3D}; +use ipc_channel::ipc::IpcSender; +use ipc_channel::router::ROUTER; +use profile_traits::ipc; +use std::cell::Cell; +use std::rc::Rc; +use webxr_api::{ + EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion, + MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility, +}; + +#[dom_struct] +pub struct FakeXRDevice { + reflector: Reflector, + #[ignore_malloc_size_of = "defined in ipc-channel"] + sender: IpcSender<MockDeviceMsg>, + #[ignore_malloc_size_of = "defined in webxr-api"] + next_input_id: Cell<InputId>, +} + +impl FakeXRDevice { + pub fn new_inherited(sender: IpcSender<MockDeviceMsg>) -> FakeXRDevice { + FakeXRDevice { + reflector: Reflector::new(), + sender, + next_input_id: Cell::new(InputId(0)), + } + } + + pub fn new(global: &GlobalScope, sender: IpcSender<MockDeviceMsg>) -> DomRoot<FakeXRDevice> { + reflect_dom_object(Box::new(FakeXRDevice::new_inherited(sender)), global) + } + + pub fn disconnect(&self, sender: IpcSender<()>) { + let _ = self.sender.send(MockDeviceMsg::Disconnect(sender)); + } +} + +pub fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> { + if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 { + return Err(Error::Type("Incorrectly sized array".into())); + } + + let mut proj = [0.; 16]; + let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect(); + proj.copy_from_slice(&v); + let projection = Transform3D::from_array(proj); + + // spec defines offsets as origins, but mock API expects the inverse transform + let transform = get_origin(&view.viewOffset)?.inverse(); + + let size = Size2D::new(view.resolution.width, view.resolution.height); + let origin = match view.eye { + XREye::Right => Point2D::new(size.width, 0), + _ => Point2D::new(0, 0), + }; + let viewport = Rect::new(origin, size); + + let fov = if let Some(ref fov) = view.fieldOfView { + Some(( + fov.leftDegrees.to_radians(), + fov.rightDegrees.to_radians(), + fov.upDegrees.to_radians(), + fov.downDegrees.to_radians(), + )) + } else { + None + }; + + Ok(MockViewInit { + projection, + transform, + viewport, + fov, + }) +} + +pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> { + match views.len() { + 1 => Ok(MockViewsInit::Mono(view(&views[0])?)), + 2 => { + let (left, right) = match (views[0].eye, views[1].eye) { + (XREye::Left, XREye::Right) => (&views[0], &views[1]), + (XREye::Right, XREye::Left) => (&views[1], &views[0]), + _ => return Err(Error::NotSupported), + }; + Ok(MockViewsInit::Stereo(view(left)?, view(right)?)) + }, + _ => Err(Error::NotSupported), + } +} + +pub fn get_origin<T, U>( + origin: &FakeXRRigidTransformInit, +) -> Fallible<RigidTransform3D<f32, T, U>> { + if origin.position.len() != 3 || origin.orientation.len() != 4 { + return Err(Error::Type("Incorrectly sized array".into())); + } + let p = Vector3D::new( + *origin.position[0], + *origin.position[1], + *origin.position[2], + ); + let o = Rotation3D::unit_quaternion( + *origin.orientation[0], + *origin.orientation[1], + *origin.orientation[2], + *origin.orientation[3], + ); + + Ok(RigidTransform3D::new(o, p)) +} + +pub fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> { + Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast() +} + +pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> { + let regions = world + .hitTestRegions + .iter() + .map(|region| { + let ty = region.type_.into(); + let faces = region + .faces + .iter() + .map(|face| { + if face.vertices.len() != 3 { + return Err(Error::Type( + "Incorrectly sized array for triangle list".into(), + )); + } + + Ok(Triangle { + first: get_point(&face.vertices[0]), + second: get_point(&face.vertices[1]), + third: get_point(&face.vertices[2]), + }) + }) + .collect::<Fallible<Vec<_>>>()?; + Ok(MockRegion { faces, ty }) + }) + .collect::<Fallible<Vec<_>>>()?; + + Ok(MockWorld { regions }) +} + +impl From<FakeXRRegionType> for EntityType { + fn from(x: FakeXRRegionType) -> Self { + match x { + FakeXRRegionType::Point => EntityType::Point, + FakeXRRegionType::Plane => EntityType::Plane, + FakeXRRegionType::Mesh => EntityType::Mesh, + } + } +} + +impl FakeXRDeviceMethods for FakeXRDevice { + /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + fn SetViews(&self, views: Vec<FakeXRViewInit>) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetViews(get_views(&views)?)); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setviewerorigin + fn SetViewerOrigin( + &self, + origin: &FakeXRRigidTransformInit, + _emulated_position: bool, + ) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearviewerorigin + fn ClearViewerOrigin(&self) { + let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearfloororigin + fn ClearFloorOrigin(&self) { + let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setfloororigin + fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearworld + fn ClearWorld(&self) { + let _ = self.sender.send(MockDeviceMsg::ClearWorld); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setworld + fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> { + let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?)); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulatevisibilitychange + fn SimulateVisibilityChange(&self, v: XRVisibilityState) { + let v = match v { + XRVisibilityState::Visible => Visibility::Visible, + XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred, + XRVisibilityState::Hidden => Visibility::Hidden, + }; + let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateinputsourceconnection + fn SimulateInputSourceConnection( + &self, + init: &FakeXRInputSourceInit, + ) -> Fallible<DomRoot<FakeXRInputController>> { + let id = self.next_input_id.get(); + self.next_input_id.set(InputId(id.0 + 1)); + + let handedness = init.handedness.into(); + let target_ray_mode = init.targetRayMode.into(); + + let pointer_origin = Some(get_origin(&init.pointerOrigin)?); + + let grip_origin = if let Some(ref g) = init.gripOrigin { + Some(get_origin(g)?) + } else { + None + }; + + let profiles = init.profiles.iter().cloned().map(String::from).collect(); + + // XXXManishearth deal with supportedButtons and selection* + + let source = InputSource { + handedness, + target_ray_mode, + id, + supports_grip: true, + profiles, + hand_support: None, + }; + + let init = MockInputInit { + source, + pointer_origin, + grip_origin, + }; + + let global = self.global(); + let _ = self.sender.send(MockDeviceMsg::AddInputSource(init)); + + let controller = FakeXRInputController::new(&global, self.sender.clone(), id); + + Ok(controller) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-disconnect + fn Disconnect(&self) -> Rc<Promise> { + let global = self.global(); + let p = Promise::new(&global); + let mut trusted = Some(TrustedPromise::new(p.clone())); + let (task_source, canceller) = global + .as_window() + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |_| { + let trusted = trusted + .take() + .expect("disconnect callback called multiple times"); + let _ = task_source.queue_with_canceller(trusted.resolve_task(()), &canceller); + }), + ); + self.disconnect(sender); + p + } +} + +impl From<XRHandedness> for Handedness { + fn from(h: XRHandedness) -> Self { + match h { + XRHandedness::None => Handedness::None, + XRHandedness::Left => Handedness::Left, + XRHandedness::Right => Handedness::Right, + } + } +} + +impl From<XRTargetRayMode> for TargetRayMode { + fn from(t: XRTargetRayMode) -> Self { + match t { + XRTargetRayMode::Gaze => TargetRayMode::Gaze, + XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer, + XRTargetRayMode::Screen => TargetRayMode::Screen, + } + } +} |