How to use viewWithCamera method in wpt

Best JavaScript code snippet using wpt

webxr-test.js

Source:webxr-test.js Github

copy

Full Screen

1import * as vrMojom from '/gen/device/vr/public/mojom/vr_service.mojom.m.js';2import {GamepadHand, GamepadMapping} from '/gen/device/gamepad/public/mojom/gamepad.mojom.m.js';3// This polyfill library implements the WebXR Test API as specified here:4// https://github.com/immersive-web/webxr-test-api5const defaultMojoFromFloor = {6 matrix: [1, 0, 0, 0,7 0, 1, 0, 0,8 0, 0, 1, 0,9 0, -1.65, 0, 1]10};11const default_stage_parameters = {12 mojoFromFloor: defaultMojoFromFloor,13 bounds: null14};15const default_framebuffer_scale = 0.7;16function getMatrixFromTransform(transform) {17 const x = transform.orientation[0];18 const y = transform.orientation[1];19 const z = transform.orientation[2];20 const w = transform.orientation[3];21 const m11 = 1.0 - 2.0 * (y * y + z * z);22 const m21 = 2.0 * (x * y + z * w);23 const m31 = 2.0 * (x * z - y * w);24 const m12 = 2.0 * (x * y - z * w);25 const m22 = 1.0 - 2.0 * (x * x + z * z);26 const m32 = 2.0 * (y * z + x * w);27 const m13 = 2.0 * (x * z + y * w);28 const m23 = 2.0 * (y * z - x * w);29 const m33 = 1.0 - 2.0 * (x * x + y * y);30 const m14 = transform.position[0];31 const m24 = transform.position[1];32 const m34 = transform.position[2];33 // Column-major linearized order is expected.34 return [m11, m21, m31, 0,35 m12, m22, m32, 0,36 m13, m23, m33, 0,37 m14, m24, m34, 1];38}39function getPoseFromTransform(transform) {40 const [px, py, pz] = transform.position;41 const [ox, oy, oz, ow] = transform.orientation;42 return {43 position: {x: px, y: py, z: pz},44 orientation: {x: ox, y: oy, z: oz, w: ow},45 };46}47function composeGFXTransform(fakeTransformInit) {48 return {matrix: getMatrixFromTransform(fakeTransformInit)};49}50// Value equality for camera image init objects - they must contain `width` &51// `height` properties and may contain `pixels` property.52function isSameCameraImageInit(rhs, lhs) {53 return lhs.width === rhs.width && lhs.height === rhs.height && lhs.pixels === rhs.pixels;54}55class ChromeXRTest {56 constructor() {57 this.mockVRService_ = new MockVRService();58 }59 // WebXR Test API60 simulateDeviceConnection(init_params) {61 return Promise.resolve(this.mockVRService_._addRuntime(init_params));62 }63 disconnectAllDevices() {64 this.mockVRService_._removeAllRuntimes();65 return Promise.resolve();66 }67 simulateUserActivation(callback) {68 if (window.top !== window) {69 // test_driver.click only works for the toplevel frame. This alternate70 // Chrome-specific method is sufficient for starting an XR session in an71 // iframe, and is used in platform-specific tests.72 //73 // TODO(https://github.com/web-platform-tests/wpt/issues/20282): use74 // a cross-platform method if available.75 xr_debug('simulateUserActivation', 'use eventSender');76 document.addEventListener('click', callback);77 eventSender.mouseMoveTo(0, 0);78 eventSender.mouseDown();79 eventSender.mouseUp();80 document.removeEventListener('click', callback);81 return;82 }83 const button = document.createElement('button');84 button.textContent = 'click to continue test';85 button.style.display = 'block';86 button.style.fontSize = '20px';87 button.style.padding = '10px';88 button.onclick = () => {89 callback();90 document.body.removeChild(button);91 };92 document.body.appendChild(button);93 test_driver.click(button);94 }95 // Helper method leveraged by chrome-specific setups.96 Debug(name, msg) {97 console.log(new Date().toISOString() + ' DEBUG[' + name + '] ' + msg);98 }99}100// Mocking class definitions101// Mock service implements the VRService mojo interface.102class MockVRService {103 constructor() {104 this.receiver_ = new vrMojom.VRServiceReceiver(this);105 this.runtimes_ = [];106 this.interceptor_ =107 new MojoInterfaceInterceptor(vrMojom.VRService.$interfaceName);108 this.interceptor_.oninterfacerequest =109 e => this.receiver_.$.bindHandle(e.handle);110 this.interceptor_.start();111 }112 // WebXR Test API Implementation Helpers113 _addRuntime(fakeDeviceInit) {114 const runtime = new MockRuntime(fakeDeviceInit, this);115 this.runtimes_.push(runtime);116 if (this.client_) {117 this.client_.onDeviceChanged();118 }119 return runtime;120 }121 _removeAllRuntimes() {122 if (this.client_) {123 this.client_.onDeviceChanged();124 }125 this.runtimes_ = [];126 }127 _removeRuntime(device) {128 const index = this.runtimes_.indexOf(device);129 if (index >= 0) {130 this.runtimes_.splice(index, 1);131 if (this.client_) {132 this.client_.onDeviceChanged();133 }134 }135 }136 // VRService overrides137 setClient(client) {138 if (this.client_) {139 throw new Error("setClient should only be called once");140 }141 this.client_ = client;142 }143 requestSession(sessionOptions) {144 const requests = [];145 // Request a session from all the runtimes.146 for (let i = 0; i < this.runtimes_.length; i++) {147 requests[i] = this.runtimes_[i]._requestRuntimeSession(sessionOptions);148 }149 return Promise.all(requests).then((results) => {150 // Find and return the first successful result.151 for (let i = 0; i < results.length; i++) {152 if (results[i].session) {153 // Construct a dummy metrics recorder154 const metricsRecorderPtr = new vrMojom.XRSessionMetricsRecorderRemote();155 metricsRecorderPtr.$.bindNewPipeAndPassReceiver().handle.close();156 const success = {157 session: results[i].session,158 metricsRecorder: metricsRecorderPtr,159 };160 return {result: {success}};161 }162 }163 // If there were no successful results, returns a null session.164 return {165 result: {failureReason: vrMojom.RequestSessionError.NO_RUNTIME_FOUND}166 };167 });168 }169 supportsSession(sessionOptions) {170 const requests = [];171 // Check supports on all the runtimes.172 for (let i = 0; i < this.runtimes_.length; i++) {173 requests[i] = this.runtimes_[i]._runtimeSupportsSession(sessionOptions);174 }175 return Promise.all(requests).then((results) => {176 // Find and return the first successful result.177 for (let i = 0; i < results.length; i++) {178 if (results[i].supportsSession) {179 return results[i];180 }181 }182 // If there were no successful results, returns false.183 return {supportsSession: false};184 });185 }186 exitPresent() {187 return Promise.resolve();188 }189 setFramesThrottled(throttled) {190 this.setFramesThrottledImpl(throttled);191 }192 // We cannot override the mojom interceptors via the prototype; so this method193 // and the above indirection exist to allow overrides by internal code.194 setFramesThrottledImpl(throttled) {}195 // Only handles asynchronous calls to makeXrCompatible. Synchronous calls are196 // not supported in Javascript.197 makeXrCompatible() {198 if (this.runtimes_.length == 0) {199 return {200 xrCompatibleResult: vrMojom.XrCompatibleResult.kNoDeviceAvailable201 };202 }203 return {xrCompatibleResult: vrMojom.XrCompatibleResult.kAlreadyCompatible};204 }205}206class FakeXRAnchorController {207 constructor() {208 // Private properties.209 this.device_ = null;210 this.id_ = null;211 this.dirty_ = true;212 // Properties backing up public attributes / methods.213 this.deleted_ = false;214 this.paused_ = false;215 this.anchorOrigin_ = XRMathHelper.identity();216 }217 // WebXR Test API (Anchors Extension)218 get deleted() {219 return this.deleted_;220 }221 pauseTracking() {222 if(!this.paused_) {223 this.paused_ = true;224 this.dirty_ = true;225 }226 }227 resumeTracking() {228 if(this.paused_) {229 this.paused_ = false;230 this.dirty_ = true;231 }232 }233 stopTracking() {234 if(!this.deleted_) {235 this.device_._deleteAnchorController(this.id_);236 this.deleted_ = true;237 this.dirty_ = true;238 }239 }240 setAnchorOrigin(anchorOrigin) {241 this.anchorOrigin_ = getMatrixFromTransform(anchorOrigin);242 this.dirty_ = true;243 }244 // Internal implementation:245 set id(value) {246 this.id_ = value;247 }248 set device(value) {249 this.device_ = value;250 }251 get dirty() {252 return this.dirty_;253 }254 get paused() {255 return this.paused_;256 }257 _markProcessed() {258 this.dirty_ = false;259 }260 _getAnchorOrigin() {261 return this.anchorOrigin_;262 }263}264// Implements XRFrameDataProvider and XRPresentationProvider. Maintains a mock265// for XRPresentationProvider. Implements FakeXRDevice test API.266class MockRuntime {267 // Mapping from string feature names to the corresponding mojo types.268 // This is exposed as a member for extensibility.269 static _featureToMojoMap = {270 'viewer': vrMojom.XRSessionFeature.REF_SPACE_VIEWER,271 'local': vrMojom.XRSessionFeature.REF_SPACE_LOCAL,272 'local-floor': vrMojom.XRSessionFeature.REF_SPACE_LOCAL_FLOOR,273 'bounded-floor': vrMojom.XRSessionFeature.REF_SPACE_BOUNDED_FLOOR,274 'unbounded': vrMojom.XRSessionFeature.REF_SPACE_UNBOUNDED,275 'hit-test': vrMojom.XRSessionFeature.HIT_TEST,276 'dom-overlay': vrMojom.XRSessionFeature.DOM_OVERLAY,277 'light-estimation': vrMojom.XRSessionFeature.LIGHT_ESTIMATION,278 'anchors': vrMojom.XRSessionFeature.ANCHORS,279 'depth-sensing': vrMojom.XRSessionFeature.DEPTH,280 'secondary-views': vrMojom.XRSessionFeature.SECONDARY_VIEWS,281 'camera-access': vrMojom.XRSessionFeature.CAMERA_ACCESS,282 };283 static _sessionModeToMojoMap = {284 "inline": vrMojom.XRSessionMode.kInline,285 "immersive-vr": vrMojom.XRSessionMode.kImmersiveVr,286 "immersive-ar": vrMojom.XRSessionMode.kImmersiveAr,287 };288 static _environmentBlendModeToMojoMap = {289 "opaque": vrMojom.XREnvironmentBlendMode.kOpaque,290 "alpha-blend": vrMojom.XREnvironmentBlendMode.kAlphaBlend,291 "additive": vrMojom.XREnvironmentBlendMode.kAdditive,292 };293 static _interactionModeToMojoMap = {294 "screen-space": vrMojom.XRInteractionMode.kScreenSpace,295 "world-space": vrMojom.XRInteractionMode.kWorldSpace,296 };297 constructor(fakeDeviceInit, service) {298 this.sessionClient_ = null;299 this.presentation_provider_ = new MockXRPresentationProvider();300 this.pose_ = null;301 this.next_frame_id_ = 0;302 this.bounds_ = null;303 this.send_mojo_space_reset_ = false;304 this.stageParameters_ = null;305 this.stageParametersId_ = 1;306 this.service_ = service;307 this.framesOfReference = {};308 this.input_sources_ = new Map();309 this.next_input_source_index_ = 1;310 // Currently active hit test subscriptons.311 this.hitTestSubscriptions_ = new Map();312 // Currently active transient hit test subscriptions.313 this.transientHitTestSubscriptions_ = new Map();314 // ID of the next subscription to be assigned.315 this.next_hit_test_id_ = 1n;316 this.anchor_controllers_ = new Map();317 // ID of the next anchor to be assigned.318 this.next_anchor_id_ = 1n;319 // Anchor creation callback (initially null, can be set by tests).320 this.anchor_creation_callback_ = null;321 this.depthSensingData_ = null;322 this.depthSensingDataDirty_ = false;323 let supportedModes = [];324 if (fakeDeviceInit.supportedModes) {325 supportedModes = fakeDeviceInit.supportedModes.slice();326 if (fakeDeviceInit.supportedModes.length === 0) {327 supportedModes = ["inline"];328 }329 } else {330 // Back-compat mode.331 console.warn("Please use `supportedModes` to signal which modes are supported by this device.");332 if (fakeDeviceInit.supportsImmersive == null) {333 throw new TypeError("'supportsImmersive' must be set");334 }335 supportedModes = ["inline"];336 if (fakeDeviceInit.supportsImmersive) {337 supportedModes.push("immersive-vr");338 }339 }340 this.supportedModes_ = this._convertModesToEnum(supportedModes);341 if (this.supportedModes_.length == 0) {342 console.error("Device has empty supported modes array!");343 throw new InvalidStateError();344 }345 if (fakeDeviceInit.viewerOrigin != null) {346 this.setViewerOrigin(fakeDeviceInit.viewerOrigin);347 }348 if (fakeDeviceInit.floorOrigin != null) {349 this.setFloorOrigin(fakeDeviceInit.floorOrigin);350 }351 if (fakeDeviceInit.world) {352 this.setWorld(fakeDeviceInit.world);353 }354 if (fakeDeviceInit.depthSensingData) {355 this.setDepthSensingData(fakeDeviceInit.depthSensingData);356 }357 this.defaultFramebufferScale_ = default_framebuffer_scale;358 this.enviromentBlendMode_ = this._convertBlendModeToEnum(fakeDeviceInit.environmentBlendMode);359 this.interactionMode_ = this._convertInteractionModeToEnum(fakeDeviceInit.interactionMode);360 // This appropriately handles if the coordinates are null361 this.setBoundsGeometry(fakeDeviceInit.boundsCoordinates);362 this.setViews(fakeDeviceInit.views, fakeDeviceInit.secondaryViews);363 // Need to support webVR which doesn't have a notion of features364 this._setFeatures(fakeDeviceInit.supportedFeatures || []);365 }366 // WebXR Test API367 setViews(primaryViews, secondaryViews) {368 this.cameraImage_ = null;369 this.primaryViews_ = [];370 this.secondaryViews_ = [];371 let xOffset = 0;372 if (primaryViews) {373 this.primaryViews_ = [];374 xOffset = this._setViews(primaryViews, xOffset, this.primaryViews_);375 const cameraImage = this._findCameraImage(primaryViews);376 if (cameraImage) {377 this.cameraImage_ = cameraImage;378 }379 }380 if (secondaryViews) {381 this.secondaryViews_ = [];382 this._setViews(secondaryViews, xOffset, this.secondaryViews_);383 const cameraImage = this._findCameraImage(secondaryViews);384 if (cameraImage) {385 if (!isSameCameraImageInit(this.cameraImage_, cameraImage)) {386 throw new Error("If present, camera resolutions on each view must match each other!"387 + " Secondary views' camera doesn't match primary views.");388 }389 this.cameraImage_ = cameraImage;390 }391 }392 }393 disconnect() {394 this.service_._removeRuntime(this);395 this.presentation_provider_._close();396 if (this.sessionClient_) {397 this.sessionClient_.$.close();398 this.sessionClient_ = null;399 }400 return Promise.resolve();401 }402 setViewerOrigin(origin, emulatedPosition = false) {403 const p = origin.position;404 const q = origin.orientation;405 this.pose_ = {406 orientation: { x: q[0], y: q[1], z: q[2], w: q[3] },407 position: { x: p[0], y: p[1], z: p[2] },408 emulatedPosition: emulatedPosition,409 angularVelocity: null,410 linearVelocity: null,411 angularAcceleration: null,412 linearAcceleration: null,413 inputState: null,414 poseIndex: 0415 };416 }417 clearViewerOrigin() {418 this.pose_ = null;419 }420 setFloorOrigin(floorOrigin) {421 if (!this.stageParameters_) {422 this.stageParameters_ = default_stage_parameters;423 this.stageParameters_.bounds = this.bounds_;424 }425 // floorOrigin is passed in as mojoFromFloor.426 this.stageParameters_.mojoFromFloor =427 {matrix: getMatrixFromTransform(floorOrigin)};428 this._onStageParametersUpdated();429 }430 clearFloorOrigin() {431 if (this.stageParameters_) {432 this.stageParameters_ = null;433 this._onStageParametersUpdated();434 }435 }436 setBoundsGeometry(bounds) {437 if (bounds == null) {438 this.bounds_ = null;439 } else if (bounds.length < 3) {440 throw new Error("Bounds must have a length of at least 3");441 } else {442 this.bounds_ = bounds;443 }444 // We can only set bounds if we have stageParameters set; otherwise, we445 // don't know the transform from local space to bounds space.446 // We'll cache the bounds so that they can be set in the future if the447 // floorLevel transform is set, but we won't update them just yet.448 if (this.stageParameters_) {449 this.stageParameters_.bounds = this.bounds_;450 this._onStageParametersUpdated();451 }452 }453 simulateResetPose() {454 this.send_mojo_space_reset_ = true;455 }456 simulateVisibilityChange(visibilityState) {457 let mojoState = null;458 switch (visibilityState) {459 case "visible":460 mojoState = vrMojom.XRVisibilityState.VISIBLE;461 break;462 case "visible-blurred":463 mojoState = vrMojom.XRVisibilityState.VISIBLE_BLURRED;464 break;465 case "hidden":466 mojoState = vrMojom.XRVisibilityState.HIDDEN;467 break;468 }469 if (mojoState && this.sessionClient_) {470 this.sessionClient_.onVisibilityStateChanged(mojoState);471 }472 }473 simulateInputSourceConnection(fakeInputSourceInit) {474 const index = this.next_input_source_index_;475 this.next_input_source_index_++;476 const source = new MockXRInputSource(fakeInputSourceInit, index, this);477 this.input_sources_.set(index, source);478 return source;479 }480 // WebXR Test API Hit Test extensions481 setWorld(world) {482 this.world_ = world;483 }484 clearWorld() {485 this.world_ = null;486 }487 // WebXR Test API Anchor extensions488 setAnchorCreationCallback(callback) {489 this.anchor_creation_callback_ = callback;490 }491 setHitTestSourceCreationCallback(callback) {492 this.hit_test_source_creation_callback_ = callback;493 }494 // WebXR Test API Lighting estimation extensions495 setLightEstimate(fakeXrLightEstimateInit) {496 if (!fakeXrLightEstimateInit.sphericalHarmonicsCoefficients) {497 throw new TypeError("sphericalHarmonicsCoefficients must be set");498 }499 if (fakeXrLightEstimateInit.sphericalHarmonicsCoefficients.length != 27) {500 throw new TypeError("Must supply all 27 sphericalHarmonicsCoefficients");501 }502 if (fakeXrLightEstimateInit.primaryLightDirection && fakeXrLightEstimateInit.primaryLightDirection.w != 0) {503 throw new TypeError("W component of primaryLightDirection must be 0");504 }505 if (fakeXrLightEstimateInit.primaryLightIntensity && fakeXrLightEstimateInit.primaryLightIntensity.w != 1) {506 throw new TypeError("W component of primaryLightIntensity must be 1");507 }508 // If the primaryLightDirection or primaryLightIntensity aren't set, we need to set them509 // to the defaults that the spec expects. ArCore will either give us everything or nothing,510 // so these aren't nullable on the mojom.511 if (!fakeXrLightEstimateInit.primaryLightDirection) {512 fakeXrLightEstimateInit.primaryLightDirection = { x: 0.0, y: 1.0, z: 0.0, w: 0.0 };513 }514 if (!fakeXrLightEstimateInit.primaryLightIntensity) {515 fakeXrLightEstimateInit.primaryLightIntensity = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };516 }517 let c = fakeXrLightEstimateInit.sphericalHarmonicsCoefficients;518 this.light_estimate_ = {519 lightProbe: {520 // XRSphereicalHarmonics521 sphericalHarmonics: {522 coefficients: [523 { red: c[0], green: c[1], blue: c[2] },524 { red: c[3], green: c[4], blue: c[5] },525 { red: c[6], green: c[7], blue: c[8] },526 { red: c[9], green: c[10], blue: c[11] },527 { red: c[12], green: c[13], blue: c[14] },528 { red: c[15], green: c[16], blue: c[17] },529 { red: c[18], green: c[19], blue: c[20] },530 { red: c[21], green: c[22], blue: c[23] },531 { red: c[24], green: c[25], blue: c[26] }532 ]533 },534 // Vector3dF535 mainLightDirection: {536 x: fakeXrLightEstimateInit.primaryLightDirection.x,537 y: fakeXrLightEstimateInit.primaryLightDirection.y,538 z: fakeXrLightEstimateInit.primaryLightDirection.z539 },540 // RgbTupleF32541 mainLightIntensity: {542 red: fakeXrLightEstimateInit.primaryLightIntensity.x,543 green: fakeXrLightEstimateInit.primaryLightIntensity.y,544 blue: fakeXrLightEstimateInit.primaryLightIntensity.z545 }546 }547 }548 }549 // WebXR Test API depth Sensing Extensions550 setDepthSensingData(depthSensingData) {551 for(const key of ["depthData", "normDepthBufferFromNormView", "rawValueToMeters", "width", "height"]) {552 if(!(key in depthSensingData)) {553 throw new TypeError("Required key not present. Key: " + key);554 }555 }556 if(depthSensingData.depthData != null) {557 // Create new object w/ properties based on the depthSensingData, but558 // convert the FakeXRRigidTransformInit into a transformation matrix object.559 this.depthSensingData_ = Object.assign({},560 depthSensingData, {561 normDepthBufferFromNormView: composeGFXTransform(depthSensingData.normDepthBufferFromNormView),562 });563 } else {564 throw new TypeError("`depthData` is not set");565 }566 this.depthSensingDataDirty_ = true;567 }568 clearDepthSensingData() {569 this.depthSensingData_ = null;570 this.depthSensingDataDirty_ = true;571 }572 // Internal Implementation/Helper Methods573 _convertModeToEnum(sessionMode) {574 if (sessionMode in MockRuntime._sessionModeToMojoMap) {575 return MockRuntime._sessionModeToMojoMap[sessionMode];576 }577 throw new TypeError("Unrecognized value for XRSessionMode enum: " + sessionMode);578 }579 _convertModesToEnum(sessionModes) {580 return sessionModes.map(mode => this._convertModeToEnum(mode));581 }582 _convertBlendModeToEnum(blendMode) {583 if (blendMode in MockRuntime._environmentBlendModeToMojoMap) {584 return MockRuntime._environmentBlendModeToMojoMap[blendMode];585 } else {586 if (this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {587 return vrMojom.XREnvironmentBlendMode.kAdditive;588 } else if (this.supportedModes_.includes(589 vrMojom.XRSessionMode.kImmersiveVr)) {590 return vrMojom.XREnvironmentBlendMode.kOpaque;591 }592 }593 }594 _convertInteractionModeToEnum(interactionMode) {595 if (interactionMode in MockRuntime._interactionModeToMojoMap) {596 return MockRuntime._interactionModeToMojoMap[interactionMode];597 } else {598 return vrMojom.XRInteractionMode.kWorldSpace;599 }600 }601 _setViews(deviceViews, xOffset, views) {602 for (let i = 0; i < deviceViews.length; i++) {603 views[i] = this._getView(deviceViews[i], xOffset);604 xOffset += deviceViews[i].resolution.width;605 }606 return xOffset;607 }608 _findCameraImage(views) {609 const viewWithCamera = views.find(view => view.cameraImageInit);610 if (viewWithCamera) {611 //If we have one view with a camera resolution, all views should have the same camera resolution.612 const allViewsHaveSameCamera = views.every(613 view => isSameCameraImageInit(view.cameraImageInit, viewWithCamera.cameraImageInit));614 if (!allViewsHaveSameCamera) {615 throw new Error("If present, camera resolutions on each view must match each other!");616 }617 return viewWithCamera.cameraImageInit;618 }619 return null;620 }621 _onStageParametersUpdated() {622 // Indicate for the frame loop that the stage parameters have been updated.623 this.stageParametersId_++;624 }625 _getDefaultViews() {626 if (this.primaryViews_) {627 return this.primaryViews_;628 }629 const viewport_size = 20;630 return [{631 eye: vrMojom.XREye.kLeft,632 fieldOfView: {633 upDegrees: 48.316,634 downDegrees: 50.099,635 leftDegrees: 50.899,636 rightDegrees: 35.197637 },638 mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform({639 position: [-0.032, 0, 0],640 orientation: [0, 0, 0, 1]641 })),642 viewport: { x: 0, y: 0, width: viewport_size, height: viewport_size }643 },644 {645 eye: vrMojom.XREye.kRight,646 fieldOfView: {647 upDegrees: 48.316,648 downDegrees: 50.099,649 leftDegrees: 50.899,650 rightDegrees: 35.197651 },652 mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform({653 position: [0.032, 0, 0],654 orientation: [0, 0, 0, 1]655 })),656 viewport: { x: viewport_size, y: 0, width: viewport_size, height: viewport_size }657 }];658 }659 // This function converts between the matrix provided by the WebXR test API660 // and the internal data representation.661 _getView(fakeXRViewInit, xOffset) {662 let fov = null;663 if (fakeXRViewInit.fieldOfView) {664 fov = {665 upDegrees: fakeXRViewInit.fieldOfView.upDegrees,666 downDegrees: fakeXRViewInit.fieldOfView.downDegrees,667 leftDegrees: fakeXRViewInit.fieldOfView.leftDegrees,668 rightDegrees: fakeXRViewInit.fieldOfView.rightDegrees669 };670 } else {671 const m = fakeXRViewInit.projectionMatrix;672 function toDegrees(tan) {673 return Math.atan(tan) * 180 / Math.PI;674 }675 const leftTan = (1 - m[8]) / m[0];676 const rightTan = (1 + m[8]) / m[0];677 const upTan = (1 + m[9]) / m[5];678 const downTan = (1 - m[9]) / m[5];679 fov = {680 upDegrees: toDegrees(upTan),681 downDegrees: toDegrees(downTan),682 leftDegrees: toDegrees(leftTan),683 rightDegrees: toDegrees(rightTan)684 };685 }686 let viewEye = vrMojom.XREye.kNone;687 // The eye passed in corresponds to the values in the WebXR spec, which are688 // the strings "none", "left", and "right". They should be converted to the689 // corresponding values of XREye in vr_service.mojom.690 switch(fakeXRViewInit.eye) {691 case "none":692 viewEye = vrMojom.XREye.kNone;693 break;694 case "left":695 viewEye = vrMojom.XREye.kLeft;696 break;697 case "right":698 viewEye = vrMojom.XREye.kRight;699 break;700 }701 return {702 eye: viewEye,703 fieldOfView: fov,704 mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform(fakeXRViewInit.viewOffset)),705 viewport: {706 x: xOffset,707 y: 0,708 width: fakeXRViewInit.resolution.width,709 height: fakeXRViewInit.resolution.height710 },711 isFirstPersonObserver: fakeXRViewInit.isFirstPersonObserver ? true : false,712 viewOffset: composeGFXTransform(fakeXRViewInit.viewOffset)713 };714 }715 _setFeatures(supportedFeatures) {716 function convertFeatureToMojom(feature) {717 if (feature in MockRuntime._featureToMojoMap) {718 return MockRuntime._featureToMojoMap[feature];719 } else {720 return vrMojom.XRSessionFeature.INVALID;721 }722 }723 this.supportedFeatures_ = [];724 for (let i = 0; i < supportedFeatures.length; i++) {725 const feature = convertFeatureToMojom(supportedFeatures[i]);726 if (feature !== vrMojom.XRSessionFeature.INVALID) {727 this.supportedFeatures_.push(feature);728 }729 }730 }731 // These methods are intended to be used by MockXRInputSource only.732 _addInputSource(source) {733 if (!this.input_sources_.has(source.source_id_)) {734 this.input_sources_.set(source.source_id_, source);735 }736 }737 _removeInputSource(source) {738 this.input_sources_.delete(source.source_id_);739 }740 // These methods are intended to be used by FakeXRAnchorController only.741 _deleteAnchorController(controllerId) {742 this.anchor_controllers_.delete(controllerId);743 }744 // Extension point for non-standard modules.745 _injectAdditionalFrameData(options, frameData) {746 }747 // Mojo function implementations.748 // XRFrameDataProvider implementation.749 getFrameData(options) {750 return new Promise((resolve) => {751 const populatePose = () => {752 const mojo_space_reset = this.send_mojo_space_reset_;753 this.send_mojo_space_reset_ = false;754 if (this.pose_) {755 this.pose_.poseIndex++;756 }757 // Setting the input_state to null tests a slightly different path than758 // the browser tests where if the last input source is removed, the device759 // code always sends up an empty array, but it's also valid mojom to send760 // up a null array.761 let input_state = null;762 if (this.input_sources_.size > 0) {763 input_state = [];764 for (const input_source of this.input_sources_.values()) {765 input_state.push(input_source._getInputSourceState());766 }767 }768 let frame_views = this.primaryViews_;769 for (let i = 0; i < this.primaryViews_.length; i++) {770 this.primaryViews_[i].mojoFromView =771 this._getMojoFromViewerWithOffset(this.primaryViews_[i].viewOffset);772 }773 if (this.enabledFeatures_.includes(vrMojom.XRSessionFeature.SECONDARY_VIEWS)) {774 for (let i = 0; i < this.secondaryViews_.length; i++) {775 this.secondaryViews_[i].mojoFromView =776 this._getMojoFromViewerWithOffset(this.secondaryViews_[i].viewOffset);777 }778 frame_views = frame_views.concat(this.secondaryViews_);779 }780 const frameData = {781 mojoFromViewer: this.pose_,782 views: frame_views,783 mojoSpaceReset: mojo_space_reset,784 inputState: input_state,785 timeDelta: {786 // window.performance.now() is in milliseconds, so convert to microseconds.787 microseconds: BigInt(Math.floor(window.performance.now() * 1000)),788 },789 frameId: this.next_frame_id_,790 bufferHolder: null,791 cameraImageSize: this.cameraImage_ ? {792 width: this.cameraImage_.width,793 height: this.cameraImage_.height794 } : null,795 renderingTimeRatio: 0,796 stageParameters: this.stageParameters_,797 stageParametersId: this.stageParametersId_,798 lightEstimationData: this.light_estimate_799 };800 this.next_frame_id_++;801 this._calculateHitTestResults(frameData);802 this._calculateAnchorInformation(frameData);803 this._calculateDepthInformation(frameData);804 this._injectAdditionalFrameData(options, frameData);805 resolve({frameData});806 };807 if(this.sessionOptions_.mode == vrMojom.XRSessionMode.kInline) {808 // Inline sessions should not have a delay introduced since it causes them809 // to miss a vsync blink-side and delays propagation of changes that happened810 // within a rAFcb by one frame (e.g. setViewerOrigin() calls would take 2 frames811 // to propagate).812 populatePose();813 } else {814 // For immerive sessions, add additional delay to allow for anchor creation815 // promises to run.816 setTimeout(populatePose, 3); // note: according to MDN, the timeout is not exact817 }818 });819 }820 getEnvironmentIntegrationProvider(environmentProviderRequest) {821 if (this.environmentProviderReceiver_) {822 this.environmentProviderReceiver_.$.close();823 }824 this.environmentProviderReceiver_ =825 new vrMojom.XREnvironmentIntegrationProviderReceiver(this);826 this.environmentProviderReceiver_.$.bindHandle(827 environmentProviderRequest.handle);828 }829 setInputSourceButtonListener(listener) { listener.$.close(); }830 // XREnvironmentIntegrationProvider implementation:831 subscribeToHitTest(nativeOriginInformation, entityTypes, ray) {832 if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {833 // Reject outside of AR.834 return Promise.resolve({835 result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,836 subscriptionId : 0n837 });838 }839 if (!this._nativeOriginKnown(nativeOriginInformation)) {840 return Promise.resolve({841 result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,842 subscriptionId : 0n843 });844 }845 // Reserve the id for hit test source:846 const id = this.next_hit_test_id_++;847 const hitTestParameters = { isTransient: false, profileName: null };848 const controller = new FakeXRHitTestSourceController(id);849 return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)850 .then((succeeded) => {851 if(succeeded) {852 // Store the subscription information as-is (including controller):853 this.hitTestSubscriptions_.set(id, { nativeOriginInformation, entityTypes, ray, controller });854 return Promise.resolve({855 result : vrMojom.SubscribeToHitTestResult.SUCCESS,856 subscriptionId : id857 });858 } else {859 return Promise.resolve({860 result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,861 subscriptionId : 0n862 });863 }864 });865 }866 subscribeToHitTestForTransientInput(profileName, entityTypes, ray){867 if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {868 // Reject outside of AR.869 return Promise.resolve({870 result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,871 subscriptionId : 0n872 });873 }874 const id = this.next_hit_test_id_++;875 const hitTestParameters = { isTransient: true, profileName: profileName };876 const controller = new FakeXRHitTestSourceController(id);877 // Check if we have hit test source creation callback.878 // If yes, ask it if the hit test source creation should succeed.879 // If no, for back-compat, assume the hit test source creation succeeded.880 return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)881 .then((succeeded) => {882 if(succeeded) {883 // Store the subscription information as-is (including controller):884 this.transientHitTestSubscriptions_.set(id, { profileName, entityTypes, ray, controller });885 return Promise.resolve({886 result : vrMojom.SubscribeToHitTestResult.SUCCESS,887 subscriptionId : id888 });889 } else {890 return Promise.resolve({891 result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,892 subscriptionId : 0n893 });894 }895 });896 }897 unsubscribeFromHitTest(subscriptionId) {898 let controller = null;899 if(this.transientHitTestSubscriptions_.has(subscriptionId)){900 controller = this.transientHitTestSubscriptions_.get(subscriptionId).controller;901 this.transientHitTestSubscriptions_.delete(subscriptionId);902 } else if(this.hitTestSubscriptions_.has(subscriptionId)){903 controller = this.hitTestSubscriptions_.get(subscriptionId).controller;904 this.hitTestSubscriptions_.delete(subscriptionId);905 }906 if(controller) {907 controller.deleted = true;908 }909 }910 createAnchor(nativeOriginInformation, nativeOriginFromAnchor) {911 return new Promise((resolve) => {912 if(this.anchor_creation_callback_ == null) {913 resolve({914 result : vrMojom.CreateAnchorResult.FAILURE,915 anchorId : 0n916 });917 return;918 }919 const mojoFromNativeOrigin = this._getMojoFromNativeOrigin(nativeOriginInformation);920 if(mojoFromNativeOrigin == null) {921 resolve({922 result : vrMojom.CreateAnchorResult.FAILURE,923 anchorId : 0n924 });925 return;926 }927 const mojoFromAnchor = XRMathHelper.mul4x4(mojoFromNativeOrigin, nativeOriginFromAnchor);928 const anchorCreationParameters = {929 requestedAnchorOrigin: mojoFromAnchor,930 isAttachedToEntity: false,931 };932 const anchorController = new FakeXRAnchorController();933 this.anchor_creation_callback_(anchorCreationParameters, anchorController)934 .then((result) => {935 if(result) {936 // If the test allowed the anchor creation,937 // store the anchor controller & return success.938 const anchor_id = this.next_anchor_id_;939 this.next_anchor_id_++;940 this.anchor_controllers_.set(anchor_id, anchorController);941 anchorController.device = this;942 anchorController.id = anchor_id;943 resolve({944 result : vrMojom.CreateAnchorResult.SUCCESS,945 anchorId : anchor_id946 });947 } else {948 // The test has rejected anchor creation.949 resolve({950 result : vrMojom.CreateAnchorResult.FAILURE,951 anchorId : 0n952 });953 }954 })955 .catch(() => {956 // The test threw an error, treat anchor creation as failed.957 resolve({958 result : vrMojom.CreateAnchorResult.FAILURE,959 anchorId : 0n960 });961 });962 });963 }964 createPlaneAnchor(planeFromAnchor, planeId) {965 return new Promise((resolve) => {966 // Not supported yet.967 resolve({968 result : vrMojom.CreateAnchorResult.FAILURE,969 anchorId : 0n,970 });971 });972 }973 detachAnchor(anchorId) {}974 // Utility function975 _requestRuntimeSession(sessionOptions) {976 return this._runtimeSupportsSession(sessionOptions).then((result) => {977 // The JavaScript bindings convert c_style_names to camelCase names.978 const options = {979 transportMethod:980 vrMojom.XRPresentationTransportMethod.SUBMIT_AS_MAILBOX_HOLDER,981 waitForTransferNotification: true,982 waitForRenderNotification: true,983 waitForGpuFence: false,984 };985 let submit_frame_sink;986 if (result.supportsSession) {987 submit_frame_sink = {988 clientReceiver: this.presentation_provider_._getClientReceiver(),989 provider: this.presentation_provider_._bindProvider(sessionOptions),990 transportOptions: options991 };992 const dataProviderPtr = new vrMojom.XRFrameDataProviderRemote();993 this.dataProviderReceiver_ =994 new vrMojom.XRFrameDataProviderReceiver(this);995 this.dataProviderReceiver_.$.bindHandle(996 dataProviderPtr.$.bindNewPipeAndPassReceiver().handle);997 this.sessionOptions_ = sessionOptions;998 this.sessionClient_ = new vrMojom.XRSessionClientRemote();999 const clientReceiver = this.sessionClient_.$.bindNewPipeAndPassReceiver();1000 const enabled_features = [];1001 for (let i = 0; i < sessionOptions.requiredFeatures.length; i++) {1002 if (this.supportedFeatures_.indexOf(sessionOptions.requiredFeatures[i]) !== -1) {1003 enabled_features.push(sessionOptions.requiredFeatures[i]);1004 } else {1005 return Promise.resolve({session: null});1006 }1007 }1008 for (let i =0; i < sessionOptions.optionalFeatures.length; i++) {1009 if (this.supportedFeatures_.indexOf(sessionOptions.optionalFeatures[i]) !== -1) {1010 enabled_features.push(sessionOptions.optionalFeatures[i]);1011 }1012 }1013 this.enabledFeatures_ = enabled_features;1014 return Promise.resolve({1015 session: {1016 submitFrameSink: submit_frame_sink,1017 dataProvider: dataProviderPtr,1018 clientReceiver: clientReceiver,1019 enabledFeatures: enabled_features,1020 deviceConfig: {1021 usesInputEventing: false,1022 defaultFramebufferScale: this.defaultFramebufferScale_,1023 supportsViewportScaling: true,1024 depthConfiguration:1025 enabled_features.includes(vrMojom.XRSessionFeature.DEPTH) ? {1026 depthUsage: vrMojom.XRDepthUsage.kCPUOptimized,1027 depthDataFormat: vrMojom.XRDepthDataFormat.kLuminanceAlpha,1028 } : null,1029 views: this._getDefaultViews(),1030 },1031 enviromentBlendMode: this.enviromentBlendMode_,1032 interactionMode: this.interactionMode_1033 }1034 });1035 } else {1036 return Promise.resolve({session: null});1037 }1038 });1039 }1040 _runtimeSupportsSession(options) {1041 let result = this.supportedModes_.includes(options.mode);1042 if (options.requiredFeatures.includes(vrMojom.XRSessionFeature.DEPTH)1043 || options.optionalFeatures.includes(vrMojom.XRSessionFeature.DEPTH)) {1044 result &= options.depthOptions.usagePreferences.includes(vrMojom.XRDepthUsage.kCPUOptimized);1045 result &= options.depthOptions.dataFormatPreferences.includes(vrMojom.XRDepthDataFormat.kLuminanceAlpha);1046 }1047 return Promise.resolve({1048 supportsSession: result,1049 });1050 }1051 // Private functions - utilities:1052 _nativeOriginKnown(nativeOriginInformation){1053 if (nativeOriginInformation.inputSourceSpaceInfo !== undefined) {1054 if (!this.input_sources_.has(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId)) {1055 // Unknown input source.1056 return false;1057 }1058 return true;1059 } else if (nativeOriginInformation.referenceSpaceType !== undefined) {1060 // Bounded_floor & unbounded ref spaces are not yet supported for AR:1061 if (nativeOriginInformation.referenceSpaceType == vrMojom.XRReferenceSpaceType.kUnbounded1062 || nativeOriginInformation.referenceSpaceType == vrMojom.XRReferenceSpaceType.kBoundedFloor) {1063 return false;1064 }1065 return true;1066 } else {1067 // Planes and anchors are not yet supported by the mock interface.1068 return false;1069 }1070 }1071 // Private functions - anchors implementation:1072 // Modifies passed in frameData to add anchor information.1073 _calculateAnchorInformation(frameData) {1074 if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {1075 return;1076 }1077 frameData.anchorsData = {allAnchorsIds: [], updatedAnchorsData: []};1078 for(const [id, controller] of this.anchor_controllers_) {1079 frameData.anchorsData.allAnchorsIds.push(id);1080 // Send the entire anchor data over if there was a change since last GetFrameData().1081 if(controller.dirty) {1082 const anchorData = {id};1083 if(!controller.paused) {1084 anchorData.mojoFromAnchor = getPoseFromTransform(1085 XRMathHelper.decomposeRigidTransform(1086 controller._getAnchorOrigin()));1087 }1088 controller._markProcessed();1089 frameData.anchorsData.updatedAnchorsData.push(anchorData);1090 }1091 }1092 }1093 // Private functions - depth sensing implementation:1094 // Modifies passed in frameData to add anchor information.1095 _calculateDepthInformation(frameData) {1096 if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {1097 return;1098 }1099 if (!this.enabledFeatures_.includes(vrMojom.XRSessionFeature.DEPTH)) {1100 return;1101 }1102 // If we don't have a current depth data, we'll return null1103 // (i.e. no data is not a valid data, so it cannot be "StillValid").1104 if (this.depthSensingData_ == null) {1105 frameData.depthData = null;1106 return;1107 }1108 if(!this.depthSensingDataDirty_) {1109 frameData.depthData = { dataStillValid: {}};1110 return;1111 }1112 frameData.depthData = {1113 updatedDepthData: {1114 timeDelta: frameData.timeDelta,1115 normTextureFromNormView: this.depthSensingData_.normDepthBufferFromNormView,1116 rawValueToMeters: this.depthSensingData_.rawValueToMeters,1117 size: { width: this.depthSensingData_.width, height: this.depthSensingData_.height },1118 pixelData: { bytes: this.depthSensingData_.depthData }1119 }1120 };1121 this.depthSensingDataDirty_ = false;1122 }1123 // Private functions - hit test implementation:1124 // Returns a Promise<bool> that signifies whether hit test source creation should succeed.1125 // If we have a hit test source creation callback installed, invoke it and return its result.1126 // If it's not installed, for back-compat just return a promise that resolves to true.1127 _shouldHitTestSourceCreationSucceed(hitTestParameters, controller) {1128 if(this.hit_test_source_creation_callback_) {1129 return this.hit_test_source_creation_callback_(hitTestParameters, controller);1130 } else {1131 return Promise.resolve(true);1132 }1133 }1134 // Modifies passed in frameData to add hit test results.1135 _calculateHitTestResults(frameData) {1136 if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) {1137 return;1138 }1139 frameData.hitTestSubscriptionResults = {results: [],1140 transientInputResults: []};1141 if (!this.world_) {1142 return;1143 }1144 // Non-transient hit test:1145 for (const [id, subscription] of this.hitTestSubscriptions_) {1146 const mojo_from_native_origin = this._getMojoFromNativeOrigin(subscription.nativeOriginInformation);1147 if (!mojo_from_native_origin) continue;1148 const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(1149 subscription.ray,1150 mojo_from_native_origin1151 );1152 const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);1153 frameData.hitTestSubscriptionResults.results.push(1154 {subscriptionId: id, hitTestResults: results});1155 }1156 // Transient hit test:1157 const mojo_from_viewer = this._getMojoFromViewer();1158 for (const [id, subscription] of this.transientHitTestSubscriptions_) {1159 const result = {subscriptionId: id,1160 inputSourceIdToHitTestResults: new Map()};1161 // Find all input sources that match the profile name:1162 const matching_input_sources = Array.from(this.input_sources_.values())1163 .filter(input_source => input_source.profiles_.includes(subscription.profileName));1164 for (const input_source of matching_input_sources) {1165 const mojo_from_native_origin = input_source._getMojoFromInputSource(mojo_from_viewer);1166 const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(1167 subscription.ray,1168 mojo_from_native_origin1169 );1170 const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);1171 result.inputSourceIdToHitTestResults.set(input_source.source_id_, results);1172 }1173 frameData.hitTestSubscriptionResults.transientInputResults.push(result);1174 }1175 }1176 // Returns 2-element array [origin, direction] of a ray in mojo space.1177 // |ray| is expressed relative to native origin.1178 _transformRayToMojoSpace(ray, mojo_from_native_origin) {1179 const ray_origin = {1180 x: ray.origin.x,1181 y: ray.origin.y,1182 z: ray.origin.z,1183 w: 11184 };1185 const ray_direction = {1186 x: ray.direction.x,1187 y: ray.direction.y,1188 z: ray.direction.z,1189 w: 01190 };1191 const mojo_ray_origin = XRMathHelper.transform_by_matrix(1192 mojo_from_native_origin,1193 ray_origin);1194 const mojo_ray_direction = XRMathHelper.transform_by_matrix(1195 mojo_from_native_origin,1196 ray_direction);1197 return [mojo_ray_origin, mojo_ray_direction];1198 }1199 // Hit tests the passed in ray (expressed as origin and direction) against the mocked world data.1200 _hitTestWorld(origin, direction, entityTypes) {1201 let result = [];1202 for (const region of this.world_.hitTestRegions) {1203 const partial_result = this._hitTestRegion(1204 region,1205 origin, direction,1206 entityTypes);1207 result = result.concat(partial_result);1208 }1209 return result.sort((lhs, rhs) => lhs.distance - rhs.distance).map((hitTest) => {1210 delete hitTest.distance;1211 return hitTest;1212 });1213 }1214 // Hit tests the passed in ray (expressed as origin and direction) against world region.1215 // |entityTypes| is a set of FakeXRRegionTypes.1216 // |region| is FakeXRRegion.1217 // Returns array of XRHitResults, each entry will be decorated with the distance from the ray origin (along the ray).1218 _hitTestRegion(region, origin, direction, entityTypes) {1219 const regionNameToMojoEnum = {1220 "point": vrMojom.EntityTypeForHitTest.POINT,1221 "plane": vrMojom.EntityTypeForHitTest.PLANE,1222 "mesh":null1223 };1224 if (!entityTypes.includes(regionNameToMojoEnum[region.type])) {1225 return [];1226 }1227 const result = [];1228 for (const face of region.faces) {1229 const maybe_hit = this._hitTestFace(face, origin, direction);1230 if (maybe_hit) {1231 result.push(maybe_hit);1232 }1233 }1234 // The results should be sorted by distance and there should be no 2 entries with1235 // the same distance from ray origin - that would mean they are the same point.1236 // This situation is possible when a ray intersects the region through an edge shared1237 // by 2 faces.1238 return result.sort((lhs, rhs) => lhs.distance - rhs.distance)1239 .filter((val, index, array) => index === 0 || val.distance !== array[index - 1].distance);1240 }1241 // Hit tests the passed in ray (expressed as origin and direction) against a single face.1242 // |face|, |origin|, and |direction| are specified in world (aka mojo) coordinates.1243 // |face| is an array of DOMPointInits.1244 // Returns null if the face does not intersect with the ray, otherwise the result is1245 // an XRHitResult with matrix describing the pose of the intersection point.1246 _hitTestFace(face, origin, direction) {1247 const add = XRMathHelper.add;1248 const sub = XRMathHelper.sub;1249 const mul = XRMathHelper.mul;1250 const normalize = XRMathHelper.normalize;1251 const dot = XRMathHelper.dot;1252 const cross = XRMathHelper.cross;1253 const neg = XRMathHelper.neg;1254 //1. Calculate plane normal in world coordinates.1255 const point_A = face.vertices[0];1256 const point_B = face.vertices[1];1257 const point_C = face.vertices[2];1258 const edge_AB = sub(point_B, point_A);1259 const edge_AC = sub(point_C, point_A);1260 const normal = normalize(cross(edge_AB, edge_AC));1261 const numerator = dot(sub(point_A, origin), normal);1262 const denominator = dot(direction, normal);1263 if (Math.abs(denominator) < XRMathHelper.EPSILON) {1264 // Planes are nearly parallel - there's either infinitely many intersection points or 0.1265 // Both cases signify a "no hit" for us.1266 return null;1267 } else {1268 // Single intersection point between the infinite plane and the line (*not* ray).1269 // Need to calculate the hit test matrix taking into account the face vertices.1270 const distance = numerator / denominator;1271 if (distance < 0) {1272 // Line - plane intersection exists, but not the half-line - plane does not.1273 return null;1274 } else {1275 const intersection_point = add(origin, mul(distance, direction));1276 // Since we are treating the face as a solid, flip the normal so that its1277 // half-space will contain the ray origin.1278 const y_axis = denominator > 0 ? neg(normal) : normal;1279 let z_axis = null;1280 const cos_direction_and_y_axis = dot(direction, y_axis);1281 if (Math.abs(cos_direction_and_y_axis) > (1 - XRMathHelper.EPSILON)) {1282 // Ray and the hit test normal are co-linear - try using the 'up' or 'right' vector's projection on the face plane as the Z axis.1283 // Note: this edge case is currently not covered by the spec.1284 const up = {x: 0.0, y: 1.0, z: 0.0, w: 0.0};1285 const right = {x: 1.0, y: 0.0, z: 0.0, w: 0.0};1286 z_axis = Math.abs(dot(up, y_axis)) > (1 - XRMathHelper.EPSILON)1287 ? sub(up, mul(dot(right, y_axis), y_axis)) // `up is also co-linear with hit test normal, use `right`1288 : sub(up, mul(dot(up, y_axis), y_axis)); // `up` is not co-linear with hit test normal, use it1289 } else {1290 // Project the ray direction onto the plane, negate it and use as a Z axis.1291 z_axis = neg(sub(direction, mul(cos_direction_and_y_axis, y_axis))); // Z should point towards the ray origin, not away.1292 }1293 z_axis = normalize(z_axis);1294 const x_axis = normalize(cross(y_axis, z_axis));1295 // Filter out the points not in polygon.1296 if (!XRMathHelper.pointInFace(intersection_point, face)) {1297 return null;1298 }1299 const hitResult = {planeId: 0n};1300 hitResult.distance = distance; // Extend the object with additional information used by higher layers.1301 // It will not be serialized over mojom.1302 const matrix = new Array(16);1303 matrix[0] = x_axis.x;1304 matrix[1] = x_axis.y;1305 matrix[2] = x_axis.z;1306 matrix[3] = 0;1307 matrix[4] = y_axis.x;1308 matrix[5] = y_axis.y;1309 matrix[6] = y_axis.z;1310 matrix[7] = 0;1311 matrix[8] = z_axis.x;1312 matrix[9] = z_axis.y;1313 matrix[10] = z_axis.z;1314 matrix[11] = 0;1315 matrix[12] = intersection_point.x;1316 matrix[13] = intersection_point.y;1317 matrix[14] = intersection_point.z;1318 matrix[15] = 1;1319 hitResult.mojoFromResult = getPoseFromTransform(1320 XRMathHelper.decomposeRigidTransform(matrix));1321 return hitResult;1322 }1323 }1324 }1325 _getMojoFromViewer() {1326 if (!this.pose_) {1327 return XRMathHelper.identity();1328 }1329 const transform = {1330 position: [1331 this.pose_.position.x,1332 this.pose_.position.y,1333 this.pose_.position.z],1334 orientation: [1335 this.pose_.orientation.x,1336 this.pose_.orientation.y,1337 this.pose_.orientation.z,1338 this.pose_.orientation.w],1339 };1340 return getMatrixFromTransform(transform);1341 }1342 _getMojoFromViewerWithOffset(viewOffset) {1343 return { matrix: XRMathHelper.mul4x4(this._getMojoFromViewer(), viewOffset.matrix) };1344 }1345 _getMojoFromNativeOrigin(nativeOriginInformation) {1346 const mojo_from_viewer = this._getMojoFromViewer();1347 if (nativeOriginInformation.inputSourceSpaceInfo !== undefined) {1348 if (!this.input_sources_.has(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId)) {1349 return null;1350 } else {1351 const inputSource = this.input_sources_.get(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId);1352 return inputSource._getMojoFromInputSource(mojo_from_viewer);1353 }1354 } else if (nativeOriginInformation.referenceSpaceType !== undefined) {1355 switch (nativeOriginInformation.referenceSpaceType) {1356 case vrMojom.XRReferenceSpaceType.kLocal:1357 return XRMathHelper.identity();1358 case vrMojom.XRReferenceSpaceType.kLocalFloor:1359 if (this.stageParameters_ == null || this.stageParameters_.mojoFromFloor == null) {1360 console.warn("Standing transform not available.");1361 return null;1362 }1363 return this.stageParameters_.mojoFromFloor.matrix;1364 case vrMojom.XRReferenceSpaceType.kViewer:1365 return mojo_from_viewer;1366 case vrMojom.XRReferenceSpaceType.kBoundedFloor:1367 return null;1368 case vrMojom.XRReferenceSpaceType.kUnbounded:1369 return null;1370 default:1371 throw new TypeError("Unrecognized XRReferenceSpaceType!");1372 }1373 } else {1374 // Anchors & planes are not yet supported for hit test.1375 return null;1376 }1377 }1378}1379class MockXRInputSource {1380 constructor(fakeInputSourceInit, id, pairedDevice) {1381 this.source_id_ = id;1382 this.pairedDevice_ = pairedDevice;1383 this.handedness_ = fakeInputSourceInit.handedness;1384 this.target_ray_mode_ = fakeInputSourceInit.targetRayMode;1385 if (fakeInputSourceInit.pointerOrigin == null) {1386 throw new TypeError("FakeXRInputSourceInit.pointerOrigin is required.");1387 }1388 this.setPointerOrigin(fakeInputSourceInit.pointerOrigin);1389 this.setProfiles(fakeInputSourceInit.profiles);1390 this.primary_input_pressed_ = false;1391 if (fakeInputSourceInit.selectionStarted != null) {1392 this.primary_input_pressed_ = fakeInputSourceInit.selectionStarted;1393 }1394 this.primary_input_clicked_ = false;1395 if (fakeInputSourceInit.selectionClicked != null) {1396 this.primary_input_clicked_ = fakeInputSourceInit.selectionClicked;1397 }1398 this.primary_squeeze_pressed_ = false;1399 this.primary_squeeze_clicked_ = false;1400 this.mojo_from_input_ = null;1401 if (fakeInputSourceInit.gripOrigin != null) {1402 this.setGripOrigin(fakeInputSourceInit.gripOrigin);1403 }1404 // This properly handles if supportedButtons were not specified.1405 this.setSupportedButtons(fakeInputSourceInit.supportedButtons);1406 this.emulated_position_ = false;1407 this.desc_dirty_ = true;1408 }1409 // WebXR Test API1410 setHandedness(handedness) {1411 if (this.handedness_ != handedness) {1412 this.desc_dirty_ = true;1413 this.handedness_ = handedness;1414 }1415 }1416 setTargetRayMode(targetRayMode) {1417 if (this.target_ray_mode_ != targetRayMode) {1418 this.desc_dirty_ = true;1419 this.target_ray_mode_ = targetRayMode;1420 }1421 }1422 setProfiles(profiles) {1423 this.desc_dirty_ = true;1424 this.profiles_ = profiles;1425 }1426 setGripOrigin(transform, emulatedPosition = false) {1427 // grip_origin was renamed to mojo_from_input in mojo1428 this.mojo_from_input_ = composeGFXTransform(transform);1429 this.emulated_position_ = emulatedPosition;1430 // Technically, setting the grip shouldn't make the description dirty, but1431 // the webxr-test-api sets our pointer as mojoFromPointer; however, we only1432 // support it across mojom as inputFromPointer, so we need to recalculate it1433 // whenever the grip moves.1434 this.desc_dirty_ = true;1435 }1436 clearGripOrigin() {1437 // grip_origin was renamed to mojo_from_input in mojo1438 if (this.mojo_from_input_ != null) {1439 this.mojo_from_input_ = null;1440 this.emulated_position_ = false;1441 this.desc_dirty_ = true;1442 }1443 }1444 setPointerOrigin(transform, emulatedPosition = false) {1445 // pointer_origin is mojo_from_pointer.1446 this.desc_dirty_ = true;1447 this.mojo_from_pointer_ = composeGFXTransform(transform);1448 this.emulated_position_ = emulatedPosition;1449 }1450 disconnect() {1451 this.pairedDevice_._removeInputSource(this);1452 }1453 reconnect() {1454 this.pairedDevice_._addInputSource(this);1455 }1456 startSelection() {1457 this.primary_input_pressed_ = true;1458 if (this.gamepad_) {1459 this.gamepad_.buttons[0].pressed = true;1460 this.gamepad_.buttons[0].touched = true;1461 }1462 }1463 endSelection() {1464 if (!this.primary_input_pressed_) {1465 throw new Error("Attempted to end selection which was not started");1466 }1467 this.primary_input_pressed_ = false;1468 this.primary_input_clicked_ = true;1469 if (this.gamepad_) {1470 this.gamepad_.buttons[0].pressed = false;1471 this.gamepad_.buttons[0].touched = false;1472 }1473 }1474 simulateSelect() {1475 this.primary_input_clicked_ = true;1476 }1477 setSupportedButtons(supportedButtons) {1478 this.gamepad_ = null;1479 this.supported_buttons_ = [];1480 // If there are no supported buttons, we can stop now.1481 if (supportedButtons == null || supportedButtons.length < 1) {1482 return;1483 }1484 const supported_button_map = {};1485 this.gamepad_ = this._getEmptyGamepad();1486 for (let i = 0; i < supportedButtons.length; i++) {1487 const buttonType = supportedButtons[i].buttonType;1488 this.supported_buttons_.push(buttonType);1489 supported_button_map[buttonType] = supportedButtons[i];1490 }1491 // Let's start by building the button state in order of priority:1492 // Primary button is index 0.1493 this.gamepad_.buttons.push({1494 pressed: this.primary_input_pressed_,1495 touched: this.primary_input_pressed_,1496 value: this.primary_input_pressed_ ? 1.0 : 0.01497 });1498 // Now add the rest of our buttons1499 this._addGamepadButton(supported_button_map['grip']);1500 this._addGamepadButton(supported_button_map['touchpad']);1501 this._addGamepadButton(supported_button_map['thumbstick']);1502 this._addGamepadButton(supported_button_map['optional-button']);1503 this._addGamepadButton(supported_button_map['optional-thumbstick']);1504 // Finally, back-fill placeholder buttons/axes1505 for (let i = 0; i < this.gamepad_.buttons.length; i++) {1506 if (this.gamepad_.buttons[i] == null) {1507 this.gamepad_.buttons[i] = {1508 pressed: false,1509 touched: false,1510 value: 01511 };1512 }1513 }1514 for (let i=0; i < this.gamepad_.axes.length; i++) {1515 if (this.gamepad_.axes[i] == null) {1516 this.gamepad_.axes[i] = 0;1517 }1518 }1519 }1520 updateButtonState(buttonState) {1521 if (this.supported_buttons_.indexOf(buttonState.buttonType) == -1) {1522 throw new Error("Tried to update state on an unsupported button");1523 }1524 const buttonIndex = this._getButtonIndex(buttonState.buttonType);1525 const axesStartIndex = this._getAxesStartIndex(buttonState.buttonType);1526 if (buttonIndex == -1) {1527 throw new Error("Unknown Button Type!");1528 }1529 // is this a 'squeeze' button?1530 if (buttonIndex === this._getButtonIndex('grip')) {1531 // squeeze1532 if (buttonState.pressed) {1533 this.primary_squeeze_pressed_ = true;1534 } else if (this.gamepad_.buttons[buttonIndex].pressed) {1535 this.primary_squeeze_clicked_ = true;1536 this.primary_squeeze_pressed_ = false;1537 } else {1538 this.primary_squeeze_clicked_ = false;1539 this.primary_squeeze_pressed_ = false;1540 }1541 }1542 this.gamepad_.buttons[buttonIndex].pressed = buttonState.pressed;1543 this.gamepad_.buttons[buttonIndex].touched = buttonState.touched;1544 this.gamepad_.buttons[buttonIndex].value = buttonState.pressedValue;1545 if (axesStartIndex != -1) {1546 this.gamepad_.axes[axesStartIndex] = buttonState.xValue == null ? 0.0 : buttonState.xValue;1547 this.gamepad_.axes[axesStartIndex + 1] = buttonState.yValue == null ? 0.0 : buttonState.yValue;1548 }1549 }1550 // DOM Overlay Extensions1551 setOverlayPointerPosition(x, y) {1552 this.overlay_pointer_position_ = {x: x, y: y};1553 }1554 // Helpers for Mojom1555 _getInputSourceState() {1556 const input_state = {};1557 input_state.sourceId = this.source_id_;1558 input_state.isAuxiliary = false;1559 input_state.primaryInputPressed = this.primary_input_pressed_;1560 input_state.primaryInputClicked = this.primary_input_clicked_;1561 input_state.primarySqueezePressed = this.primary_squeeze_pressed_;1562 input_state.primarySqueezeClicked = this.primary_squeeze_clicked_;1563 // Setting the input source's "clicked" state should generate one "select"1564 // event. Reset the input value to prevent it from continuously generating1565 // events.1566 this.primary_input_clicked_ = false;1567 // Setting the input source's "clicked" state should generate one "squeeze"1568 // event. Reset the input value to prevent it from continuously generating1569 // events.1570 this.primary_squeeze_clicked_ = false;1571 input_state.mojoFromInput = this.mojo_from_input_;1572 input_state.gamepad = this.gamepad_;1573 input_state.emulatedPosition = this.emulated_position_;1574 if (this.desc_dirty_) {1575 const input_desc = {};1576 switch (this.target_ray_mode_) {1577 case 'gaze':1578 input_desc.targetRayMode = vrMojom.XRTargetRayMode.GAZING;1579 break;1580 case 'tracked-pointer':1581 input_desc.targetRayMode = vrMojom.XRTargetRayMode.POINTING;1582 break;1583 case 'screen':1584 input_desc.targetRayMode = vrMojom.XRTargetRayMode.TAPPING;1585 break;1586 default:1587 throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);1588 }1589 switch (this.handedness_) {1590 case 'left':1591 input_desc.handedness = vrMojom.XRHandedness.LEFT;1592 break;1593 case 'right':1594 input_desc.handedness = vrMojom.XRHandedness.RIGHT;1595 break;1596 default:1597 input_desc.handedness = vrMojom.XRHandedness.NONE;1598 break;1599 }1600 // Mojo requires us to send the pointerOrigin as relative to the grip1601 // space. If we don't have a grip space, we'll just assume that there1602 // is a grip at identity. This allows tests to simulate controllers that1603 // are really just a pointer with no tracked grip, though we will end up1604 // exposing that grip space.1605 let mojo_from_input = XRMathHelper.identity();1606 switch (this.target_ray_mode_) {1607 case 'gaze':1608 case 'screen':1609 // For gaze and screen space, we won't have a mojo_from_input; however1610 // the "input" position is just the viewer, so use mojo_from_viewer.1611 mojo_from_input = this.pairedDevice_._getMojoFromViewer();1612 break;1613 case 'tracked-pointer':1614 // If we have a tracked grip position (e.g. mojo_from_input), then use1615 // that. If we don't, then we'll just set the pointer offset directly,1616 // using identity as set above.1617 if (this.mojo_from_input_) {1618 mojo_from_input = this.mojo_from_input_.matrix;1619 }1620 break;1621 default:1622 throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);1623 }1624 // To convert mojo_from_pointer to input_from_pointer, we need:1625 // input_from_pointer = input_from_mojo * mojo_from_pointer1626 // Since we store mojo_from_input, we need to invert it here before1627 // multiplying.1628 let input_from_mojo = XRMathHelper.inverse(mojo_from_input);1629 input_desc.inputFromPointer = {};1630 input_desc.inputFromPointer.matrix =1631 XRMathHelper.mul4x4(input_from_mojo, this.mojo_from_pointer_.matrix);1632 input_desc.profiles = this.profiles_;1633 input_state.description = input_desc;1634 this.desc_dirty_ = false;1635 }1636 // Pointer data for DOM Overlay, set by setOverlayPointerPosition()1637 if (this.overlay_pointer_position_) {1638 input_state.overlayPointerPosition = this.overlay_pointer_position_;1639 this.overlay_pointer_position_ = null;1640 }1641 return input_state;1642 }1643 _getEmptyGamepad() {1644 // Mojo complains if some of the properties on Gamepad are null, so set1645 // everything to reasonable defaults that tests can override.1646 const gamepad = {1647 connected: true,1648 id: [],1649 timestamp: 0n,1650 axes: [],1651 buttons: [],1652 mapping: GamepadMapping.GamepadMappingStandard,1653 displayId: 0,1654 };1655 switch (this.handedness_) {1656 case 'left':1657 gamepad.hand = GamepadHand.GamepadHandLeft;1658 break;1659 case 'right':1660 gamepad.hand = GamepadHand.GamepadHandRight;1661 break;1662 default:1663 gamepad.hand = GamepadHand.GamepadHandNone;1664 break;1665 }1666 return gamepad;1667 }1668 _addGamepadButton(buttonState) {1669 if (buttonState == null) {1670 return;1671 }1672 const buttonIndex = this._getButtonIndex(buttonState.buttonType);1673 const axesStartIndex = this._getAxesStartIndex(buttonState.buttonType);1674 if (buttonIndex == -1) {1675 throw new Error("Unknown Button Type!");1676 }1677 this.gamepad_.buttons[buttonIndex] = {1678 pressed: buttonState.pressed,1679 touched: buttonState.touched,1680 value: buttonState.pressedValue1681 };1682 // Add x/y value if supported.1683 if (axesStartIndex != -1) {1684 this.gamepad_.axes[axesStartIndex] = (buttonState.xValue == null ? 0.0 : buttonSate.xValue);1685 this.gamepad_.axes[axesStartIndex + 1] = (buttonState.yValue == null ? 0.0 : buttonSate.yValue);1686 }1687 }1688 // General Helper methods1689 _getButtonIndex(buttonType) {1690 switch (buttonType) {1691 case 'grip':1692 return 1;1693 case 'touchpad':1694 return 2;1695 case 'thumbstick':1696 return 3;1697 case 'optional-button':1698 return 4;1699 case 'optional-thumbstick':1700 return 5;1701 default:1702 return -1;1703 }1704 }1705 _getAxesStartIndex(buttonType) {1706 switch (buttonType) {1707 case 'touchpad':1708 return 0;1709 case 'thumbstick':1710 return 2;1711 case 'optional-thumbstick':1712 return 4;1713 default:1714 return -1;1715 }1716 }1717 _getMojoFromInputSource(mojo_from_viewer) {1718 return this.mojo_from_pointer_.matrix;1719 }1720}1721// Mojo helper classes1722class FakeXRHitTestSourceController {1723 constructor(id) {1724 this.id_ = id;1725 this.deleted_ = false;1726 }1727 get deleted() {1728 return this.deleted_;1729 }1730 // Internal setter:1731 set deleted(value) {1732 this.deleted_ = value;1733 }1734}1735class MockXRPresentationProvider {1736 constructor() {1737 this.receiver_ = null;1738 this.submit_frame_count_ = 0;1739 this.missing_frame_count_ = 0;1740 }1741 _bindProvider() {1742 const provider = new vrMojom.XRPresentationProviderRemote();1743 if (this.receiver_) {1744 this.receiver_.$.close();1745 }1746 this.receiver_ = new vrMojom.XRPresentationProviderReceiver(this);1747 this.receiver_.$.bindHandle(provider.$.bindNewPipeAndPassReceiver().handle);1748 return provider;1749 }1750 _getClientReceiver() {1751 this.submitFrameClient_ = new vrMojom.XRPresentationClientRemote();1752 return this.submitFrameClient_.$.bindNewPipeAndPassReceiver();1753 }1754 // XRPresentationProvider mojo implementation1755 updateLayerBounds(frameId, leftBounds, rightBounds, sourceSize) {}1756 submitFrameMissing(frameId, mailboxHolder, timeWaited) {1757 this.missing_frame_count_++;1758 }1759 submitFrame(frameId, mailboxHolder, timeWaited) {1760 this.submit_frame_count_++;1761 // Trigger the submit completion callbacks here. WARNING: The1762 // Javascript-based mojo mocks are *not* re-entrant. It's OK to1763 // wait for these notifications on the next frame, but waiting1764 // within the current frame would never finish since the incoming1765 // calls would be queued until the current execution context finishes.1766 this.submitFrameClient_.onSubmitFrameTransferred(true);1767 this.submitFrameClient_.onSubmitFrameRendered();1768 }1769 submitFrameWithTextureHandle(frameId, texture, syncToken) {}1770 submitFrameDrawnIntoTexture(frameId, syncToken, timeWaited) {}1771 // Utility methods1772 _close() {1773 if (this.receiver_) {1774 this.receiver_.$.close();1775 }1776 }1777}1778// Export these into the global object as a side effect of importing this1779// module.1780self.ChromeXRTest = ChromeXRTest;1781self.MockRuntime = MockRuntime;1782self.MockVRService = MockVRService;1783self.SubscribeToHitTestResult = vrMojom.SubscribeToHitTestResult;...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptools = require('wptools');2var wp = new wptools('Eiffel Tower');3wp.viewWithCamera(function(err, resp) {4 if (err) {5 console.log(err);6 } else {7 console.log(resp);8 }9});

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptk = require('com.waynegraham.wptoolkit');2var win = Ti.UI.createWindow({3});4var view = wptk.viewWithCamera({5});6win.add(view);7win.open();

