diff --git a/Cargo.lock b/Cargo.lock index bf024a4c5b3..4aa4e5d7a8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "0.1.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "background_hang_monitor" @@ -971,14 +971,15 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "lazy_static", + "maybe-uninit", "memoffset", "scopeguard", ] @@ -995,9 +996,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg", "cfg-if", @@ -1473,9 +1474,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.20.5" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8813df82772c5ef4c2e9cd4a986773c125ffeafdc08204c9d5c2f06e0abdc17" +checksum = "0c6a5b0c779cd0b744c73a1d2083faf181080d696903cdad99a3b03d015d7030" dependencies = [ "num-traits", "serde", @@ -3846,9 +3847,12 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.4" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] [[package]] name = "num_cpus" @@ -3926,9 +3930,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.53" +version = "0.9.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" +checksum = "7717097d810a0f2e2323f9e5d11e71608355e24828410b55b9d4f18aa5f9a5d8" dependencies = [ "autocfg", "cc", @@ -6270,9 +6274,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.2" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" [[package]] name = "vec_map" @@ -6566,7 +6570,7 @@ dependencies = [ [[package]] name = "webxr" version = "0.0.1" -source = "git+https://github.com/servo/webxr#0d9c83f333920b98d95adf9666b0a365258990a3" +source = "git+https://github.com/servo/webxr#805811544cafbc8dcebc3cd02c38dd329226088d" dependencies = [ "android_injected_glue", "bindgen", @@ -6590,7 +6594,7 @@ dependencies = [ [[package]] name = "webxr-api" version = "0.0.1" -source = "git+https://github.com/servo/webxr#0d9c83f333920b98d95adf9666b0a365258990a3" +source = "git+https://github.com/servo/webxr#805811544cafbc8dcebc3cd02c38dd329226088d" dependencies = [ "euclid", "ipc-channel", diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index cefc930af0a..97fffe52853 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -157,6 +157,7 @@ use webgpu::{ WebGPUPipelineLayout, WebGPUQueue, WebGPUShaderModule, }; use webrender_api::{DocumentId, ImageKey}; +use webxr_api::Ray; use webxr_api::SwapChainId as WebXRSwapChainId; unsafe_no_jsmanaged_fields!(Tm); @@ -552,7 +553,9 @@ unsafe_no_jsmanaged_fields!( webxr_api::Session, webxr_api::Frame, webxr_api::InputSource, - webxr_api::InputId + webxr_api::InputId, + webxr_api::HitTestId, + webxr_api::HitTestResult ); unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); unsafe_no_jsmanaged_fields!(InteractiveMetrics); @@ -750,6 +753,13 @@ unsafe impl JSTraceable for euclid::Rect { } } +unsafe impl JSTraceable for Ray { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + unsafe impl JSTraceable for StyleLocked { unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing. diff --git a/components/script/dom/fakexrdevice.rs b/components/script/dom/fakexrdevice.rs index 537251fffed..5b1ca75fb3d 100644 --- a/components/script/dom/fakexrdevice.rs +++ b/components/script/dom/fakexrdevice.rs @@ -2,8 +2,10 @@ * 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, FakeXRRigidTransformInit, FakeXRViewInit, + FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit, FakeXRViewInit, + FakeXRWorldInit, }; use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit; use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{ @@ -21,15 +23,15 @@ use crate::dom::promise::Promise; use crate::task_source::TaskSource; use dom_struct::dom_struct; use euclid::{Point2D, Rect, Size2D}; -use euclid::{RigidTransform3D, Rotation3D, Transform3D, Vector3D}; +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::{ - Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockViewInit, MockViewsInit, - TargetRayMode, Visibility, + EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion, + MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility, }; #[dom_struct] @@ -97,6 +99,7 @@ pub fn view(view: &FakeXRViewInit) -> Fallible> { fov, }) } + pub fn get_views(views: &[FakeXRViewInit]) -> Fallible { match views.len() { 1 => Ok(MockViewsInit::Mono(view(&views[0])?)), @@ -133,6 +136,50 @@ pub fn get_origin( Ok(RigidTransform3D::new(o, p)) } +pub fn get_point(pt: &DOMPointInit) -> Point3D { + Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast() +} + +pub fn get_world(world: &FakeXRWorldInit) -> Fallible { + 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::>>()?; + Ok(MockRegion { faces, ty }) + }) + .collect::>>()?; + + Ok(MockWorld { regions }) +} + +impl From 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) -> Fallible<()> { @@ -172,6 +219,17 @@ impl FakeXRDeviceMethods for FakeXRDevice { 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 { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 9ae4274a194..a410220b427 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -572,6 +572,8 @@ pub mod xmlhttprequesteventtarget; pub mod xmlhttprequestupload; pub mod xmlserializer; pub mod xrframe; +pub mod xrhittestresult; +pub mod xrhittestsource; pub mod xrinputsource; pub mod xrinputsourcearray; pub mod xrinputsourceevent; @@ -579,6 +581,7 @@ pub mod xrinputsourceschangeevent; pub mod xrlayer; pub mod xrmediabinding; pub mod xrpose; +pub mod xrray; pub mod xrreferencespace; pub mod xrrenderstate; pub mod xrrigidtransform; diff --git a/components/script/dom/webidls/FakeXRDevice.webidl b/components/script/dom/webidls/FakeXRDevice.webidl index 856df51cc7a..6349d70963b 100644 --- a/components/script/dom/webidls/FakeXRDevice.webidl +++ b/components/script/dom/webidls/FakeXRDevice.webidl @@ -25,6 +25,10 @@ interface FakeXRDevice { // behaves as if device was disconnected Promise disconnect(); + + // Hit test extensions: + [Throws] void setWorld(FakeXRWorldInit world); + void clearWorld(); }; // https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport @@ -61,3 +65,26 @@ dictionary FakeXRFieldOfViewInit { required float leftDegrees; required float rightDegrees; }; + +// hit testing +dictionary FakeXRWorldInit { + required sequence hitTestRegions; +}; + + +dictionary FakeXRRegionInit { + required sequence faces; + required FakeXRRegionType type; +}; + + +dictionary FakeXRTriangleInit { + required sequence vertices; // size = 3 +}; + + +enum FakeXRRegionType { + "point", + "plane", + "mesh" +}; diff --git a/components/script/dom/webidls/XRFrame.webidl b/components/script/dom/webidls/XRFrame.webidl index a393acbc588..3c202d5e061 100644 --- a/components/script/dom/webidls/XRFrame.webidl +++ b/components/script/dom/webidls/XRFrame.webidl @@ -10,5 +10,5 @@ interface XRFrame { [Throws] XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace); [Throws] XRPose? getPose(XRSpace space, XRSpace relativeTo); - // XRInputPose? getInputPose(XRInputSource inputSource, optional XRReferenceSpace referenceSpace); + sequence getHitTestResults(XRHitTestSource hitTestSource); }; diff --git a/components/script/dom/webidls/XRHitTestResult.webidl b/components/script/dom/webidls/XRHitTestResult.webidl new file mode 100644 index 00000000000..08bf4bf5389 --- /dev/null +++ b/components/script/dom/webidls/XRHitTestResult.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrhittestresult-interface + +[SecureContext, Exposed=Window] +interface XRHitTestResult { + XRPose? getPose(XRSpace baseSpace); +}; diff --git a/components/script/dom/webidls/XRHitTestSource.webidl b/components/script/dom/webidls/XRHitTestSource.webidl new file mode 100644 index 00000000000..a3a56cebed4 --- /dev/null +++ b/components/script/dom/webidls/XRHitTestSource.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrhittestsource-interface + +enum XRHitTestTrackableType { + "point", + "plane", + "mesh" +}; + +dictionary XRHitTestOptionsInit { + required XRSpace space; + sequence entityTypes; + XRRay offsetRay; +}; + +[SecureContext, Exposed=Window] +interface XRHitTestSource { + void cancel(); +}; diff --git a/components/script/dom/webidls/XRRay.webidl b/components/script/dom/webidls/XRRay.webidl new file mode 100644 index 00000000000..f578436685d --- /dev/null +++ b/components/script/dom/webidls/XRRay.webidl @@ -0,0 +1,21 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrray-interface + +dictionary XRRayDirectionInit { + double x = 0; + double y = 0; + double z = -1; + double w = 0; +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRRay { + [Throws] constructor(optional DOMPointInit origin = {}, optional XRRayDirectionInit direction = {}); + [Throws] constructor(XRRigidTransform transform); + [SameObject] readonly attribute DOMPointReadOnly origin; + [SameObject] readonly attribute DOMPointReadOnly direction; + [SameObject] readonly attribute Float32Array matrix; +}; diff --git a/components/script/dom/webidls/XRSession.webidl b/components/script/dom/webidls/XRSession.webidl index a00e0c4cd3d..45cdbe2339e 100644 --- a/components/script/dom/webidls/XRSession.webidl +++ b/components/script/dom/webidls/XRSession.webidl @@ -36,6 +36,9 @@ interface XRSession : EventTarget { Promise end(); + // hit test module + Promise requestHitTestSource(XRHitTestOptionsInit options); + // // Events attribute EventHandler onend; attribute EventHandler onselect; diff --git a/components/script/dom/webidls/XRTest.webidl b/components/script/dom/webidls/XRTest.webidl index 914bcb2d54d..1ae98f703bf 100644 --- a/components/script/dom/webidls/XRTest.webidl +++ b/components/script/dom/webidls/XRTest.webidl @@ -20,13 +20,14 @@ interface XRTest { }; dictionary FakeXRDeviceInit { - required boolean supportsImmersive; + boolean supportsImmersive = false; + sequence supportedModes; + required sequence views; // this is actually sequence, but we don't support // non-string features anyway sequence supportedFeatures; - boolean supportsUnbounded = false; // Whether the space supports tracking in inline sessions boolean supportsTrackingInInline = true; // The bounds coordinates. If null, bounded reference spaces are not supported. @@ -34,5 +35,8 @@ dictionary FakeXRDeviceInit { // Eye level used for calculating floor-level spaces FakeXRRigidTransformInit floorOrigin; FakeXRRigidTransformInit viewerOrigin; + + // Hit test extensions: + FakeXRWorldInit world; }; diff --git a/components/script/dom/xrframe.rs b/components/script/dom/xrframe.rs index 27a9085bded..fdafd932e8a 100644 --- a/components/script/dom/xrframe.rs +++ b/components/script/dom/xrframe.rs @@ -8,9 +8,11 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::globalscope::GlobalScope; +use crate::dom::xrhittestresult::XRHitTestResult; +use crate::dom::xrhittestsource::XRHitTestSource; use crate::dom::xrpose::XRPose; use crate::dom::xrreferencespace::XRReferenceSpace; -use crate::dom::xrsession::XRSession; +use crate::dom::xrsession::{ApiPose, XRSession}; use crate::dom::xrspace::XRSpace; use crate::dom::xrviewerpose::XRViewerPose; use dom_struct::dom_struct; @@ -51,6 +53,10 @@ impl XRFrame { pub fn set_animation_frame(&self, animation_frame: bool) { self.animation_frame.set(animation_frame); } + + pub fn get_pose(&self, space: &XRSpace) -> Option { + space.get_pose(&self.data) + } } impl XRFrameMethods for XRFrame { @@ -92,12 +98,12 @@ impl XRFrameMethods for XRFrame { if !self.active.get() { return Err(Error::InvalidState); } - let space = if let Some(space) = space.get_pose(&self.data) { + let space = if let Some(space) = self.get_pose(space) { space } else { return Ok(None); }; - let relative_to = if let Some(r) = relative_to.get_pose(&self.data) { + let relative_to = if let Some(r) = self.get_pose(relative_to) { r } else { return Ok(None); @@ -105,4 +111,14 @@ impl XRFrameMethods for XRFrame { let pose = relative_to.inverse().pre_transform(&space); Ok(Some(XRPose::new(&self.global(), pose))) } + + /// https://immersive-web.github.io/hit-test/#dom-xrframe-gethittestresults + fn GetHitTestResults(&self, source: &XRHitTestSource) -> Vec> { + self.data + .hit_test_results + .iter() + .filter(|r| r.id == source.id()) + .map(|r| XRHitTestResult::new(&self.global(), *r, self)) + .collect() + } } diff --git a/components/script/dom/xrhittestresult.rs b/components/script/dom/xrhittestresult.rs new file mode 100644 index 00000000000..be11f3dc7b6 --- /dev/null +++ b/components/script/dom/xrhittestresult.rs @@ -0,0 +1,51 @@ +/* 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::XRHitTestResultBinding::XRHitTestResultMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrpose::XRPose; +use crate::dom::xrspace::XRSpace; +use dom_struct::dom_struct; +use webxr_api::HitTestResult; + +#[dom_struct] +pub struct XRHitTestResult { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + result: HitTestResult, + frame: Dom, +} + +impl XRHitTestResult { + fn new_inherited(result: HitTestResult, frame: &XRFrame) -> XRHitTestResult { + XRHitTestResult { + reflector_: Reflector::new(), + result, + frame: Dom::from_ref(frame), + } + } + + pub fn new( + global: &GlobalScope, + result: HitTestResult, + frame: &XRFrame, + ) -> DomRoot { + reflect_dom_object( + Box::new(XRHitTestResult::new_inherited(result, frame)), + global, + ) + } +} + +impl XRHitTestResultMethods for XRHitTestResult { + // https://immersive-web.github.io/hit-test/#dom-xrhittestresult-getpose + fn GetPose(&self, base: &XRSpace) -> Option> { + let base = self.frame.get_pose(base)?; + let pose = base.inverse().pre_transform(&self.result.space); + Some(XRPose::new(&self.global(), pose.cast_unit())) + } +} diff --git a/components/script/dom/xrhittestsource.rs b/components/script/dom/xrhittestsource.rs new file mode 100644 index 00000000000..a665a9f1b71 --- /dev/null +++ b/components/script/dom/xrhittestsource.rs @@ -0,0 +1,51 @@ +/* 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::XRHitTestSourceBinding::XRHitTestSourceMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrsession::XRSession; +use dom_struct::dom_struct; +use webxr_api::HitTestId; + +#[dom_struct] +pub struct XRHitTestSource { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + id: HitTestId, + session: Dom, +} + +impl XRHitTestSource { + fn new_inherited(id: HitTestId, session: &XRSession) -> XRHitTestSource { + XRHitTestSource { + reflector_: Reflector::new(), + id, + session: Dom::from_ref(session), + } + } + + pub fn new( + global: &GlobalScope, + id: HitTestId, + session: &XRSession, + ) -> DomRoot { + reflect_dom_object( + Box::new(XRHitTestSource::new_inherited(id, session)), + global, + ) + } + + pub fn id(&self) -> HitTestId { + self.id + } +} + +impl XRHitTestSourceMethods for XRHitTestSource { + // https://immersive-web.github.io/hit-test/#dom-xrhittestsource-cancel + fn Cancel(&self) { + self.session.with_session(|s| s.cancel_hit_test(self.id)); + } +} diff --git a/components/script/dom/xrray.rs b/components/script/dom/xrray.rs new file mode 100644 index 00000000000..37262732c3a --- /dev/null +++ b/components/script/dom/xrray.rs @@ -0,0 +1,147 @@ +/* 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::XRRayBinding::{XRRayDirectionInit, XRRayMethods}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::utils::create_typed_array; +use crate::dom::dompointreadonly::DOMPointReadOnly; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use euclid::{Angle, RigidTransform3D, Rotation3D, Vector3D}; +use js::jsapi::{Heap, JSObject}; +use std::ptr::NonNull; +use webxr_api::{ApiSpace, Ray}; + +#[dom_struct] +pub struct XRRay { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + ray: Ray, + #[ignore_malloc_size_of = "defined in mozjs"] + matrix: Heap<*mut JSObject>, +} + +impl XRRay { + fn new_inherited(ray: Ray) -> XRRay { + XRRay { + reflector_: Reflector::new(), + ray, + matrix: Heap::default(), + } + } + + pub fn new(global: &GlobalScope, ray: Ray) -> DomRoot { + reflect_dom_object(Box::new(XRRay::new_inherited(ray)), global) + } + + #[allow(non_snake_case)] + /// https://immersive-web.github.io/hit-test/#dom-xrray-xrray + pub fn Constructor( + window: &Window, + origin: &DOMPointInit, + direction: &XRRayDirectionInit, + ) -> Fallible> { + if origin.w != 1.0 { + return Err(Error::Type("Origin w coordinate must be 1".into())); + } + if *direction.w != 0.0 { + return Err(Error::Type("Direction w coordinate must be 0".into())); + } + if *direction.x == 0.0 && *direction.y == 0.0 && *direction.z == 0.0 { + return Err(Error::Type( + "Direction vector cannot have zero length".into(), + )); + } + + let origin = Vector3D::new(origin.x as f32, origin.y as f32, origin.z as f32); + let direction = Vector3D::new( + *direction.x as f32, + *direction.y as f32, + *direction.z as f32, + ) + .normalize(); + + Ok(Self::new(&window.global(), Ray { origin, direction })) + } + + #[allow(non_snake_case)] + /// https://immersive-web.github.io/hit-test/#dom-xrray-xrray-transform + pub fn Constructor_(window: &Window, transform: &XRRigidTransform) -> Fallible> { + let transform = transform.transform(); + let origin = transform.translation; + let direction = transform + .rotation + .transform_vector3d(Vector3D::new(0., 0., -1.)); + + Ok(Self::new(&window.global(), Ray { origin, direction })) + } + + pub fn ray(&self) -> Ray { + self.ray + } +} + +impl XRRayMethods for XRRay { + /// https://immersive-web.github.io/hit-test/#dom-xrray-origin + fn Origin(&self) -> DomRoot { + DOMPointReadOnly::new( + &self.global(), + self.ray.origin.x as f64, + self.ray.origin.y as f64, + self.ray.origin.z as f64, + 1., + ) + } + + /// https://immersive-web.github.io/hit-test/#dom-xrray-direction + fn Direction(&self) -> DomRoot { + DOMPointReadOnly::new( + &self.global(), + self.ray.direction.x as f64, + self.ray.direction.y as f64, + self.ray.direction.z as f64, + 0., + ) + } + + /// https://immersive-web.github.io/hit-test/#dom-xrray-matrix + fn Matrix(&self, _cx: JSContext) -> NonNull { + // https://immersive-web.github.io/hit-test/#xrray-obtain-the-matrix + // Step 1 + if self.matrix.get().is_null() { + let cx = self.global().get_cx(); + // Step 2 + let z = Vector3D::new(0., 0., -1.); + // Step 3 + let axis = z.cross(self.ray.direction); + // Step 4 + let cos_angle = z.dot(self.ray.direction); + // Step 5 + let rotation = if cos_angle > -1. && cos_angle < 1. { + Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos())) + } else if cos_angle == -1. { + let axis = Vector3D::new(1., 0., 0.); + Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos())) + } else { + Rotation3D::identity() + }; + // Step 6 + let translation = self.ray.origin; + // Step 7 + // According to the spec all matrices are column-major, + // however euclid uses row vectors so we use .to_row_major_array() + let arr = RigidTransform3D::new(rotation, translation) + .to_transform() + .to_row_major_array(); + create_typed_array(cx, &arr, &self.matrix); + } + NonNull::new(self.matrix.get()).unwrap() + } +} diff --git a/components/script/dom/xrreferencespace.rs b/components/script/dom/xrreferencespace.rs index 4565b7681f0..3d05a59564c 100644 --- a/components/script/dom/xrreferencespace.rs +++ b/components/script/dom/xrreferencespace.rs @@ -13,7 +13,7 @@ use crate::dom::xrsession::{cast_transform, ApiPose, ApiViewerPose, XRSession}; use crate::dom::xrspace::XRSpace; use dom_struct::dom_struct; use euclid::RigidTransform3D; -use webxr_api::Frame; +use webxr_api::{BaseSpace, Frame, Space}; #[dom_struct] pub struct XRReferenceSpace { @@ -57,6 +57,17 @@ impl XRReferenceSpace { global, ) } + + pub fn space(&self) -> Space { + let base = match self.ty { + XRReferenceSpaceType::Local => BaseSpace::Local, + XRReferenceSpaceType::Viewer => BaseSpace::Viewer, + XRReferenceSpaceType::Local_floor => BaseSpace::Floor, + _ => panic!("unsupported reference space found"), + }; + let offset = self.offset.transform(); + Space { base, offset } + } } impl XRReferenceSpaceMethods for XRReferenceSpace { diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs index dc2b5b3c497..2ef47361f92 100644 --- a/components/script/dom/xrsession.rs +++ b/components/script/dom/xrsession.rs @@ -6,6 +6,8 @@ use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestOptionsInit; +use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestTrackableType; use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType; use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateInit; use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods; @@ -28,6 +30,7 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::performance::reduce_timing_resolution; use crate::dom::promise::Promise; use crate::dom::xrframe::XRFrame; +use crate::dom::xrhittestsource::XRHitTestSource; use crate::dom::xrinputsourcearray::XRInputSourceArray; use crate::dom::xrinputsourceevent::XRInputSourceEvent; use crate::dom::xrreferencespace::XRReferenceSpace; @@ -37,18 +40,20 @@ use crate::dom::xrspace::XRSpace; use crate::realms::InRealm; use crate::task_source::TaskSource; use dom_struct::dom_struct; -use euclid::{Rect, RigidTransform3D, Transform3D}; +use euclid::{Rect, RigidTransform3D, Transform3D, Vector3D}; use ipc_channel::ipc::IpcReceiver; use ipc_channel::router::ROUTER; use metrics::ToMs; use profile_traits::ipc; use std::cell::Cell; +use std::collections::HashMap; use std::f64::consts::{FRAC_PI_2, PI}; use std::mem; use std::rc::Rc; use webxr_api::{ - self, util, Display, EnvironmentBlendMode, Event as XREvent, Frame, SelectEvent, SelectKind, - Session, SessionId, View, Viewer, Visibility, + self, util, ApiSpace, Display, EntityTypes, EnvironmentBlendMode, Event as XREvent, Frame, + FrameUpdateEvent, HitTestId, HitTestSource, Ray, SelectEvent, SelectKind, Session, SessionId, + View, Viewer, Visibility, }; #[dom_struct] @@ -75,6 +80,10 @@ pub struct XRSession { end_promises: DomRefCell>>, /// https://immersive-web.github.io/webxr/#ended ended: Cell, + #[ignore_malloc_size_of = "defined in webxr"] + next_hit_test_id: Cell, + #[ignore_malloc_size_of = "defined in webxr"] + pending_hit_test_promises: DomRefCell>>, /// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame" /// https://immersive-web.github.io/webxr/#opaque-framebuffer outside_raf: Cell, @@ -104,6 +113,8 @@ impl XRSession { input_sources: Dom::from_ref(input_sources), end_promises: DomRefCell::new(vec![]), ended: Cell::new(false), + next_hit_test_id: Cell::new(HitTestId(0)), + pending_hit_test_promises: DomRefCell::new(HashMap::new()), outside_raf: Cell::new(true), } } @@ -373,7 +384,7 @@ impl XRSession { } for event in frame.events.drain(..) { - self.session.borrow_mut().apply_event(event) + self.handle_frame_update(event); } // Step 2 @@ -480,6 +491,22 @@ impl XRSession { } } } + + fn handle_frame_update(&self, event: FrameUpdateEvent) { + match event { + FrameUpdateEvent::HitTestSourceAdded(id) => { + if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) { + promise.resolve_native(&XRHitTestSource::new(&self.global(), id, &self)); + } else { + warn!( + "received hit test add request for unknown hit test {:?}", + id + ) + } + }, + _ => self.session.borrow_mut().apply_event(event), + } + } } impl XRSessionMethods for XRSession { @@ -709,10 +736,66 @@ impl XRSessionMethods for XRSession { self.session.borrow_mut().end_session(); p } + + // https://immersive-web.github.io/hit-test/#dom-xrsession-requesthittestsource + fn RequestHitTestSource(&self, options: &XRHitTestOptionsInit) -> Rc { + let p = Promise::new(&self.global()); + + if self + .session + .borrow() + .granted_features() + .iter() + .find(|f| &**f == "hit-test") + .is_none() + { + p.reject_error(Error::NotSupported); + return p; + } + + let id = self.next_hit_test_id.get(); + self.next_hit_test_id.set(HitTestId(id.0 + 1)); + + let space = options.space.space(); + let ray = if let Some(ref ray) = options.offsetRay { + ray.ray() + } else { + Ray { + origin: Vector3D::new(0., 0., 0.), + direction: Vector3D::new(0., 0., -1.), + } + }; + + let mut types = EntityTypes::default(); + + if let Some(ref tys) = options.entityTypes { + for ty in tys { + match ty { + XRHitTestTrackableType::Point => types.point = true, + XRHitTestTrackableType::Plane => types.plane = true, + XRHitTestTrackableType::Mesh => types.mesh = true, + } + } + } else { + types.plane = true; + } + + let source = HitTestSource { + id, + space, + ray, + types, + }; + self.pending_hit_test_promises + .borrow_mut() + .insert(id, p.clone()); + + self.session.borrow().request_hit_test(source); + + p + } } -#[derive(Clone, Copy, Debug)] -pub struct ApiSpace; // The pose of an object in native-space. Should never be exposed. pub type ApiPose = RigidTransform3D; // The pose of the viewer in some api-space. diff --git a/components/script/dom/xrspace.rs b/components/script/dom/xrspace.rs index 80752be3e35..b4a26b7b8bb 100644 --- a/components/script/dom/xrspace.rs +++ b/components/script/dom/xrspace.rs @@ -11,7 +11,8 @@ use crate::dom::xrinputsource::XRInputSource; use crate::dom::xrreferencespace::XRReferenceSpace; use crate::dom::xrsession::{cast_transform, ApiPose, XRSession}; use dom_struct::dom_struct; -use webxr_api::Frame; +use euclid::RigidTransform3D; +use webxr_api::{BaseSpace, Frame, Space}; #[dom_struct] pub struct XRSpace { @@ -56,6 +57,24 @@ impl XRSpace { global, ) } + + pub fn space(&self) -> Space { + if let Some(rs) = self.downcast::() { + rs.space() + } else if let Some(source) = self.input_source.get() { + let base = if self.is_grip_space { + BaseSpace::Grip(source.id()) + } else { + BaseSpace::TargetRay(source.id()) + }; + Space { + base, + offset: RigidTransform3D::identity(), + } + } else { + panic!("invalid space found") + } + } } impl XRSpace { diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs index 32adb628c11..37707c3ce8e 100644 --- a/components/script/dom/xrtest.rs +++ b/components/script/dom/xrtest.rs @@ -9,11 +9,12 @@ use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; +use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode; use crate::dom::bindings::codegen::Bindings::XRTestBinding::{FakeXRDeviceInit, XRTestMethods}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; -use crate::dom::fakexrdevice::{get_origin, get_views, FakeXRDevice}; +use crate::dom::fakexrdevice::{get_origin, get_views, get_world, FakeXRDevice}; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::script_thread::ScriptThread; @@ -106,13 +107,40 @@ impl XRTestMethods for XRTest { vec![] }; + let world = if let Some(ref w) = init.world { + let w = match get_world(w) { + Ok(w) => w, + Err(e) => { + p.reject_error(e); + return p; + }, + }; + Some(w) + } else { + None + }; + + let (mut supports_inline, mut supports_vr, mut supports_ar) = (false, false, false); + + if let Some(ref modes) = init.supportedModes { + for mode in modes { + match mode { + XRSessionMode::Immersive_vr => supports_vr = true, + XRSessionMode::Immersive_ar => supports_ar = true, + XRSessionMode::Inline => supports_inline = true, + } + } + } + let init = MockDeviceInit { viewer_origin: origin, views, - supports_immersive: init.supportsImmersive, - supports_unbounded: init.supportsUnbounded, + supports_inline, + supports_vr, + supports_ar, floor_origin, supported_features, + world, }; let global = self.global(); diff --git a/tests/wpt/metadata/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html.ini b/tests/wpt/metadata/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html.ini deleted file mode 100644 index 178edb6ed13..00000000000 --- a/tests/wpt/metadata/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[xrDevice_isSessionSupported_immersive-ar.https.html] - [isSessionSupported resolves to false for immersive-ar on an unsupported device] - expected: FAIL - diff --git a/tests/wpt/metadata/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html.ini b/tests/wpt/metadata/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html.ini deleted file mode 100644 index 956d3ed136a..00000000000 --- a/tests/wpt/metadata/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[xrDevice_requestSession_immersive-ar.https.html] - [Tests requestSession rejects immersive-ar mode when unsupported] - expected: FAIL - diff --git a/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay.https.html.ini b/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay.https.html.ini index 224197c5a20..5e168fd4be5 100644 --- a/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay.https.html.ini +++ b/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay.https.html.ini @@ -1,4 +1,5 @@ [ar_dom_overlay.https.html] + expected: ERROR [Ensures DOM Overlay element selection works] expected: FAIL @@ -6,7 +7,7 @@ expected: FAIL [Ensures DOM Overlay input deduplication works] - expected: FAIL + expected: TIMEOUT [Ensures DOM Overlay feature works for immersive-ar, body element] expected: FAIL @@ -15,11 +16,11 @@ expected: FAIL [Ensures DOM Overlay Fullscreen API doesn't change DOM overlay] - expected: FAIL + expected: NOTRUN [Ensures DOM Overlay feature works for immersive-ar, div element] expected: FAIL [Ensures DOM Overlay interactions on cross origin iframe are ignored] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html.ini b/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html.ini index 39d3a1c94ce..26ed26f69c2 100644 --- a/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html.ini +++ b/tests/wpt/metadata/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html.ini @@ -1,2 +1,4 @@ [ar_dom_overlay_hit_test.https.html] - expected: ERROR + [Ensures DOM Overlay interactions on cross origin iframe do not cause hit test results to come up] + expected: FAIL + diff --git a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_inputSources.https.html.ini b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_inputSources.https.html.ini index 670b46927bc..23f35a787af 100644 --- a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_inputSources.https.html.ini +++ b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_inputSources.https.html.ini @@ -1,2 +1,10 @@ [ar_hittest_subscription_inputSources.https.html] - expected: ERROR + [Ensures subscription to hit test works with an XRSpace from input source - after move - no results] + expected: FAIL + + [Ensures subscription to hit test works with an XRSpace from input source - after move - 1 result] + expected: FAIL + + [Ensures subscription to hit test works with an XRSpace from input source - no move] + expected: FAIL + diff --git a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html.ini b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html.ini index acc47d4ee07..eca12b3d24c 100644 --- a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html.ini +++ b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html.ini @@ -1,2 +1,10 @@ [ar_hittest_subscription_refSpaces.https.html] - expected: ERROR + [Ensures subscription to hit test works with viewer space - straight ahead - plane] + expected: FAIL + + [Ensures subscription to hit test works with local space] + expected: FAIL + + [Ensures subscription to hit test works with local-floor space] + expected: FAIL + diff --git a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_states_regular.https.html.ini b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_states_regular.https.html.ini deleted file mode 100644 index c82ddf9b4af..00000000000 --- a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_states_regular.https.html.ini +++ /dev/null @@ -1,10 +0,0 @@ -[ar_hittest_subscription_states_regular.https.html] - [Hit test subscription succeeds if the feature was requested] - expected: FAIL - - [Hit test subscription fails if the feature was not requested] - expected: FAIL - - [Hit test subscription fails if the feature was requested but the session already ended] - expected: FAIL - diff --git a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html.ini b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html.ini index e33e7dce5f2..95708a29336 100644 --- a/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html.ini +++ b/tests/wpt/metadata/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html.ini @@ -1,2 +1,11 @@ [ar_hittest_subscription_transientInputSources.https.html] expected: ERROR + [Ensures subscription to transient hit test works with an XRSpace from input source - after move - 1 result] + expected: NOTRUN + + [Ensures subscription to transient hit test works with an XRSpace from input source - after move - no results] + expected: NOTRUN + + [Ensures subscription to transient hit test works with an XRSpace from input source - no move] + expected: TIMEOUT + diff --git a/tests/wpt/metadata/webxr/hit-test/xrRay_constructor.https.html.ini b/tests/wpt/metadata/webxr/hit-test/xrRay_constructor.https.html.ini deleted file mode 100644 index 5381acd7bbf..00000000000 --- a/tests/wpt/metadata/webxr/hit-test/xrRay_constructor.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[xrRay_constructor.https.html] - [XRRay constructors work] - expected: FAIL - diff --git a/tests/wpt/metadata/webxr/hit-test/xrRay_matrix.https.html.ini b/tests/wpt/metadata/webxr/hit-test/xrRay_matrix.https.html.ini deleted file mode 100644 index fd045eaa394..00000000000 --- a/tests/wpt/metadata/webxr/hit-test/xrRay_matrix.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[xrRay_matrix.https.html] - [XRRay matrix works] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 43a57321277..c4e49f39a76 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13870,7 +13870,7 @@ ] ], "interfaces.html": [ - "dc9a1f5f378bc50487bfb7dc3db6d121d2f325ca", + "1a579837cc22d31a7792566615d9e321b3d7fe39", [ null, {} @@ -14609,21 +14609,21 @@ }, "webxr": { "create_session.html": [ - "af76c5a812d7d05a0158194560933def3fbdb9f9", + "5b5d485b372bfffb22204bc162c9e182306395cb", [ null, {} ] ], "layers.html": [ - "31f4b6bd51cfcca47666331857bd2bbdf84d2f5e", + "49821d7661f92bc9cf22232d3fcb391c2cdc7295", [ null, {} ] ], "obtain_frame.html": [ - "74fda5bad43e8ea95552e65380e83952680e8469", + "d9ff25729f9cdfd348e7c9914ce2dacd231e13a0", [ null, {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index dc9a1f5f378..1a579837cc2 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -266,11 +266,14 @@ test_interfaces([ "XMLHttpRequestUpload", "XMLSerializer", "XRFrame", + "XRHitTestResult", + "XRHitTestSource", "XRInputSource", "XRInputSourceArray", "XRInputSourceEvent", "XRPose", "XRReferenceSpace", + "XRRay", "XRRenderState", "XRRigidTransform", "XRSession", diff --git a/tests/wpt/mozilla/tests/webxr/create_session.html b/tests/wpt/mozilla/tests/webxr/create_session.html index af76c5a812d..5b5d485b372 100644 --- a/tests/wpt/mozilla/tests/webxr/create_session.html +++ b/tests/wpt/mozilla/tests/webxr/create_session.html @@ -8,7 +8,7 @@