UNPKG

@google/model-viewer

Version:

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

180 lines 8.16 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 { Vector3 } from 'three'; import { AnnotationMixin } from '../../features/annotation'; import ModelViewerElementBase, { $loaded, $needsRender, $scene } from '../../model-viewer-base'; import { Hotspot } from '../../three-components/Hotspot.js'; import { timePasses, waitForEvent } from '../../utilities'; import { assetPath, rafPasses } from '../helpers'; import { BasicSpecTemplate } from '../templates'; const expect = chai.expect; const sceneContainsHotspot = (scene, element) => { const { children } = scene.target; for (let i = 0, l = children.length; i < l; i++) { const hotspot = children[i]; if (hotspot instanceof Hotspot && hotspot.element.children[0].children[0] .name === element.slot) { // expect it has been changed from default expect(hotspot.position).to.not.eql(new Vector3()); expect(hotspot.normal).to.not.eql(new Vector3(0, 1, 0)); return true; } } return false; }; const closeToVector3 = (a, b) => { const delta = 0.001; expect(a.x).to.be.closeTo(b.x, delta); expect(a.y).to.be.closeTo(b.y, delta); expect(a.z).to.be.closeTo(b.z, delta); }; suite('ModelViewerElementBase with AnnotationMixin', () => { let nextId = 0; let tagName; let ModelViewerElement; let element; let scene; setup(() => { tagName = `model-viewer-annotation-${nextId++}`; ModelViewerElement = class extends AnnotationMixin(ModelViewerElementBase) { static get is() { return tagName; } }; customElements.define(tagName, ModelViewerElement); element = new ModelViewerElement(); document.body.insertBefore(element, document.body.firstChild); scene = element[$scene]; }); teardown(() => { if (element.parentNode != null) { element.parentNode.removeChild(element); } }); BasicSpecTemplate(() => ModelViewerElement, () => tagName); suite('a model-viewer element with a hotspot', () => { let hotspot; let numSlots; setup(async () => { hotspot = document.createElement('div'); hotspot.setAttribute('slot', 'hotspot-1'); hotspot.setAttribute('data-position', '1m 1m 1m'); hotspot.setAttribute('data-normal', '0m 0m -1m'); element.appendChild(hotspot); await timePasses(); numSlots = scene.target.children.length; }); test('creates a corresponding slot', () => { expect(sceneContainsHotspot(scene, hotspot)).to.be.true; }); suite('adding a second hotspot with the same name', () => { let hotspot2; setup(async () => { hotspot2 = document.createElement('div'); hotspot2.setAttribute('slot', 'hotspot-1'); hotspot2.setAttribute('data-position', '0m 1m 2m'); hotspot2.setAttribute('data-normal', '1m 0m 0m'); element.appendChild(hotspot2); await timePasses(); }); test('does not change the slot', () => { expect(scene.target.children.length).to.be.equal(numSlots); }); test('does not change the data', () => { const { position, normal } = scene.target.children[numSlots - 1]; expect(position).to.be.deep.equal(new Vector3(1, 1, 1)); expect(normal).to.be.deep.equal(new Vector3(0, 0, -1)); }); test('updateHotspot does change the data', () => { element.updateHotspot({ name: 'hotspot-1', position: '0m 1m 2m', normal: '1m 0m 0m' }); const { position, normal } = scene.target.children[numSlots - 1]; expect(position).to.be.deep.equal(new Vector3(0, 1, 2)); expect(normal).to.be.deep.equal(new Vector3(1, 0, 0)); }); suite('with a camera', () => { let wrapper; setup(async () => { // This is to wait for the hotspots to be added to their slots, as // this triggers their visibility to "show". Otherwise, sometimes the // following hide() call will happen first, then when the camera // moves, we never get a hotspot-visibility event because they were // already visible. await rafPasses(); const hotspotObject2D = scene.target.children[numSlots - 1]; const camera = element[$scene].getCamera(); camera.position.z = 2; camera.updateMatrixWorld(); element[$loaded] = true; element[$needsRender](); await waitForEvent(hotspot2, 'hotspot-visibility'); wrapper = hotspotObject2D.element; }); test('the hotspot is hidden', async () => { expect(wrapper.classList.contains('hide')).to.be.true; }); test('the hotspot is visible after turning', async () => { element[$scene].yaw = Math.PI; element[$scene].updateMatrixWorld(); element[$needsRender](); await waitForEvent(hotspot2, 'hotspot-visibility'); expect(!!wrapper.classList.contains('hide')).to.be.false; }); }); test('and removing it does not remove the slot', async () => { element.removeChild(hotspot); await timePasses(); expect(scene.target.children.length).to.be.equal(numSlots); }); test('but removing both does remove the slot', async () => { element.removeChild(hotspot); element.removeChild(hotspot2); await timePasses(); expect(scene.target.children.length).to.be.equal(numSlots - 1); }); }); }); suite('a model-viewer element with a loaded cube', () => { let width = 0; let height = 0; setup(async () => { width = 200; height = 300; element.setAttribute('style', `width: ${width}px; height: ${height}px`); element.src = assetPath('models/cube.gltf'); const camera = element[$scene].getCamera(); camera.position.z = 2; camera.updateMatrixWorld(); await waitForEvent(element, 'load'); }); test('gets expected hit result', () => { const hitResult = element.positionAndNormalFromPoint(width / 2, height / 2); expect(hitResult).to.be.ok; const { position, normal } = hitResult; closeToVector3(position, new Vector3(0, 0, 0.5)); closeToVector3(normal, new Vector3(0, 0, 1)); }); test('gets expected hit result when turned', () => { element[$scene].yaw = -Math.PI / 2; element[$scene].updateMatrixWorld(); const hitResult = element.positionAndNormalFromPoint(width / 2, height / 2); expect(hitResult).to.be.ok; const { position, normal } = hitResult; closeToVector3(position, new Vector3(0.5, 0, 0)); closeToVector3(normal, new Vector3(1, 0, 0)); }); }); }); //# sourceMappingURL=annotation-spec.js.map