Full Screen

Using AI Code Generation

copy

Full Screen

1var win = Ti.UI.createWindow({2});3var button = Ti.UI.createButton({4});5button.addEventListener('click', function() {6 var wpt = require('com.waypoints.wptoolkit');7 wpt.viewWithCamera();8});9win.add(button);10win.open();11Copyright (c) 2014 Waypoints12Copyright (c) 2014 Waypoints

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptoolkit = require("wptoolkit");2var wp = new wptoolkit();3 if (err) {4 console.log("Error: " + err);5 }6 else {7 console.log("Image: " + res);8 }9});10var wptoolkit = require("wptoolkit");11var wp = new wptoolkit();12 if (err) {13 console.log("Error: " + err);14 }15 else {16 console.log("Image: " + res);17 }18});19var wptoolkit = require("wptoolkit");20var wp = new wptoolkit();21 if (err) {22 console.log("Error: " + err);23 }24 else {25 console.log("Image: " + res);26 }27});28var wptoolkit = require("wptoolkit");29var wp = new wptoolkit();30 if (err) {31 console.log("Error: " + err);32 }33 else {34 console.log("Image: " + res);35 }36});37var wptoolkit = require("wptoolkit");38var wp = new wptoolkit();39 if (err) {40 console.log("Error: " + err);41 }42 else {43 console.log("Image: " + res);44 }45});46var wptoolkit = require("wptoolkit");47var wp = new wptoolkit();

