tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ar_hittest_subscription_inputSources.https.html (7524B)


      1 <!DOCTYPE html>
      2 <script src="/resources/testharness.js"></script>
      3 <script src="/resources/testharnessreport.js"></script>
      4 <script src="../resources/webxr_util.js"></script>
      5 <script src="../resources/webxr_math_utils.js"></script>
      6 <script src="../resources/webxr_test_asserts.js"></script>
      7 <script src="../resources/webxr_test_constants.js"></script>
      8 <script src="../resources/webxr_test_constants_fake_world.js"></script>
      9 
     10 <script>
     11 
     12 // 1m above world origin.
     13 const VIEWER_ORIGIN_TRANSFORM = {
     14  position: [0, 1, 0],
     15  orientation: [0, 0, 0, 1],
     16 };
     17 
     18 // 0.25m above world origin.
     19 const FLOOR_ORIGIN_TRANSFORM = {
     20  position: [0, -0.25, 0],
     21  orientation: [0, 0, 0, 1],
     22 };
     23 
     24 // Start the screen pointer at the same place as the viewer, so it's essentially
     25 // coming straight forward from the middle of the screen.
     26 const SCREEN_POINTER_TRANSFORM = VIEWER_ORIGIN_TRANSFORM;
     27 
     28 const screen_controller_init = {
     29    handedness: "none",
     30    targetRayMode: "screen",
     31    pointerOrigin: SCREEN_POINTER_TRANSFORM,  // aka mojo_from_pointer
     32    profiles: ["generic-touchscreen",]
     33 };
     34 
     35 const fakeDeviceInitParams = {
     36  supportedModes: ["immersive-ar"],
     37  views: VALID_VIEWS,
     38  floorOrigin: FLOOR_ORIGIN_TRANSFORM,    // aka floor_from_mojo
     39  viewerOrigin: VIEWER_ORIGIN_TRANSFORM,  // aka mojo_from_viewer
     40  supportedFeatures: ALL_FEATURES,
     41  world: createFakeWorld(5.0, 2.0, 5.0),  // see webxr_test_constants_fake_world.js for details
     42 };
     43 
     44 // Generates a test function given the parameters for the hit test.
     45 // |ray| - ray that will be used to subscribe to hit test.
     46 // |expectedPoses| - array of expected pose objects. The poses should be expressed in local space.
     47 //                   Null entries in the array mean that the given entry will not be validated.
     48 // |inputFromPointer| - input from pointer transform that will be used as the input source's
     49 //                      inputFromPointer (aka pointer origin) in subsequent rAF.
     50 // |nextFrameExpectedPoses| - array of expected pose objects. The poses should be expressed in local space.
     51 //                            Null entries in the array mean that the given entry will not be validated.
     52 let testFunctionGenerator = function(ray, expectedPoses, inputFromPointer, nextFrameExpectedPoses) {
     53  const testFunction = function(session, fakeDeviceController, t) {
     54    return session.requestReferenceSpace('local').then((localRefSpace) => new Promise((resolve, reject) => {
     55 
     56      const input_source_controller = fakeDeviceController.simulateInputSourceConnection(screen_controller_init);
     57 
     58      requestSkipAnimationFrame(session, (time, frame) => {
     59        t.step(() => {
     60          assert_equals(session.inputSources.length, 1);
     61        });
     62 
     63        const input_source = session.inputSources[0];
     64        const hitTestOptionsInit = {
     65          space: input_source.targetRaySpace,
     66          offsetRay: ray,
     67        };
     68 
     69        session.requestHitTestSource(hitTestOptionsInit).then((hitTestSource) => {
     70          t.step(() => {
     71            assert_not_equals(hitTestSource, null);
     72          });
     73 
     74          // We got a hit test source, now get the results in subsequent rAFcb:
     75          session.requestAnimationFrame((time, frame) => {
     76            const results = frame.getHitTestResults(hitTestSource);
     77 
     78            t.step(() => {
     79              assert_equals(results.length, expectedPoses.length);
     80              for(const [index, expectedPose] of expectedPoses.entries()) {
     81                const pose = results[index].getPose(localRefSpace);
     82                assert_true(pose != null, "Each hit test result should have a pose in local space");
     83                if(expectedPose != null) {
     84                  assert_transform_approx_equals(pose.transform, expectedPose, FLOAT_EPSILON, "before-move-pose: ");
     85                }
     86              }
     87            });
     88 
     89            input_source_controller.setPointerOrigin(inputFromPointer, false);
     90 
     91            session.requestAnimationFrame((time, frame) => {
     92              const results = frame.getHitTestResults(hitTestSource);
     93 
     94              t.step(() => {
     95                assert_equals(results.length, nextFrameExpectedPoses.length);
     96                for(const [index, expectedPose] of nextFrameExpectedPoses.entries()) {
     97                  const pose = results[index].getPose(localRefSpace);
     98                  assert_true(pose != null, "Each hit test result should have a pose in local space");
     99                  if(expectedPose != null) {
    100                    assert_transform_approx_equals(pose.transform, expectedPose, FLOAT_EPSILON, "after-move-pose: ");
    101                  }
    102                }
    103              });
    104 
    105              resolve();
    106            });
    107          });
    108        });
    109      });
    110    }));
    111  };
    112 
    113  return testFunction;
    114 };
    115 
    116 
    117 // Pose of the first expected hit test result - straight ahead of the input source, viewer-facing.
    118 const pose_1 = {
    119  position: {x: 0.0, y: 1.0, z: -2.5, w: 1.0},
    120  orientation: {x: 0.0, y: -0.707, z: -0.707, w: 0.0},
    121    // Hit test API will set Y axis to the surface normal at the intersection point,
    122    // Z axis towards the ray origin and X axis to cross product of Y axis & Z axis.
    123    // If the surface normal and Z axis would be parallel, the hit test API
    124    // will attempt to use `up` vector ([0, 1, 0]) as the Z axis, and if it so happens that Z axis
    125    // and the surface normal would still be parallel, it will use the `right` vector ([1, 0, 0]) as the Z axis.
    126    // In this particular case, `up` vector will work so the resulting pose.orientation
    127    // becomes a rotation around [0, 1, 1] vector by 180 degrees.
    128 };
    129 
    130 xr_session_promise_test("Ensures subscription to hit test works with an XRSpace from input source - no move",
    131  testFunctionGenerator(new XRRay(), [pose_1], SCREEN_POINTER_TRANSFORM, [pose_1]),
    132  fakeDeviceInitParams,
    133  'immersive-ar', { 'requiredFeatures': ['hit-test'] });
    134 
    135 const moved_pointer_transform_1 = {
    136  position: [0, 1, 0],
    137  orientation: [ 0.707, 0, 0, 0.707 ] // 90 degrees around X axis = facing up
    138 };
    139 
    140 xr_session_promise_test("Ensures subscription to hit test works with an XRSpace from input source - after move - no results",
    141  testFunctionGenerator(new XRRay(), [pose_1], moved_pointer_transform_1, []),
    142  fakeDeviceInitParams,
    143  'immersive-ar', { 'requiredFeatures': ['hit-test'] });
    144 
    145 const pose_2 = {
    146  position: {x: -1.443, y: 1.0, z: -2.5, w: 1.0},
    147    // Intersection point will be on the same height as the viewer, on the front
    148    // wall. Distance from the front wall to viewer is 2.5m, and we are rotating
    149    // to the left, so X coordinate of the intersection point will be negative
    150    // & equal to -2.5 * tan(30 deg) ~= 1.443m.
    151  orientation: {x: 0.5, y: 0.5, z: 0.5, w: 0.5 },
    152    // See comment for pose_1.orientation for details.
    153    // In this case, the hit test pose will have Y axis facing towards world's
    154    // positive Z axis ([0,0,1]), Z axis to the right ([1,0,0]) and X axis
    155    // towards world's Y axis ([0,1,0]).
    156    // This is equivalent to the rotation around [1, 1, 1] vector by 120 degrees.
    157 };
    158 
    159 const moved_pointer_transform_2 = {
    160  position: [0, 1, 0],
    161  orientation: [ 0, 0.2588, 0, 0.9659 ] // 30 degrees around Y axis = to the left,
    162                                        // creating 30-60-90 triangle with the front wall
    163 };
    164 
    165 xr_session_promise_test("Ensures subscription to hit test works with an XRSpace from input source - after move - 1 result",
    166  testFunctionGenerator(new XRRay(), [pose_1], moved_pointer_transform_2, [pose_2]),
    167  fakeDeviceInitParams,
    168  'immersive-ar', { 'requiredFeatures': ['hit-test'] });
    169 
    170 </script>