@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
223 lines • 10.7 kB
JavaScript
/* @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 { Vector3 } from 'three';
import { $needsRender, $scene, toVector3D } from '../../model-viewer-base.js';
import { ModelViewerElement } from '../../model-viewer.js';
import { Hotspot } from '../../three-components/Hotspot.js';
import { timePasses, waitForEvent } from '../../utilities.js';
import { assetPath, rafPasses } from '../helpers.js';
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);
};
const withinRange = (a, start, finish) => {
expect(a.u).to.be.within(start, finish);
expect(a.v).to.be.within(start, finish);
};
suite('Annotation', () => {
let element;
let scene;
setup(async () => {
element = new ModelViewerElement();
document.body.insertBefore(element, document.body.firstChild);
scene = element[$scene];
element.src = assetPath('models/cube.gltf');
await waitForEvent(element, 'poster-dismissed');
});
teardown(() => {
if (element.parentNode != null) {
element.parentNode.removeChild(element);
}
});
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;
});
teardown(() => {
if (hotspot.parentElement === element) {
element.removeChild(hotspot);
}
});
test('creates a corresponding slot', () => {
expect(sceneContainsHotspot(scene, hotspot)).to.be.true;
});
test.skip('querying it returns valid data', () => {
// to test querying, place hotspot in the center and verify the screen
// position is half the default width and height (300 x 150) with a depth
// value of ~1.
const defaultDimensions = { width: 300, height: 150 };
element.updateHotspot({ name: 'hotspot-1', position: `0m 0m 0m` });
const hotspotData = element.queryHotspot('hotspot-1');
expect(hotspotData === null || hotspotData === void 0 ? void 0 : hotspotData.canvasPosition.x)
.to.be.closeTo(defaultDimensions.width / 2, 0.0001);
expect(hotspotData === null || hotspotData === void 0 ? void 0 : hotspotData.canvasPosition.y)
.to.be.closeTo(defaultDimensions.height / 2, 0.0001);
expect(hotspotData === null || hotspotData === void 0 ? void 0 : hotspotData.position.toString())
.to.equal(toVector3D(new Vector3(0, 0, 0)).toString());
expect(hotspotData === null || hotspotData === void 0 ? void 0 : hotspotData.normal.toString())
.to.equal(toVector3D(new Vector3(0, 0, -1)).toString());
expect(hotspotData === null || hotspotData === void 0 ? void 0 : hotspotData.facingCamera).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();
});
teardown(() => {
if (hotspot2.parentElement === element) {
element.removeChild(hotspot2);
}
});
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));
});
test('updateHotspot does change the surface', () => {
const hotspot = scene.target.children[numSlots - 1];
const { x } = hotspot.position;
const surface = '0 0 1 2 3 0.217 0.341 0.442';
element.updateHotspot({ name: 'hotspot-1', surface });
expect(x).to.not.be.equal(hotspot.position.x);
});
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('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();
wrapper = scene.target.children[numSlots - 1].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;
});
});
});
});
suite('a model-viewer element with a loaded cube', () => {
let rect;
setup(async () => {
element.setAttribute('style', `width: 200px; height: 300px`);
rect = element.getBoundingClientRect();
element.cameraOrbit = '0deg 90deg 2m';
element.jumpCameraToGoal();
await rafPasses();
});
test('gets expected hit result', async () => {
await rafPasses();
const hitResult = element.positionAndNormalFromPoint(rect.width / 2 + rect.x, rect.height / 2 + rect.y);
expect(hitResult).to.be.ok;
const { position, normal, uv } = hitResult;
closeToVector3(position, new Vector3(0, 0, 0.5));
closeToVector3(normal, new Vector3(0, 0, 1));
if (uv != null) {
withinRange(uv, 0, 1);
}
});
test('gets expected hit result when turned', async () => {
element.resetTurntableRotation(-Math.PI / 2);
await rafPasses();
const hitResult = element.positionAndNormalFromPoint(rect.width / 2 + rect.x, rect.height / 2 + rect.y);
expect(hitResult).to.be.ok;
const { position, normal, uv } = hitResult;
closeToVector3(position, new Vector3(0.5, 0, 0));
closeToVector3(normal, new Vector3(1, 0, 0));
if (uv != null) {
withinRange(uv, 0, 1);
}
});
test('returns a surface that shows and hides appropriately', async () => {
await rafPasses();
const surface = element.surfaceFromPoint(rect.width / 2 + rect.x, rect.height / 2 + rect.y);
expect(surface).to.be.ok;
const hotspot = document.createElement('div');
hotspot.setAttribute('slot', 'hotspot-1');
hotspot.setAttribute('data-surface', surface);
element.appendChild(hotspot);
await rafPasses();
expect(sceneContainsHotspot(scene, hotspot)).to.be.true;
const numSlots = scene.target.children.length;
const wrapper = scene.target.children[numSlots - 1].element;
expect(wrapper.classList.contains('hide')).to.be.false;
element[$scene].yaw = Math.PI;
element[$scene].updateMatrixWorld();
element[$needsRender]();
await waitForEvent(hotspot, 'hotspot-visibility');
expect(wrapper.classList.contains('hide')).to.be.true;
});
});
});
//# sourceMappingURL=annotation-spec.js.map