Full Screen

Using AI Code Generation

copy

Full Screen

1var wpt = require('wpt-api');2var options = {3};4wpt.viewWithCamera(options, function(err, data) {5 if (err) {6 console.log(err);7 } else {8 console.log(data);9 }10});11var _visualComplete = false;12var _visualCompleteTime = 0;13var _visualComplete85 = false;14var _visualComplete85Time = 0;15var _visualComplete95 = false;16var _visualComplete95Time = 0;17var _visualComplete99 = false;18var _visualComplete99Time = 0;19var _domElements = 0;20var _lastVisualChange = 0;21var _lastVisualChangeTime = 0;22var _visualChanges = 0;23var _lastPaint = 0;24var _lastPaintTime = 0;25var _paints = 0;26var _lastScroll = 0;27var _lastScrollTime = 0;28var _scrolls = 0;29var _lastDocumentComplete = 0;30var _lastDocumentCompleteTime = 0;31var _documentCompletes = 0;32var _lastFullyLoaded = 0;33var _lastFullyLoadedTime = 0;34var _fullyLoaded = 0;35var _lastSpeedIndex = 0;36var _lastSpeedIndexTime = 0;37var _speedIndex = 0;38var _lastVisualProgress = 0;39var _lastVisualProgressTime = 0;40var _visualProgress = 0;41var _lastVisualReadiness = 0;42var _lastVisualReadinessTime = 0;43var _visualReadiness = 0;44var _lastVisualReadiness85 = 0;

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptk = require("wptoolkit");2var canvas = document.createElement("canvas");3canvas.width = 640;4canvas.height = 480;5document.body.appendChild(canvas);6var ctx = canvas.getContext("2d");7var wptk = new wptk();8wptk.loadFaceDetectorModel("models");9wptk.loadFaceLandmarkModel("models");10var video = document.createElement("video");11video.width = 640;12video.height = 480;13document.body.appendChild(video);14navigator.mediaDevices.getUserMedia({ video: true, audio: false })15.then(function(stream) {16 video.srcObject = stream;17 video.play();18})19.catch(function(err) {20 console.log("An error occurred! " + err);21});22loop();23function loop() {24 requestAnimationFrame(loop);25 var face = wptk.detectFaceInVideo(video);26 if (face) {27 ctx.drawImage(video, 0, 0, video.width, video.height);28 ctx.strokeStyle = "red";29 ctx.strokeRect(face.x, face.y, face.width, face.height);30 }31}

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run wpt automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful