Skip to content

Commit

Permalink
Ensure traversal order (google#1166)
Browse files Browse the repository at this point in the history
* Enforce scene graph order

* Remove local-dev specific file

* WIP

* Make it possible to correlate clones

* Use canonical GLTFInstance instead of partial clone

* Fix test failures

* WIP

* Clean up correlation based on tweaked GLTFLoader

* Sync-ify CorrelatedSceneGraph

* Use temporary Three.js dependency, update m-v

* Fix object-key-order-dependent bug in test

* De-async-ify prepare/clone, fix test helper

* Remove vestigial debugger

* IE11 doesn't support Map.prototype.entries

* IE11 doesn't support any Map iteration, actually

* Really, no iteration in IE11

* Update to more recent Three.js revision

* Use Three.js dev branch

* Remove vestigial comments

* Fix typo

* Fix typo

* Add commentary about active scenes

* Remove vestigial async/await
  • Loading branch information
Christopher Joel authored May 18, 2020
1 parent 93d81d2 commit b3f48eb
Show file tree
Hide file tree
Showing 37 changed files with 8,462 additions and 223 deletions.
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
BasedOnStyle: Google
Language: JavaScript
AlignAfterOpenBracket: AlwaysBreak
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
Expand Down
11 changes: 0 additions & 11 deletions model-viewer.code-workspace

This file was deleted.

4 changes: 3 additions & 1 deletion packages/3dom/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@

<script type="module">
import { ModelGraft } from '../lib/facade/three-js/model-graft.js';
import { CorrelatedSceneGraph } from '../lib/facade/three-js/correlated-scene-graph.js';
import { ThreeDOMExecutionContext } from '../lib/context.js';

import { Scene, sRGBEncoding, ACESFilmicToneMapping, UnsignedByteType, PMREMGenerator, PerspectiveCamera, WebGLRenderer } from '../node_modules/three/build/three.module.js';
Expand Down Expand Up @@ -165,7 +166,8 @@
const scriptText = script.textContent;

const context = new ThreeDOMExecutionContext();
const graft = new ModelGraft('../shared-assets/models/Astronaut.glb', gltf);
const graft = new ModelGraft('../shared-assets/models/Astronaut.glb',
CorrelatedSceneGraph.from(gltf));

context.eval(scriptText);
context.changeModel(graft);
Expand Down
5 changes: 2 additions & 3 deletions packages/3dom/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/3dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"typescript": "3.7.3"
},
"dependencies": {
"three": "^0.115.0"
"three": "mrdoob/three.js#dev"
},
"publishConfig": {
"access": "public"
Expand Down
10 changes: 8 additions & 2 deletions packages/3dom/src/context-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
*/

import {ThreeDOMExecutionContext} from './context.js';
import {CorrelatedSceneGraph} from './facade/three-js/correlated-scene-graph.js';
import {ModelGraft} from './facade/three-js/model-graft.js';
import {createFakeGLTF, waitForEvent} from './test-helpers.js';
import {assetPath, loadThreeGLTF, waitForEvent} from './test-helpers.js';

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

suite('context', () => {
suite('ThreeDOMExecutionContext', () => {
Expand Down Expand Up @@ -70,7 +73,10 @@ suite('context', () => {

suite('when the model changes', () => {
test('dispatches an event in the worker', async () => {
const modelGraft = new ModelGraft('', createFakeGLTF());
const modelGraft = new ModelGraft(
'',
await CorrelatedSceneGraph.from(
await loadThreeGLTF(ASTRONAUT_GLB_PATH)));
const context = new ThreeDOMExecutionContext(['messaging']);
const workerConfirmsEvent = waitForEvent(context.worker, 'message');

Expand Down
82 changes: 55 additions & 27 deletions packages/3dom/src/end-to-end-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,38 @@
* limitations under the License.
*/

import {GLTF, GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
import {BufferGeometry} from 'three/src/core/BufferGeometry.js';
import {Object3D} from 'three/src/core/Object3D.js';
import {MeshStandardMaterial} from 'three/src/materials/MeshStandardMaterial.js';
import {Color} from 'three/src/math/Color.js';
import {Mesh} from 'three/src/objects/Mesh.js';

import {ThreeDOMCapability} from './api.js';
import {ThreeDOMExecutionContext} from './context.js';
import {CorrelatedSceneGraph} from './facade/three-js/correlated-scene-graph.js';
import {ModelGraft} from './facade/three-js/model-graft.js';
import {createFakeGLTF, waitForEvent} from './test-helpers.js';
import {assetPath, loadThreeGLTF, waitForEvent} from './test-helpers.js';

const ASTRONAUT_GLB_URL = './base/shared-assets/models/Astronaut.glb';
const ASTRONAUT_GLB_PATH = assetPath('models/Astronaut.glb');
const ORDER_TEST_GLB_PATH = assetPath('models/order-test/order-test.glb');

suite('end-to-end', () => {
test('can operate on a scene graph via a custom script in a worker', async () => {
const gltf = createFakeGLTF();
const prepareConstructsFor =
async (url: string, capabilities: ThreeDOMCapability[] = []) => {
const gltf = await loadThreeGLTF(url);
const executionContext = new ThreeDOMExecutionContext(capabilities);
const graft = new ModelGraft(url, CorrelatedSceneGraph.from(gltf));

const material = new MeshStandardMaterial();
material.color = new Color('rgba(255, 0, 0)');
const mesh = new Mesh(new BufferGeometry(), material);
return {executionContext, graft, gltf};
};

gltf.scene.add(mesh);
suite('end-to-end', () => {
test('can operate on a scene graph via a custom script in a worker', async () => {
const {executionContext, graft, gltf} =
await prepareConstructsFor(ASTRONAUT_GLB_PATH, ['material-properties']);

const executionContext =
new ThreeDOMExecutionContext(['material-properties']);
const graft = new ModelGraft('', gltf);
// Note that this lookup is specific to the Astronaut model and will need
// to be adapted in case the model changes:
const material =
((gltf.scene.children[0] as Object3D).children[0] as Mesh).material as
MeshStandardMaterial;

executionContext.changeModel(graft);

Expand All @@ -51,16 +58,12 @@ suite('end-to-end', () => {
});

test('can operate on the artifact of a Three.js GLTFLoader', async () => {
const gltf = await new Promise<GLTF>((resolve) => {
new GLTFLoader().load(ASTRONAUT_GLB_URL, (gltf) => resolve(gltf));
});
const {executionContext, graft, gltf} =
await prepareConstructsFor(ASTRONAUT_GLB_PATH, ['material-properties']);

const material = (gltf.scene.children[0]!.children[0] as Mesh).material as
MeshStandardMaterial;
const graft = new ModelGraft(ASTRONAUT_GLB_URL, gltf);

const executionContext =
new ThreeDOMExecutionContext(['material-properties']);
executionContext.changeModel(graft);

executionContext.eval(
Expand All @@ -73,16 +76,12 @@ suite('end-to-end', () => {
});

test('expresses the name of a material in the worklet context', async () => {
const gltf = await new Promise<GLTF>((resolve) => {
new GLTFLoader().load(ASTRONAUT_GLB_URL, (gltf) => resolve(gltf));
});
const {executionContext, graft, gltf} = await prepareConstructsFor(
ASTRONAUT_GLB_PATH, ['messaging', 'material-properties']);

const material = (gltf.scene.children[0]!.children[0] as Mesh).material as
MeshStandardMaterial;
const graft = new ModelGraft(ASTRONAUT_GLB_URL, gltf);

const executionContext =
new ThreeDOMExecutionContext(['messaging', 'material-properties']);
executionContext.changeModel(graft);

const messageEventArrives =
Expand All @@ -95,4 +94,33 @@ suite('end-to-end', () => {
expect(messageEvent.data).to.not.be.equal('');
expect(messageEvent.data).to.be.equal(material.name);
});

suite('scene graph order', () => {
test('orders materials deterministically', async () => {
const {executionContext, graft} = await prepareConstructsFor(
ORDER_TEST_GLB_PATH, ['messaging', 'material-properties']);

executionContext.changeModel(graft);

const messageEventArrives =
waitForEvent<MessageEvent>(executionContext.worker, 'message');

executionContext.eval(`
var materialNames = model.materials.map(function (material) {
return material.name;
});
self.postMessage(JSON.stringify(materialNames));
`);

const messageEvent = await messageEventArrives;
const materialNames = JSON.parse(messageEvent.data);

expect(materialNames).to.be.deep.equal([
'Material0',
'Material1',
'Material2',
]);
});
});
});
4 changes: 3 additions & 1 deletion packages/3dom/src/facade/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
*/

import {RGBA} from '../api.js';
import {GLTF, GLTFElement} from '../gltf-2.0.js';
import {SerializedMaterial, SerializedModel, SerializedPBRMetallicRoughness, SerializedThreeDOMElement} from '../protocol.js';

export interface ThreeDOMElement {
readonly ownerModel: Model;
readonly internalID: number;
readonly name: string|null;
readonly sourceObject: GLTF|GLTFElement;
toJSON(): SerializedThreeDOMElement;
}

Expand All @@ -40,7 +42,7 @@ export interface PBRMetallicRoughness extends ThreeDOMElement {
* @see https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#material
*/
export interface Material extends ThreeDOMElement {
readonly pbrMetallicRoughness: PBRMetallicRoughness;
readonly pbrMetallicRoughness: PBRMetallicRoughness|null;
toJSON(): SerializedMaterial;
}

Expand Down
91 changes: 91 additions & 0 deletions packages/3dom/src/facade/three-js/correlated-scene-graph-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* @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 {Mesh, MeshStandardMaterial, Object3D} from 'three';
import {GLTFReference} from 'three/examples/jsm/loaders/GLTFLoader.js';

import {Material, PBRMetallicRoughness, Texture, TextureInfo} from '../../gltf-2.0.js';
import {assetPath, loadThreeGLTF} from '../../test-helpers.js';

import {CorrelatedSceneGraph} from './correlated-scene-graph.js';

const HORSE_GLB_PATH = assetPath('models/Horse.glb');
const ORDER_TEST_GLB_PATH = assetPath('models/order-test/order-test.glb');

const getObject3DByName =
<T extends Object3D>(root: Object3D, name: string): T|null => {
const objects = [root];
while (objects.length) {
const next = objects.shift()!;
if (next.name === name) {
return next as T;
}
objects.push(...next.children);
}
return null;
};

suite('facade/three-js/correlated-scene-graph', () => {
suite('CorrelatedSceneGraph', () => {
test('maps Three.js materials to glTF elements', async () => {
const threeGLTF = await loadThreeGLTF(HORSE_GLB_PATH);
const correlatedSceneGraph = CorrelatedSceneGraph.from(threeGLTF);

const threeMaterial =
((threeGLTF.scene.children[0] as Mesh).material as
MeshStandardMaterial);
const gltfMaterial = threeGLTF.parser.json.materials[0]! as Material;
const gltfReference =
correlatedSceneGraph.threeObjectMap.get(threeMaterial);

expect(gltfReference).to.be.ok;

const {type, index} = gltfReference as GLTFReference;

const referencedGltfMaterial = threeGLTF.parser.json[type][index];

expect(referencedGltfMaterial).to.be.equal(gltfMaterial);
});

test('maps Three.js textures to glTF elements', async () => {
const threeGLTF = await loadThreeGLTF(ORDER_TEST_GLB_PATH);
const correlatedSceneGraph = CorrelatedSceneGraph.from(threeGLTF);

const threeMaterial =
getObject3DByName<Mesh>(threeGLTF.scene, 'Node0')!.material as
MeshStandardMaterial;
const threeTexture = threeMaterial.map!;

const gltfMaterial = threeGLTF.parser.json.materials[2]! as Material;
const textureIndex =
((gltfMaterial.pbrMetallicRoughness as PBRMetallicRoughness)
.baseColorTexture as TextureInfo)
.index;

const gltfTexture =
threeGLTF.parser.json.textures[textureIndex] as Texture;
const gltfReference =
correlatedSceneGraph.threeObjectMap.get(threeTexture);

expect(gltfReference).to.be.ok;

const {type, index} = gltfReference as GLTFReference;

const referencedGltfTexture = threeGLTF.parser.json[type][index];

expect(referencedGltfTexture).to.be.equal(gltfTexture);
});
});
});
Loading

0 comments on commit b3f48eb

Please sign in to comment.