Auto merge of #26171 - Manishearth:hittest, r=asajeffrey

Implement hit testing API

Depends on https://github.com/servo/webxr/pull/149 , https://github.com/servo/servo/pull/26170

This implements non-transient hit tests.

The tests that do not pass are due to https://github.com/web-platform-tests/wpt/issues/22898 , https://github.com/web-platform-tests/wpt/issues/22900, https://github.com/web-platform-tests/wpt/issues/22901 , and https://github.com/immersive-web/hit-test/issues/86
This commit is contained in:
bors-servo 2020-04-20 00:30:46 -04:00 committed by GitHub
commit 99cd30eaad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 650 additions and 77 deletions

36
Cargo.lock generated
View file

@ -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",

View file

@ -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<U> JSTraceable for euclid::Rect<i32, U> {
}
}
unsafe impl<Space> JSTraceable for Ray<Space> {
#[inline]
unsafe fn trace(&self, _trc: *mut JSTracer) {
// Do nothing
}
}
unsafe impl JSTraceable for StyleLocked<FontFaceRule> {
unsafe fn trace(&self, _trc: *mut JSTracer) {
// Do nothing.

View file

@ -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<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> {
fov,
})
}
pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> {
match views.len() {
1 => Ok(MockViewsInit::Mono(view(&views[0])?)),
@ -133,6 +136,50 @@ pub fn get_origin<T, U>(
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<()> {
@ -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 {

View file

@ -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;

View file

@ -25,6 +25,10 @@ interface FakeXRDevice {
// behaves as if device was disconnected
Promise<void> 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<FakeXRRegionInit> hitTestRegions;
};
dictionary FakeXRRegionInit {
required sequence<FakeXRTriangleInit> faces;
required FakeXRRegionType type;
};
dictionary FakeXRTriangleInit {
required sequence<DOMPointInit> vertices; // size = 3
};
enum FakeXRRegionType {
"point",
"plane",
"mesh"
};

View file

@ -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<XRHitTestResult> getHitTestResults(XRHitTestSource hitTestSource);
};

View file

@ -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);
};

View file

@ -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<XRHitTestTrackableType> entityTypes;
XRRay offsetRay;
};
[SecureContext, Exposed=Window]
interface XRHitTestSource {
void cancel();
};

View file

@ -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;
};

View file

@ -36,6 +36,9 @@ interface XRSession : EventTarget {
Promise<void> end();
// hit test module
Promise<XRHitTestSource> requestHitTestSource(XRHitTestOptionsInit options);
// // Events
attribute EventHandler onend;
attribute EventHandler onselect;

View file

@ -20,13 +20,14 @@ interface XRTest {
};
dictionary FakeXRDeviceInit {
required boolean supportsImmersive;
boolean supportsImmersive = false;
sequence<XRSessionMode> supportedModes;
required sequence<FakeXRViewInit> views;
// this is actually sequence<any>, but we don't support
// non-string features anyway
sequence<DOMString> 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;
};

View file

@ -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<ApiPose> {
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<DomRoot<XRHitTestResult>> {
self.data
.hit_test_results
.iter()
.filter(|r| r.id == source.id())
.map(|r| XRHitTestResult::new(&self.global(), *r, self))
.collect()
}
}

View file

@ -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<XRFrame>,
}
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<XRHitTestResult> {
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<DomRoot<XRPose>> {
let base = self.frame.get_pose(base)?;
let pose = base.inverse().pre_transform(&self.result.space);
Some(XRPose::new(&self.global(), pose.cast_unit()))
}
}

View file

@ -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<XRSession>,
}
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<XRHitTestSource> {
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));
}
}

View file

@ -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<ApiSpace>,
#[ignore_malloc_size_of = "defined in mozjs"]
matrix: Heap<*mut JSObject>,
}
impl XRRay {
fn new_inherited(ray: Ray<ApiSpace>) -> XRRay {
XRRay {
reflector_: Reflector::new(),
ray,
matrix: Heap::default(),
}
}
pub fn new(global: &GlobalScope, ray: Ray<ApiSpace>) -> DomRoot<XRRay> {
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<DomRoot<Self>> {
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<DomRoot<Self>> {
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<ApiSpace> {
self.ray
}
}
impl XRRayMethods for XRRay {
/// https://immersive-web.github.io/hit-test/#dom-xrray-origin
fn Origin(&self) -> DomRoot<DOMPointReadOnly> {
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> {
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<JSObject> {
// 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()
}
}

View file

@ -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 {

View file

@ -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<Vec<Rc<Promise>>>,
/// https://immersive-web.github.io/webxr/#ended
ended: Cell<bool>,
#[ignore_malloc_size_of = "defined in webxr"]
next_hit_test_id: Cell<HitTestId>,
#[ignore_malloc_size_of = "defined in webxr"]
pending_hit_test_promises: DomRefCell<HashMap<HitTestId, Rc<Promise>>>,
/// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame"
/// https://immersive-web.github.io/webxr/#opaque-framebuffer
outside_raf: Cell<bool>,
@ -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<Promise> {
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<f32, ApiSpace, webxr_api::Native>;
// The pose of the viewer in some api-space.

View file

@ -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::<XRReferenceSpace>() {
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 {

View file

@ -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();

View file

@ -1,4 +0,0 @@
[xrDevice_isSessionSupported_immersive-ar.https.html]
[isSessionSupported resolves to false for immersive-ar on an unsupported device]
expected: FAIL

View file

@ -1,4 +0,0 @@
[xrDevice_requestSession_immersive-ar.https.html]
[Tests requestSession rejects immersive-ar mode when unsupported]
expected: FAIL

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,4 +0,0 @@
[xrRay_constructor.https.html]
[XRRay constructors work]
expected: FAIL

View file

@ -1,4 +0,0 @@
[xrRay_matrix.https.html]
[XRRay matrix works]
expected: FAIL

View file

@ -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,
{}

View file

@ -266,11 +266,14 @@ test_interfaces([
"XMLHttpRequestUpload",
"XMLSerializer",
"XRFrame",
"XRHitTestResult",
"XRHitTestSource",
"XRInputSource",
"XRInputSourceArray",
"XRInputSourceEvent",
"XRPose",
"XRReferenceSpace",
"XRRay",
"XRRenderState",
"XRRigidTransform",
"XRSession",

View file

@ -8,7 +8,7 @@
<script>
async_test(function(t) {
navigator.xr.test.simulateDeviceConnection({
supportsImmersive: true,
supportedModes: ["immersive-vr"],
views: TEST_VIEWS,
viewerOrigin: {position: [0.5, 0.1, 0.1], orientation: [1, 0, 0, 1] }
}).then((m) => {

View file

@ -12,7 +12,7 @@
let gl = canvas.getContext('webgl');
promise_test(async function() {
let mock = await navigator.xr.test.simulateDeviceConnection({
supportsImmersive: true,
supportedModes: ["immersive-vr"],
views: TEST_VIEWS,
viewerOrigin: {position: [0.5, 0.1, 0.1], orientation: [1, 0, 0, 1] }
});

View file

@ -12,7 +12,7 @@
let gl = canvas.getContext('webgl');
promise_test(async function() {
let mock = await navigator.xr.test.simulateDeviceConnection({
supportsImmersive: true,
supportedModes: ["immersive-vr"],
views: TEST_VIEWS,
viewerOrigin: {position: [0.5, 0.1, 0.1], orientation: [1, 0, 0, 1] }
});