UNPKG

@google/model-viewer

Version:

Easily display interactive 3D models on the web and in AR!

236 lines 8.91 kB
/* @license * Copyright 2019 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { expect } from 'chai'; import { Matrix4, PerspectiveCamera, Vector2, Vector3 } from 'three'; import { $scene } from '../../model-viewer-base.js'; import { ModelViewerElement } from '../../model-viewer.js'; import { Renderer } from '../../three-components/Renderer.js'; import { waitForEvent } from '../../utilities.js'; import { assetPath } from '../helpers.js'; class MockXRFrame { constructor(session) { this.session = session; this.predictedDisplayTime = 0; } // We don't use nor test the returned XRPose other than its existence. getPose(_xrSpace, _frameOfRef) { return {}; } // We don't use getDepthInformation() getDepthInformation(_view) { return; } getViewerPose(_referenceSpace) { // Rotate 180 degrees on Y (so it's not the default) // and angle 45 degrees towards the ground, like a phone. const matrix = new Matrix4() .identity() .makeRotationAxis(new Vector3(0, 1, 0), Math.PI) .multiply(new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), -Math.PI / 4)); matrix.setPosition(10, 2, 3); const transform = { matrix: matrix.elements, position: {}, orientation: {}, inverse: {} }; const camera = new PerspectiveCamera(); const view = { eye: {}, projectionMatrix: camera.projectionMatrix.elements, transform: transform, requestViewportScale: (_scale) => { } }; const viewerPos = { transform: transform, views: [view], emulatedPosition: false }; return viewerPos; } getHitTestResults(_xrHitTestSource) { return []; } getHitTestResultsForTransientInput(_hitTestSource) { return []; } } suite('ARRenderer', () => { let element; let arRenderer; let xrSession; let inputSources = []; const setInputSources = (sources) => { inputSources = sources; }; const stubWebXrInterface = (arRenderer) => { arRenderer.resolveARSession = async () => { class FakeSession extends EventTarget { constructor() { super(...arguments); this.renderState = { baseLayer: { getViewport: () => { return { x: 0, y: 0, width: 320, height: 240 }; } } }; this.hitTestSources = new Set(); this.environmentBlendMode = {}; this.visibilityState = {}; this.isSystemKeyboardSupported = false; } async updateRenderState(_object) { } requestFrameOfReference() { return {}; } async requestReferenceSpace(_type) { return {}; } get inputSources() { return inputSources; } async requestHitTestSource(_options) { const result = { cancel: () => { } }; this.hitTestSources.add(result); return result; } async requestHitTestSourceForTransientInput(_options) { const result = { cancel: () => { } }; this.hitTestSources.add(result); return result; } requestAnimationFrame() { return 1; } cancelAnimationFrame() { } async end() { this.dispatchEvent(new CustomEvent('end')); } async updateTargetFrameRate(_rate) { return; } onend() { } oninputsourceschange() { } onselect() { } onselectstart() { } onselectend() { } onsqueeze() { } onsqueezestart() { } onsqueezeend() { } onvisibilitychange() { } onframeratechange() { } } xrSession = new FakeSession(); return xrSession; }; }; setup(() => { element = new ModelViewerElement(); document.body.insertBefore(element, document.body.firstChild); arRenderer = Renderer.singleton.arRenderer; }); teardown(() => { if (element.parentNode != null) { element.parentNode.removeChild(element); } }); test('is not presenting if present has not been invoked', () => { expect(arRenderer.isPresenting).to.be.equal(false); }); suite('when presenting a scene', () => { let modelScene; let oldXRRay; setup(async () => { const sourceLoads = waitForEvent(element, 'poster-dismissed'); element.src = assetPath('models/Astronaut.glb'); await sourceLoads; modelScene = element[$scene]; stubWebXrInterface(arRenderer); setInputSources([]); oldXRRay = window.XRRay; window.XRRay = class MockXRRay { constructor(_origin, _direction) { this.origin = new DOMPointReadOnly; this.direction = new DOMPointReadOnly; this.matrix = new Float32Array; } }; await arRenderer.present(modelScene); }); teardown(async () => { window.XRRay = oldXRRay; await arRenderer.stopPresenting().catch(() => { }); }); test('presents the model at its natural scale', () => { const scale = modelScene.target.getWorldScale(new Vector3()); expect(scale.x).to.be.equal(1); expect(scale.y).to.be.equal(1); expect(scale.z).to.be.equal(1); }); suite('presentation ends', () => { setup(async () => { await arRenderer.stopPresenting(); }); test('restores the model to its natural scale', () => { const scale = modelScene.target.getWorldScale(new Vector3()); expect(scale.x).to.be.equal(1); expect(scale.y).to.be.equal(1); expect(scale.z).to.be.equal(1); }); test('restores original camera', () => { expect(modelScene.camera).to.be.equal(modelScene.camera); }); test('restores scene size', () => { expect(modelScene.width).to.be.equal(300); expect(modelScene.height).to.be.equal(150); }); }); // We're going to need to mock out XRFrame more so it can set the camera // in order to properly test this. suite('after initial placement', () => { let yaw; setup(async () => { arRenderer.onWebXRFrame(0, new MockXRFrame(arRenderer.currentSession)); yaw = modelScene.yaw; }); test('places the model oriented to the camera', () => { const epsilon = 0.0001; const { target, position, camera } = modelScene; const cameraPosition = camera.position; const cameraToHit = new Vector2(position.x - cameraPosition.x, position.z - cameraPosition.z); const forward = target.getWorldDirection(new Vector3()); const forwardProjection = new Vector2(forward.x, forward.z); expect(forward.y).to.be.equal(0); expect(cameraToHit.cross(forwardProjection)).to.be.closeTo(0, epsilon); expect(cameraToHit.dot(forwardProjection)).to.be.lessThan(0); expect(modelScene.yaw).to.be.equal(yaw); }); }); }); }); //# sourceMappingURL=ARRenderer-spec.js.map