зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1622134 [wpt PR 22229] - WebXR - add WPTs for transient hit test API, a=testonly
Automatic update from web-platform-tests WebXR - add WPTs for transient hit test API Change-Id: I845e082ca413bcfa19a5ca4e06f587ebcb2cb254 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2101713 Commit-Queue: Piotr Bialecki <bialpio@chromium.org> Reviewed-by: Alexander Cooper <alcooper@chromium.org> Cr-Commit-Position: refs/heads/master@{#750277} -- wpt-commits: 9610643196e6b5eb25b14c33016121290886dac2 wpt-pr: 22229
This commit is contained in:
Родитель
c576686793
Коммит
45ac24b3e2
|
@ -238,6 +238,8 @@ class MockRuntime {
|
|||
|
||||
// Currently active hit test subscriptons.
|
||||
this.hitTestSubscriptions_ = new Map();
|
||||
// Currently active transient hit test subscriptions.
|
||||
this.transientHitTestSubscriptions_ = new Map();
|
||||
// ID of the next subscription to be assigned.
|
||||
this.next_hit_test_id_ = 1;
|
||||
|
||||
|
@ -687,6 +689,25 @@ class MockRuntime {
|
|||
});
|
||||
}
|
||||
|
||||
subscribeToHitTestForTransientInput(profileName, entityTypes, ray){
|
||||
if (!this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {
|
||||
// Reject outside of AR.
|
||||
return Promise.resolve({
|
||||
result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,
|
||||
subscriptionId : 0
|
||||
});
|
||||
}
|
||||
|
||||
// Store the subscription information as-is:
|
||||
const id = this.next_hit_test_id_++;
|
||||
this.transientHitTestSubscriptions_.set(id, { profileName, entityTypes, ray });
|
||||
|
||||
return Promise.resolve({
|
||||
result : device.mojom.SubscribeToHitTestResult.SUCCESS,
|
||||
subscriptionId : id
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function
|
||||
requestRuntimeSession(sessionOptions) {
|
||||
return this.runtimeSupportsSession(sessionOptions).then((result) => {
|
||||
|
@ -769,11 +790,10 @@ class MockRuntime {
|
|||
const mojo_from_native_origin = this._getMojoFromNativeOrigin(subscription.nativeOriginInformation);
|
||||
if (!mojo_from_native_origin) continue;
|
||||
|
||||
const ray_origin = {x: subscription.ray.origin.x, y: subscription.ray.origin.y, z: subscription.ray.origin.z, w: 1};
|
||||
const ray_direction = {x: subscription.ray.direction.x, y: subscription.ray.direction.y, z: subscription.ray.direction.z, w: 0};
|
||||
|
||||
const mojo_ray_origin = XRMathHelper.transform_by_matrix(mojo_from_native_origin, ray_origin);
|
||||
const mojo_ray_direction = XRMathHelper.transform_by_matrix(mojo_from_native_origin, ray_direction);
|
||||
const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(
|
||||
subscription.ray,
|
||||
mojo_from_native_origin
|
||||
);
|
||||
|
||||
const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);
|
||||
|
||||
|
@ -783,6 +803,60 @@ class MockRuntime {
|
|||
|
||||
frameData.hitTestSubscriptionResults.results.push(result);
|
||||
}
|
||||
|
||||
// Transient hit test:
|
||||
const mojo_from_viewer = this._getMojoFromViewer();
|
||||
|
||||
for (const [id, subscription] of this.transientHitTestSubscriptions_) {
|
||||
const result = new device.mojom.XRHitTestTransientInputSubscriptionResultData();
|
||||
result.subscriptionId = id;
|
||||
result.inputSourceIdToHitTestResults = new Map();
|
||||
|
||||
// Find all input sources that match the profile name:
|
||||
const matching_input_sources = Array.from(this.input_sources_.values())
|
||||
.filter(input_source => input_source.profiles_.includes(subscription.profileName));
|
||||
|
||||
for (const input_source of matching_input_sources) {
|
||||
const mojo_from_native_origin = this._getMojoFromInputSource(mojo_from_viewer, input_source);
|
||||
|
||||
const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(
|
||||
subscription.ray,
|
||||
mojo_from_native_origin
|
||||
);
|
||||
|
||||
const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);
|
||||
|
||||
result.inputSourceIdToHitTestResults.set(input_source.source_id_, results);
|
||||
}
|
||||
|
||||
frameData.hitTestSubscriptionResults.transientInputResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns 2-element array [origin, direction] of a ray in mojo space.
|
||||
// |ray| is expressed relative to native origin.
|
||||
_transformRayToMojoSpace(ray, mojo_from_native_origin) {
|
||||
const ray_origin = {
|
||||
x: ray.origin.x,
|
||||
y: ray.origin.y,
|
||||
z: ray.origin.z,
|
||||
w: 1
|
||||
};
|
||||
const ray_direction = {
|
||||
x: ray.direction.x,
|
||||
y: ray.direction.y,
|
||||
z: ray.direction.z,
|
||||
w: 0
|
||||
};
|
||||
|
||||
const mojo_ray_origin = XRMathHelper.transform_by_matrix(
|
||||
mojo_from_native_origin,
|
||||
ray_origin);
|
||||
const mojo_ray_direction = XRMathHelper.transform_by_matrix(
|
||||
mojo_from_native_origin,
|
||||
ray_direction);
|
||||
|
||||
return [mojo_ray_origin, mojo_ray_direction];
|
||||
}
|
||||
|
||||
// Hit tests the passed in ray (expressed as origin and direction) against the mocked world data.
|
||||
|
@ -933,17 +1007,17 @@ class MockRuntime {
|
|||
}
|
||||
|
||||
_getMojoFromInputSource(mojo_from_viewer, input_source) {
|
||||
if(input_source.target_ray_mode_ === 'gaze') { // XRTargetRayMode::GAZING
|
||||
if (input_source.target_ray_mode_ === 'gaze') { // XRTargetRayMode::GAZING
|
||||
// If the pointer origin is gaze, then the result is
|
||||
// just mojo_from_viewer.
|
||||
return mojo_from_viewer;
|
||||
} else if(input_source.target_ray_mode_ === 'tracked-pointer') { // XRTargetRayMode:::POINTING
|
||||
} else if (input_source.target_ray_mode_ === 'tracked-pointer') { // XRTargetRayMode:::POINTING
|
||||
// If the pointer origin is tracked-pointer, the result is just
|
||||
// mojo_from_input*input_from_pointer.
|
||||
return XRMathHelper.mul4x4(
|
||||
input_source.mojo_from_input_.matrix,
|
||||
input_source.input_from_pointer_.matrix);
|
||||
} else if(input_source.target_ray_mode_ === 'screen') { // XRTargetRayMode::TAPPING
|
||||
} else if (input_source.target_ray_mode_ === 'screen') { // XRTargetRayMode::TAPPING
|
||||
// If the pointer origin is screen, the input_from_pointer is
|
||||
// equivalent to viewer_from_pointer and the result is
|
||||
// mojo_from_viewer*viewer_from_pointer.
|
||||
|
@ -951,20 +1025,11 @@ class MockRuntime {
|
|||
mojo_from_viewer,
|
||||
input_source.input_from_pointer_.matrix);
|
||||
} else {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_getMojoFromNativeOrigin(nativeOriginInformation) {
|
||||
const identity = function() {
|
||||
return [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
];
|
||||
};
|
||||
|
||||
_getMojoFromViewer() {
|
||||
const transform = {
|
||||
position: [
|
||||
this.pose_.position.x,
|
||||
|
@ -977,7 +1042,20 @@ class MockRuntime {
|
|||
this.pose_.orientation.w],
|
||||
};
|
||||
|
||||
const mojo_from_viewer = getMatrixFromTransform(transform)
|
||||
return getMatrixFromTransform(transform);
|
||||
}
|
||||
|
||||
_getMojoFromNativeOrigin(nativeOriginInformation) {
|
||||
const identity = function() {
|
||||
return [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
];
|
||||
};
|
||||
|
||||
const mojo_from_viewer = this._getMojoFromViewer();
|
||||
|
||||
if (nativeOriginInformation.$tag == device.mojom.XRNativeOriginInformation.Tags.inputSourceId) {
|
||||
if (!this.input_sources_.has(nativeOriginInformation.inputSourceId)) {
|
||||
|
@ -1026,7 +1104,7 @@ class MockXRInputSource {
|
|||
this.handedness_ = fakeInputSourceInit.handedness;
|
||||
this.target_ray_mode_ = fakeInputSourceInit.targetRayMode;
|
||||
|
||||
if(fakeInputSourceInit.pointerOrigin == null) {
|
||||
if (fakeInputSourceInit.pointerOrigin == null) {
|
||||
throw new TypeError("FakeXRInputSourceInit.pointerOrigin is required.");
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,12 @@ const fakeDeviceInitParams = {
|
|||
supportedFeatures: ALL_FEATURES,
|
||||
};
|
||||
|
||||
// Creates a test method that leverages regular hit test API (as opposed to hit
|
||||
// test for transient input).
|
||||
// |shouldSucceed| - true if the hit test request is expected to succeed, false otherwise
|
||||
// |endSession| - true if the test case should call session.end() prior to requesting hit test
|
||||
const testFunctionGenerator = function(shouldSucceed, endSession) {
|
||||
// |expectedError| - expected error name that should be returned in case shouldSucceed is false
|
||||
const testFunctionGeneratorRegular = function(shouldSucceed, endSession, expectedError) {
|
||||
const testFunction = function(session, fakeDeviceController, t) {
|
||||
session.requestReferenceSpace('viewer').then((viewerRefSpace) => {
|
||||
|
||||
|
@ -39,6 +42,8 @@ const testFunctionGenerator = function(shouldSucceed, endSession) {
|
|||
t.step(() => {
|
||||
assert_false(shouldSucceed,
|
||||
"`requestHitTestSource` failed when it was expected to succeed, error: " + error);
|
||||
assert_equals(error.name, expectedError,
|
||||
"`requestHitTestSource` failed with unexpected error name");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -48,17 +53,17 @@ const testFunctionGenerator = function(shouldSucceed, endSession) {
|
|||
};
|
||||
|
||||
xr_session_promise_test("Hit test subscription succeeds if the feature was requested",
|
||||
testFunctionGenerator(/*shouldSucceed=*/true, /*endSession=*/false),
|
||||
testFunctionGeneratorRegular(/*shouldSucceed=*/true, /*endSession=*/false),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
xr_session_promise_test("Hit test subscription fails if the feature was not requested",
|
||||
testFunctionGenerator(/*shouldSucceed=*/false, /*endSession=*/false),
|
||||
testFunctionGeneratorRegular(/*shouldSucceed=*/false, /*endSession=*/false, "NotSupportedError"),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', {});
|
||||
|
||||
xr_session_promise_test("Hit test subscription fails if the feature was requested but the session already ended",
|
||||
testFunctionGenerator(/*shouldSucceed=*/false, /*endSession=*/true),
|
||||
testFunctionGeneratorRegular(/*shouldSucceed=*/false, /*endSession=*/true, "InvalidStateError"),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../resources/webxr_util.js"></script>
|
||||
<script src="../resources/webxr_test_asserts.js"></script>
|
||||
<script src="../resources/webxr_test_constants.js"></script>
|
||||
<script src="../resources/webxr_test_constants_fake_world.js"></script>
|
||||
<canvas />
|
||||
|
||||
<script>
|
||||
|
||||
const fakeDeviceInitParams = {
|
||||
supportedModes: ["immersive-ar"],
|
||||
views: VALID_VIEWS,
|
||||
supportedFeatures: ALL_FEATURES,
|
||||
};
|
||||
|
||||
// Creates a test method that leverages hit test API for transient input.
|
||||
// |shouldSucceed| - true if the hit test request is expected to succeed, false otherwise
|
||||
// |endSession| - true if the test case should call session.end() prior to requesting hit test
|
||||
// |expectedError| - expected error name that should be returned in case shouldSucceed is false
|
||||
const testFunctionGeneratorTransient = function(shouldSucceed, endSession, expectedError) {
|
||||
const testFunction = function(session, fakeDeviceController, t) {
|
||||
const hitTestOptionsInit = {
|
||||
profile: "generic-touchscreen",
|
||||
offsetRay: new XRRay(),
|
||||
};
|
||||
|
||||
if(endSession) {
|
||||
session.end();
|
||||
}
|
||||
|
||||
return session.requestHitTestSourceForTransientInput(hitTestOptionsInit)
|
||||
.then((hitTestSource) => {
|
||||
t.step(() => {
|
||||
assert_true(shouldSucceed,
|
||||
"`requestHitTestSourceForTransientInput` succeeded when it was expected to fail");
|
||||
});
|
||||
}).catch((error) => {
|
||||
t.step(() => {
|
||||
assert_false(shouldSucceed,
|
||||
"`requestHitTestSourceForTransientInput` failed when it was expected to succeed, error: " + error);
|
||||
assert_equals(error.name, expectedError,
|
||||
"`requestHitTestSourceForTransientInput` failed with unexpected error name");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return testFunction;
|
||||
};
|
||||
|
||||
xr_session_promise_test("Transient hit test subscription succeeds if the feature was requested",
|
||||
testFunctionGeneratorTransient(/*shouldSucceed=*/true, /*endSession=*/false),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
xr_session_promise_test("Transient hit test subscription fails if the feature was not requested",
|
||||
testFunctionGeneratorTransient(/*shouldSucceed=*/false, /*endSession=*/false, "NotSupportedError"),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', {});
|
||||
|
||||
xr_session_promise_test("Transient test subscription fails if the feature was requested but the session already ended",
|
||||
testFunctionGeneratorTransient(/*shouldSucceed=*/false, /*endSession=*/true, "InvalidStateError"),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
</script>
|
|
@ -0,0 +1,173 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../resources/webxr_util.js"></script>
|
||||
<script src="../resources/webxr_test_asserts.js"></script>
|
||||
<script src="../resources/webxr_test_constants.js"></script>
|
||||
<script src="../resources/webxr_test_constants_fake_world.js"></script>
|
||||
<canvas />
|
||||
|
||||
<script>
|
||||
|
||||
// 1m above world origin.
|
||||
const VIEWER_ORIGIN_TRANSFORM = {
|
||||
position: [0, 1, 0],
|
||||
orientation: [0, 0, 0, 1],
|
||||
};
|
||||
|
||||
// 0.25m above world origin.
|
||||
const FLOOR_ORIGIN_TRANSFORM = {
|
||||
position: [0, -0.25, 0],
|
||||
orientation: [0, 0, 0, 1],
|
||||
};
|
||||
|
||||
const SCREEN_POINTER_TRANSFORM = {
|
||||
position: [0, 0, 0], // middle of the screen
|
||||
orientation: [0, 0, 0, 1] // forward-facing
|
||||
};
|
||||
|
||||
const screen_controller_init = {
|
||||
handedness: "none",
|
||||
targetRayMode: "screen",
|
||||
pointerOrigin: SCREEN_POINTER_TRANSFORM, // aka input_from_pointer
|
||||
profiles: ["generic-touchscreen",]
|
||||
};
|
||||
|
||||
const fakeDeviceInitParams = {
|
||||
supportedModes: ["immersive-ar"],
|
||||
views: VALID_VIEWS,
|
||||
floorOrigin: FLOOR_ORIGIN_TRANSFORM, // aka floor_from_mojo
|
||||
viewerOrigin: VIEWER_ORIGIN_TRANSFORM, // aka mojo_from_viewer
|
||||
supportedFeatures: ALL_FEATURES,
|
||||
world: createFakeWorld(5.0, 2.0, 5.0), // see webxr_test_constants_fake_world.js for details
|
||||
};
|
||||
|
||||
// Generates a test function given the parameters for the transient hit test.
|
||||
// |ray| - ray that will be used to subscribe to hit test.
|
||||
// |expectedPoses| - array of expected pose objects. The poses should be expressed in local space.
|
||||
// Null entries in the array mean that the given entry will not be validated.
|
||||
// |inputFromPointer| - input from pointer transform that will be used as the input source's
|
||||
// inputFromPointer (aka pointer origin) in subsequent rAF.
|
||||
// |nextFrameExpectedPoses| - array of expected pose objects. The poses should be expressed in local space.
|
||||
// Null entries in the array mean that the given entry will not be validated.
|
||||
let testFunctionGenerator = function(ray, expectedPoses, inputFromPointer, nextFrameExpectedPoses) {
|
||||
const testFunction = function(session, fakeDeviceController, t) {
|
||||
return session.requestReferenceSpace('local').then((localRefSpace) => new Promise((resolve, reject) => {
|
||||
|
||||
const input_source_controller = fakeDeviceController.simulateInputSourceConnection(screen_controller_init);
|
||||
|
||||
session.requestAnimationFrame((time, frame) => {
|
||||
t.step(() => {
|
||||
assert_equals(session.inputSources.length, 1);
|
||||
});
|
||||
|
||||
const hitTestOptionsInit = {
|
||||
profile: "generic-touchscreen",
|
||||
offsetRay: ray,
|
||||
};
|
||||
|
||||
session.requestHitTestSourceForTransientInput(hitTestOptionsInit)
|
||||
.then((hitTestSource) => {
|
||||
t.step(() => {
|
||||
assert_not_equals(hitTestSource, null);
|
||||
});
|
||||
|
||||
// We got a hit test source, now get the results in subsequent rAFcb:
|
||||
session.requestAnimationFrame((time, frame) => {
|
||||
const results = frame.getHitTestResultsForTransientInput(hitTestSource);
|
||||
|
||||
t.step(() => {
|
||||
assert_true(results != null, "Transient input hit tests should not be null");
|
||||
assert_equals(results.length, 1, "There should be exactly one group of transient hit test results!");
|
||||
assert_equals(results[0].results.length, expectedPoses.length);
|
||||
for(const [index, expectedPose] of expectedPoses.entries()) {
|
||||
const pose = results[0].results[index].getPose(localRefSpace);
|
||||
assert_true(pose != null, "Each hit test result should have a pose in local space");
|
||||
if(expectedPose != null) {
|
||||
assert_transform_approx_equals(pose.transform, expectedPose, FLOAT_EPSILON, "before-move-pose: ");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
input_source_controller.setPointerOrigin(inputFromPointer, false);
|
||||
|
||||
session.requestAnimationFrame((time, frame) => {
|
||||
const results = frame.getHitTestResultsForTransientInput(hitTestSource);
|
||||
|
||||
t.step(() => {
|
||||
assert_equals(results[0].results.length, nextFrameExpectedPoses.length);
|
||||
for(const [index, expectedPose] of nextFrameExpectedPoses.entries()) {
|
||||
const pose = results[0].results[index].getPose(localRefSpace);
|
||||
assert_true(pose != null, "Each hit test result should have a pose in local space");
|
||||
if(expectedPose != null) {
|
||||
assert_transform_approx_equals(pose.transform, expectedPose, FLOAT_EPSILON, "after-move-pose: ");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
return testFunction;
|
||||
};
|
||||
|
||||
|
||||
// Pose of the first expected hit test result - straight ahead of the input source, viewer-facing.
|
||||
const pose_1 = {
|
||||
position: {x: 0.0, y: 1.0, z: -2.5, w: 1.0},
|
||||
orientation: {x: 0.0, y: -0.707, z: -0.707, w: 0.0},
|
||||
// Hit test API will set Y axis to the surface normal at the intersection point,
|
||||
// Z axis towards the ray origin and X axis to cross product of Y axis & Z axis.
|
||||
// If the surface normal and Z axis would be parallel, the hit test API
|
||||
// will attempt to use `up` vector ([0, 1, 0]) as the Z axis, and if it so happens that Z axis
|
||||
// and the surface normal would still be parallel, it will use the `right` vector ([1, 0, 0]) as the Z axis.
|
||||
// In this particular case, `up` vector will work so the resulting pose.orientation
|
||||
// becomes a rotation around [0, 1, 1] vector by 180 degrees.
|
||||
};
|
||||
|
||||
xr_session_promise_test("Ensures subscription to transient hit test works with an XRSpace from input source - no move",
|
||||
testFunctionGenerator(new XRRay(), [pose_1], SCREEN_POINTER_TRANSFORM, [pose_1]),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
const moved_pointer_transform_1 = {
|
||||
position: [0, 0, 0], // middle of the screen
|
||||
orientation: [ 0.707, 0, 0, 0.707 ] // 90 degrees around X axis = facing up
|
||||
};
|
||||
|
||||
xr_session_promise_test("Ensures subscription to transient hit test works with an XRSpace from input source - after move - no results",
|
||||
testFunctionGenerator(new XRRay(), [pose_1], moved_pointer_transform_1, []),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
const pose_2 = {
|
||||
position: {x: -1.443, y: 1.0, z: -2.5, w: 1.0},
|
||||
// Intersection point will be on the same height as the viewer, on the front
|
||||
// wall. Distance from the front wall to viewer is 2.5m, and we are rotating
|
||||
// to the left, so X coordinate of the intersection point will be negative
|
||||
// & equal to -2.5 * tan(30 deg) ~= 1.443m.
|
||||
orientation: {x: 0.5, y: 0.5, z: 0.5, w: 0.5 },
|
||||
// See comment for pose_1.orientation for details.
|
||||
// In this case, the hit test pose will have Y axis facing towards world's
|
||||
// positive Z axis ([0,0,1]), Z axis to the right ([1,0,0]) and X axis
|
||||
// towards world's Y axis ([0,1,0]).
|
||||
// This is equivalent to the rotation around [1, 1, 1] vector by 120 degrees.
|
||||
};
|
||||
|
||||
const moved_pointer_transform_2 = {
|
||||
position: [0, 0, 0], // middle of the screen
|
||||
orientation: [ 0, 0.2588, 0, 0.9659 ] // 30 degrees around Y axis = to the left,
|
||||
// creating 30-60-90 triangle with the front wall
|
||||
};
|
||||
|
||||
xr_session_promise_test("Ensures subscription to transient hit test works with an XRSpace from input source - after move - 1 result",
|
||||
testFunctionGenerator(new XRRay(), [pose_1], moved_pointer_transform_2, [pose_2]),
|
||||
fakeDeviceInitParams,
|
||||
'immersive-ar', { 'requiredFeatures': ['hit-test'] });
|
||||
|
||||
</script>
|
Загрузка…
Ссылка в новой задаче