Skip to content

Commit

Permalink
Implement align-model attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Joel authored and cdata committed Mar 24, 2019
1 parent 0cb0834 commit 7965d9e
Show file tree
Hide file tree
Showing 19 changed files with 460 additions and 165 deletions.
Binary file added examples/assets/odd-shape.glb
Binary file not shown.
53 changes: 0 additions & 53 deletions src/features/auto-rotate.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/features/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export const EnvironmentMixin = (ModelViewerElement) => {
}

[$updateToneMapping]() {
this[$renderer].exposure = this.exposure;
this[$scene].exposure = this.exposure;
this[$needsRender]();
}

Expand Down
107 changes: 107 additions & 0 deletions src/features/staging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

/*
* Copyright 2019 Google Inc. 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 {property} from 'lit-element';

import ModelViewerElementBase, {$needsRender, $scene, $tick} from '../model-viewer-base.js';
import {Constructor} from '../utils.js';

const Alignment = {
CENTER: 'center',
ORIGIN: 'origin'
};

// How much the model will rotate per
// second in radians:
const ROTATION_SPEED = Math.PI / 32;

const UNBOUNDED_WHITESPACE_RE = /\s+/;

const alignmentToMaskValues = (alignmentString: string) => {
const alignments = alignmentString.split(UNBOUNDED_WHITESPACE_RE);
const maskValues = [];
let firstAlignment;

for (let i = 0; i < 3; ++i) {
const alignment = alignments[i];

if (alignment != null && firstAlignment == null) {
firstAlignment = alignment;
}

switch (alignment || firstAlignment) {
default:
case Alignment.CENTER:
maskValues.push(1.0);
break;
case Alignment.ORIGIN:
maskValues.push(0.0);
break;
}
}

return maskValues;
};

const $updateAlignment = Symbol('updateAlignment');

export const StagingMixin = (ModelViewerElement:
Constructor<ModelViewerElementBase>):
Constructor<ModelViewerElementBase> => {
class StagingModelViewerElement extends ModelViewerElement {
@property({type: Boolean, attribute: 'auto-rotate'})
autoRotate: boolean = false;

@property({type: String, attribute: 'align-model'})
alignModel: string = 'center';

updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);

if (changedProperties.has('alignModel')) {
this[$updateAlignment]();
}

if (changedProperties.has('autoRotate')) {
(this as any)[$scene].pivot.rotation.set(0, 0, 0);
this[$needsRender]();
}
}

[$tick](time: number, delta: number) {
super[$tick](time, delta);

if (this.autoRotate) {
(this as any)[$scene].pivot.rotation.y +=
ROTATION_SPEED * delta * 0.001;
this[$needsRender]();
}
}

[$updateAlignment]() {
const {alignModel} = this;
const alignmentMaskValues = alignmentToMaskValues(alignModel);

(this as any)[$scene].setModelAlignmentMask(...alignmentMaskValues);
}

get turntableRotation(): number {
return (this as any)[$scene].pivot.rotation.y;
}
}

return StagingModelViewerElement;
};
5 changes: 3 additions & 2 deletions src/model-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@

import {AnimationMixin} from './features/animation.js';
import {ARMixin} from './features/ar.js';
import {AutoRotateMixin} from './features/auto-rotate.js';
import {ControlsMixin} from './features/controls.js';
import {EnvironmentMixin} from './features/environment.js';
import {LoadingMixin} from './features/loading.js';
import {MagicLeapMixin} from './features/magic-leap.js';
import {StagingMixin} from './features/staging.js';
import ModelViewerElementBase from './model-viewer-base.js';

const ModelViewerElement = [
AnimationMixin,
LoadingMixin,
ARMixin,
AutoRotateMixin,
StagingMixin,
ControlsMixin,
EnvironmentMixin,
StagingMixin,
MagicLeapMixin
].reduce((Base, Mixin) => Mixin(Base), ModelViewerElementBase);

Expand Down
38 changes: 21 additions & 17 deletions src/test/features/environment-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const expect = chai.expect;
const ALT_BG_IMAGE_URL = assetPath('quick_4k.png');
const BG_IMAGE_URL = assetPath('spruit_sunrise_2k.jpg');
const MODEL_URL = assetPath('reflective-sphere.gltf');
const UNLIT_MODEL_URL = assetPath('glTF-Sample-Models/2.0/UnlitTest/glTF-Binary/UnlitTest.glb');
const UNLIT_MODEL_URL =
assetPath('glTF-Sample-Models/2.0/UnlitTest/glTF-Binary/UnlitTest.glb');
const MULTI_MATERIAL_MODEL_URL = assetPath('Triangle.gltf');

const backgroundHasMap =
Expand Down Expand Up @@ -61,18 +62,18 @@ const modelUsingEnvMap = (scene, meta) => {
return found;
};

const modelHasEnvMap = (scene) => {
let found = false;
scene.model.traverse(object => {
if (Array.isArray(object.material)) {
found = found || object.material.some(m => m.envMap);
}
else if (object.material && object.material.envMap) {
found = true;
const modelHasEnvMap =
(scene) => {
let found = false;
scene.model.traverse(object => {
if (Array.isArray(object.material)) {
found = found || object.material.some(m => m.envMap);
} else if (object.material && object.material.envMap) {
found = true;
}
});
return found;
}
});
return found;
}

/**
* Takes a model object and a meta object and returns
Expand Down Expand Up @@ -197,11 +198,13 @@ suite('ModelViewerElementBase with EnvironmentMixin', () => {
element.src = MULTI_MATERIAL_MODEL_URL;
await onLoad;
});
test('applies environment map on model with multi-material meshes', async function() {
expect(modelUsingEnvMap(scene, {
url: element.backgroundImage
})).to.be.ok;
});
test(
'applies environment map on model with multi-material meshes',
async function() {
expect(modelUsingEnvMap(scene, {
url: element.backgroundImage
})).to.be.ok;
});
});
});
});
Expand Down Expand Up @@ -275,6 +278,7 @@ suite('ModelViewerElementBase with EnvironmentMixin', () => {
scene.renderer.renderer.toneMappingExposure;
element.exposure = 2.0;
await timePasses();
scene.renderer.render(performance.now());
const newToneMappingExposure =
scene.renderer.renderer.toneMappingExposure;
expect(newToneMappingExposure)
Expand Down
119 changes: 119 additions & 0 deletions src/test/features/staging-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2019 Google Inc. 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 {StagingMixin} from '../../features/staging.js';
import ModelViewerElementBase, {$scene} from '../../model-viewer-base.js';
import {assetPath, timePasses, waitForEvent} from '../helpers.js';
import {BasicSpecTemplate} from '../templates.js';

const expect = chai.expect;

const ODD_SHAPE_GLB_PATH = assetPath('odd-shape.glb');
const CENTER_OFFSET = new Vector3(0.5, -1.25, 0.5);
const ORIGIN_OFFSET = new Vector3();

suite('ModelViewerElementBase with StagingMixin', () => {
let nextId = 0;
let tagName: string;
let ModelViewerElement: any;
let element: any;

setup(() => {
tagName = `model-viewer-staging-${nextId++}`;
ModelViewerElement = class extends StagingMixin
(ModelViewerElementBase) {
static get is() {
return tagName;
}
};
customElements.define(tagName, ModelViewerElement);
});

BasicSpecTemplate(() => ModelViewerElement, () => tagName);

suite('with a loaded model', () => {
setup(async () => {
element = new ModelViewerElement();
element.src = ODD_SHAPE_GLB_PATH;
document.body.appendChild(element);

await waitForEvent(element, 'load');
});

teardown(() => {
document.body.removeChild(element);
});

suite('align-model', () => {
test('centers the model in the frame by default', () => {
const offset = (element as any)[$scene].unscaledModelOffset;
expect(offset).to.be.deep.equal(CENTER_OFFSET);
});

suite('origin alignment', () => {
setup(async () => {
element.alignModel = 'origin';
await timePasses();
});

test('aligns model origin with the scene origin', () => {
// NOTE(cdata): We clone the offset as a cheap way of normalizing
// -0 values as 0
const offset = (element as any)[$scene].unscaledModelOffset.clone();
expect(offset).to.be.deep.equal(ORIGIN_OFFSET);
});
});

suite('mixed values', () => {
suite('two values', () => {
test('aligns x and y axes accordingly', async () => {
element.alignModel = 'center origin';
await timePasses();

const offset = (element as any)[$scene].unscaledModelOffset.clone();
expect(offset).to.be.deep.equal(
new Vector3(CENTER_OFFSET.x, ORIGIN_OFFSET.y, CENTER_OFFSET.z));
});
});

suite('three values', () => {
test('aligns x, y and z axes accordingly', async () => {
element.alignModel = 'origin center origin';
await timePasses();

const offset = (element as any)[$scene].unscaledModelOffset.clone();
expect(offset).to.be.deep.equal(
new Vector3(ORIGIN_OFFSET.x, CENTER_OFFSET.y, ORIGIN_OFFSET.z));
});
});
});
});

suite('auto-rotate', () => {
setup(() => {
element.autoRotate = true;
});

test('causes the model to rotate over time', async () => {
const {turntableRotation} = element;
await timePasses(50); // An arbitrary amount of time, greater than one
// rAF though
expect(element.turntableRotation).to.be.greaterThan(turntableRotation);
});
});
});
});
2 changes: 2 additions & 0 deletions src/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import './three-components/ARRenderer-spec.js';
import './three-components/SmoothControls-spec.js';
import './three-components/TextureUtils-spec.js';
import './three-components/CachingGLTFLoader-spec.js';
import './three-components/ModelUtils-spec.js';
import './features/animation-spec.js';
import './features/staging-spec.js';
import './features/controls-spec.js';
import './features/environment-spec.js';
import './features/loading-spec.js';
Expand Down
Loading

0 comments on commit 7965d9e

Please sign in to comment.