Skip to content

Commit

Permalink
Reduce to unique materials (google#1072)
Browse files Browse the repository at this point in the history
* Express material names in the 3DOM scene graph

* Remove vestigial logging, stricter test

* Refactor GLTF preparation and cloning

* Get Constructor from utilities

* Remove vestigial comments

* Fix GLTFInstance preparation in IE11

* Remove vestigial debug code

* Lightly refactor test suite
  • Loading branch information
Christopher Joel authored Mar 4, 2020
1 parent ae70902 commit 31664d7
Show file tree
Hide file tree
Showing 13 changed files with 621 additions and 276 deletions.
3 changes: 2 additions & 1 deletion packages/model-viewer/src/features/loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {property} from 'lit-element';

import ModelViewerElementBase, {$ariaLabel, $canvas, $getLoaded, $getModelIsVisible, $isInRenderTree, $progressTracker, $updateSource} from '../model-viewer-base.js';
import {$loader, CachingGLTFLoader} from '../three-components/CachingGLTFLoader.js';
import {ModelViewerGLTFInstance} from '../three-components/gltf-instance/ModelViewerGLTFInstance.js';
import {Constructor, debounce, deserializeUrl, throttle} from '../utilities.js';

import {LoadingStatusAnnouncer} from './loading/status-announcer.js';
Expand Down Expand Up @@ -44,7 +45,7 @@ const PosterDismissalSource: {[index: string]: DismissalSource} = {
INTERACTION: 'interaction'
};

const loader = new CachingGLTFLoader();
const loader = new CachingGLTFLoader(ModelViewerGLTFInstance);
const loadingStatusAnnouncer = new LoadingStatusAnnouncer();

export const $defaultProgressBarElement = Symbol('defaultProgressBarElement');
Expand Down
19 changes: 17 additions & 2 deletions packages/model-viewer/src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {EventDispatcher, Texture} from 'three';
import {EventDispatcher, Scene, Texture} from 'three';
import {GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader.js';

import {ExpressionNode, ExpressionTerm, FunctionNode, HexNode, IdentNode, Operator, OperatorNode} from '../styles/parsers.js';
import {deserializeUrl} from '../utilities.js';
Expand Down Expand Up @@ -206,4 +207,18 @@ export const operatorNode = (value: Operator): OperatorNode =>

export const functionNode =
(name: string, args: Array<ExpressionNode>): FunctionNode =>
({type: 'function', name: identNode(name), arguments: args});
({type: 'function', name: identNode(name), arguments: args});

export const createFakeGLTF = () => {
const scene = new Scene();

return {
animations: [],
scene,
scenes: [scene],
cameras: [],
asset: {},
parser: {} as unknown as GLTFParser,
userData: {}
};
};
2 changes: 2 additions & 0 deletions packages/model-viewer/src/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import './model-viewer-base-spec.js';
import './three-components/ModelScene-spec.js';
import './three-components/Renderer-spec.js';
import './three-components/ARRenderer-spec.js';
import './three-components/GLTFInstance-spec.js';
import './three-components/gltf-instance/ModelViewerGLTFInstance-spec.js';
import './three-components/SmoothControls-spec.js';
import './three-components/TextureUtils-spec.js';
import './three-components/CachingGLTFLoader-spec.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {$evictionPolicy, $releaseFromCache, CachingGLTFLoader} from '../../three-components/CachingGLTFLoader.js';

import {$evictionPolicy, CachingGLTFLoader} from '../../three-components/CachingGLTFLoader.js';
import {ModelViewerGLTFInstance} from '../../three-components/gltf-instance/ModelViewerGLTFInstance.js';
import {assetPath} from '../helpers.js';

const expect = chai.expect;
Expand All @@ -24,7 +26,7 @@ suite('CachingGLTFLoader', () => {
let loader: CachingGLTFLoader;

setup(() => {
loader = new CachingGLTFLoader();
loader = new CachingGLTFLoader(ModelViewerGLTFInstance);
});

teardown(() => {
Expand Down Expand Up @@ -61,7 +63,7 @@ suite('CachingGLTFLoader', () => {
});

test('yields a promise that resolves a scene', async () => {
const scene = await loader.load(ASTRONAUT_GLB_PATH);
const {scene} = await loader.load(ASTRONAUT_GLB_PATH);
expect(scene).to.be.ok;
expect(scene!.type).to.be.equal('Scene');
});
Expand All @@ -79,10 +81,10 @@ suite('CachingGLTFLoader', () => {
});

test('deletinates them when they are fully released', async () => {
const scene = await loader.load(ASTRONAUT_GLB_PATH);
const gltf = await loader.load(ASTRONAUT_GLB_PATH);

expect(CachingGLTFLoader.has(ASTRONAUT_GLB_PATH)).to.be.true;
scene![$releaseFromCache]();
gltf.dispose();
expect(CachingGLTFLoader.has(ASTRONAUT_GLB_PATH)).to.be.false;
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* @license
* Copyright 2020 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 {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js';

import {$prepared, GLTFInstance, PreparedGLTF} from '../../three-components/GLTFInstance.js';
import {createFakeGLTF} from '../helpers.js';


const expect = chai.expect;

suite('GLTFInstance', () => {
let rawGLTF: GLTF;
let preparedGLTF: PreparedGLTF;

setup(async () => {
rawGLTF = createFakeGLTF();
preparedGLTF = GLTFInstance.prepare(rawGLTF);
});

suite('with a prepared GLTF', () => {
test('exposes the same scene as the GLTF', () => {
const gltfInstance = new GLTFInstance(preparedGLTF);
expect(gltfInstance.scene).to.be.equal(preparedGLTF.scene);
});

suite('when cloned', () => {
test('creates a unique scene', () => {
const gltfInstance = new GLTFInstance(preparedGLTF);
const cloneInstance = gltfInstance.clone();

expect(cloneInstance.scene).to.be.ok;
expect(cloneInstance.scene).to.not.be.equal(gltfInstance.scene);
});
});
});

suite('preparing the GLTF', () => {
test('creates a prepared GLTF', () => {
expect(preparedGLTF).to.not.be.equal(rawGLTF);
expect(preparedGLTF[$prepared]).to.be.equal(true);
});
});
});
34 changes: 2 additions & 32 deletions packages/model-viewer/src/test/three-components/ModelUtils-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Box3, Material, Scene, Vector3} from 'three';
import {Box3, Vector3} from 'three';
import {GLTF, GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';

import {loadWithLoader} from '../../three-components/CachingGLTFLoader.js';
import {cloneGltf, reduceVertices} from '../../three-components/ModelUtils.js';
import {reduceVertices} from '../../three-components/ModelUtils.js';
import {assetPath} from '../helpers.js';

const expect = chai.expect;

const ASTRONAUT_GLB_PATH = assetPath('models/Astronaut.glb');



const collectMaterials = (scene: Scene): Array<Material> => {
const materials: Array<Material> = [];

scene.traverse((node: any) => {
if (Array.isArray(node.material)) {
materials.push(...node.material);
} else if (node.material != null) {
materials.push(node.material);
}
});

return materials;
};

suite('ModelUtils', () => {
suite('cloneGltf', () => {
let loader: any;
Expand All @@ -50,20 +34,6 @@ suite('ModelUtils', () => {
gltf = await loadWithLoader(ASTRONAUT_GLB_PATH, loader);
});

test('makes unique copies of all materials', () => {
const clonedGltf = cloneGltf(gltf);

const sourceMaterials = collectMaterials(gltf.scene!);
const clonedMaterials = collectMaterials(clonedGltf.scene!);

expect(sourceMaterials.length).to.be.greaterThan(0);
expect(sourceMaterials.length).to.be.equal(clonedMaterials.length);

sourceMaterials.forEach((material, index) => {
expect(clonedMaterials[index]).to.not.be.eql(material);
});
});

test('reduceVertices matches boundingBox', () => {
const maxX = (value: number, vertex: Vector3): number => {
return Math.max(value, vertex.x);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* @license
* Copyright 2020 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 {BufferGeometry, DoubleSide, Mesh, MeshStandardMaterial} from 'three';
import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js';

import {ModelViewerGLTFInstance} from '../../../three-components/gltf-instance/ModelViewerGLTFInstance.js';
import {PreparedGLTF} from '../../../three-components/GLTFInstance.js';
import {createFakeGLTF} from '../../helpers.js';


const expect = chai.expect;

suite('ModelViewerGLTFInstance', () => {
let rawGLTF: GLTF;
let preparedGLTF: PreparedGLTF;

setup(async () => {
rawGLTF = createFakeGLTF();

const materialOne = new MeshStandardMaterial();
const materialTwo = new MeshStandardMaterial();

materialTwo.transparent = true;
materialTwo.side = DoubleSide;

const meshOne = new Mesh(new BufferGeometry(), materialOne);
const meshTwo = new Mesh(new BufferGeometry(), materialOne);
const meshThree = new Mesh(new BufferGeometry(), materialTwo);

rawGLTF.scene.add(meshOne, meshTwo, meshThree);

preparedGLTF = ModelViewerGLTFInstance.prepare(rawGLTF);
});

suite('with a prepared GLTF', () => {
suite('when cloned', () => {
let cloneInstance: ModelViewerGLTFInstance;
let gltfInstance: ModelViewerGLTFInstance;

setup(() => {
gltfInstance = new ModelViewerGLTFInstance(preparedGLTF);
cloneInstance = gltfInstance.clone();
});

teardown(() => {
gltfInstance.dispose();
cloneInstance.dispose();
});

test('clones materials in a mesh', () => {
const [originalMeshOne, originalMeshTwo, originalMeshThree] =
gltfInstance.scene.children as [Mesh, Mesh, Mesh];
const [meshOne, meshTwo, meshThree] =
cloneInstance.scene.children as [Mesh, Mesh, Mesh];

expect(originalMeshOne.material).to.not.be.equal(meshOne.material);
expect(originalMeshTwo.material).to.not.be.equal(meshTwo.material);
expect(originalMeshThree.material).to.not.be.equal(meshThree.material);
});

test('only clones a discrete material once', () => {
const [meshOne, meshTwo, meshThree] =
cloneInstance.scene.children as [Mesh, Mesh, Mesh];

expect(meshOne.material).to.be.equal(meshTwo.material);
expect(meshOne.material).to.not.be.equal(meshThree.material);
});
});
});

suite('preparing the GLTF', () => {
test('duplicates a transparent, double-sided mesh', () => {
const meshThree = preparedGLTF.scene.children[2] as Mesh;
expect(meshThree.children[0]).to.be.ok;
expect((meshThree.children[0] as Mesh).isMesh).to.be.ok;
});

test('sets meshes to cast shadows', () => {
(preparedGLTF.scene.children as [Mesh, Mesh, Mesh])
.forEach(mesh => expect(mesh.castShadow).to.be.true);
});

test('disables frustum culling on meshes', () => {
(preparedGLTF.scene.children as [Mesh, Mesh, Mesh])
.forEach(mesh => expect(mesh.frustumCulled).to.be.false);
});
});
});
Loading

0 comments on commit 31664d7

Please sign in to comment.