diff --git a/components/script/dom/webidls/XRHitTestSource.webidl b/components/script/dom/webidls/XRHitTestSource.webidl index 92faf352686..a3a56cebed4 100644 --- a/components/script/dom/webidls/XRHitTestSource.webidl +++ b/components/script/dom/webidls/XRHitTestSource.webidl @@ -4,6 +4,17 @@ // 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 { 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/xrray.rs b/components/script/dom/xrray.rs index 6f50820c6f0..37262732c3a 100644 --- a/components/script/dom/xrray.rs +++ b/components/script/dom/xrray.rs @@ -82,6 +82,10 @@ impl XRRay { Ok(Self::new(&window.global(), Ray { origin, direction })) } + + pub fn ray(&self) -> Ray { + self.ray + } } impl XRRayMethods for XRRay { 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 093c15e8dd6..6bde94c31a0 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, ApiSpace, 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,6 +736,52 @@ 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()); + + 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 + } } // The pose of an object in native-space. Should never be exposed. 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/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 694dd586a5d..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,7 +1,4 @@ [ar_hittest_subscription_refSpaces.https.html] - [Ensures subscription to hit test works with viewer space - straight up - no results] - expected: FAIL - [Ensures subscription to hit test works with viewer space - straight ahead - plane] 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 77ee3e16b32..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,10 +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: FAIL + expected: NOTRUN [Ensures subscription to transient hit test works with an XRSpace from input source - after move - no results] - expected: FAIL + expected: NOTRUN [Ensures subscription to transient hit test works with an XRSpace from input source - no move] - expected: FAIL + expected: TIMEOUT