UNPKG

@google/model-viewer

Version:

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

185 lines 8.39 kB
import { Material } from 'three'; const $threeGLTF = Symbol('threeGLTF'); const $gltf = Symbol('gltf'); const $gltfElementMap = Symbol('gltfElementMap'); const $threeObjectMap = Symbol('threeObjectMap'); const $parallelTraverseThreeScene = Symbol('parallelTraverseThreeScene'); const $correlateOriginalThreeGLTF = Symbol('correlateOriginalThreeGLTF'); const $correlateCloneThreeGLTF = Symbol('correlateCloneThreeGLTF'); /** * The Three.js GLTFLoader provides us with an in-memory representation * of a glTF in terms of Three.js constructs. It also provides us with a copy * of the deserialized glTF without any Three.js decoration, and a mapping of * glTF elements to their corresponding Three.js constructs. * * A CorrelatedSceneGraph exposes a synchronously available mapping of glTF * element references to their corresponding Three.js constructs. */ export class CorrelatedSceneGraph { /** * Produce a CorrelatedSceneGraph from a naturally generated Three.js GLTF. * Such GLTFs are produced by Three.js' GLTFLoader, and contain cached * details that expedite the correlation step. * * If a CorrelatedSceneGraph is provided as the second argument, re-correlates * a cloned Three.js GLTF with a clone of the glTF hierarchy used to produce * the upstream Three.js GLTF that the clone was created from. The result * CorrelatedSceneGraph is representative of the cloned hierarchy. */ static from(threeGLTF, upstreamCorrelatedSceneGraph) { if (upstreamCorrelatedSceneGraph != null) { return this[$correlateCloneThreeGLTF](threeGLTF, upstreamCorrelatedSceneGraph); } else { return this[$correlateOriginalThreeGLTF](threeGLTF); } } static [$correlateOriginalThreeGLTF](threeGLTF) { const gltf = threeGLTF.parser.json; const associations = threeGLTF.parser.associations; const gltfElementMap = new Map(); const defaultMaterial = { name: 'Default' }; const defaultReference = { type: 'materials', index: -1 }; for (const threeMaterial of associations.keys()) { // Note: GLTFLoader creates a "default" material that has no // corresponding glTF element in the case that no materials are // specified in the source glTF. In this case we append a default // material to allow this to be operated upon. if (threeMaterial instanceof Material && associations.get(threeMaterial) == null) { if (defaultReference.index < 0) { if (gltf.materials == null) { gltf.materials = []; } defaultReference.index = gltf.materials.length; gltf.materials.push(defaultMaterial); } threeMaterial.name = defaultMaterial.name; associations.set(threeMaterial, { materials: defaultReference.index }); } } // Creates a reverse look up map (gltf-object to Three-object) for (const [threeObject, gltfMappings] of associations) { if (gltfMappings) { threeObject.userData = threeObject.userData || {}; threeObject.userData.associations = gltfMappings; } for (const mapping in gltfMappings) { if (mapping != null && mapping !== 'primitives') { const type = mapping; const elementArray = gltf[type] || []; const gltfElement = elementArray[gltfMappings[type]]; if (gltfElement == null) { // TODO: Maybe throw here... continue; } let threeObjects = gltfElementMap.get(gltfElement); if (threeObjects == null) { threeObjects = new Set(); gltfElementMap.set(gltfElement, threeObjects); } threeObjects.add(threeObject); } } } return new CorrelatedSceneGraph(threeGLTF, gltf, associations, gltfElementMap); } /** * Transfers the association between a raw glTF and a Three.js scene graph * to a clone of the Three.js scene graph, resolved as a new * CorrelatedSceneGraph instance. */ static [$correlateCloneThreeGLTF](cloneThreeGLTF, upstreamCorrelatedSceneGraph) { const originalThreeGLTF = upstreamCorrelatedSceneGraph.threeGLTF; const originalGLTF = upstreamCorrelatedSceneGraph.gltf; const cloneGLTF = JSON.parse(JSON.stringify(originalGLTF)); const cloneThreeObjectMap = new Map(); const cloneGLTFElementMap = new Map(); for (let i = 0; i < originalThreeGLTF.scenes.length; i++) { this[$parallelTraverseThreeScene](originalThreeGLTF.scenes[i], cloneThreeGLTF.scenes[i], (object, cloneObject) => { const elementReference = upstreamCorrelatedSceneGraph.threeObjectMap.get(object); if (elementReference == null) { return; } for (const mapping in elementReference) { if (mapping != null && mapping !== 'primitives') { const type = mapping; const index = elementReference[type]; const cloneElement = cloneGLTF[type][index]; const mappings = cloneThreeObjectMap.get(cloneObject) || {}; mappings[type] = index; cloneThreeObjectMap.set(cloneObject, mappings); const cloneObjects = cloneGLTFElementMap.get(cloneElement) || new Set(); cloneObjects.add(cloneObject); cloneGLTFElementMap.set(cloneElement, cloneObjects); } } }); } return new CorrelatedSceneGraph(cloneThreeGLTF, cloneGLTF, cloneThreeObjectMap, cloneGLTFElementMap); } /** * Traverses two presumably identical Three.js scenes, and invokes a * callback for each Object3D or Material encountered, including the initial * scene. Adapted from * https://github.com/mrdoob/three.js/blob/7c1424c5819ab622a346dd630ee4e6431388021e/examples/jsm/utils/SkeletonUtils.js#L586-L596 */ static [$parallelTraverseThreeScene](sceneOne, sceneTwo, callback) { const traverse = (a, b) => { callback(a, b); if (a.isObject3D) { const meshA = a; const meshB = b; if (meshA.material) { if (Array.isArray(meshA.material)) { for (let i = 0; i < meshA.material.length; ++i) { callback(meshA.material[i], meshB.material[i]); } } else { callback(meshA.material, meshB.material); } } for (let i = 0; i < a.children.length; ++i) { traverse(a.children[i], b.children[i]); } } }; traverse(sceneOne, sceneTwo); } /** * The source Three.js GLTF result given to us by a Three.js GLTFLoader. */ get threeGLTF() { return this[$threeGLTF]; } /** * The in-memory deserialized source glTF. */ get gltf() { return this[$gltf]; } /** * A Map of glTF element references to arrays of corresponding Three.js * object references. Three.js objects are kept in arrays to account for * cases where more than one Three.js object corresponds to a single glTF * element. */ get gltfElementMap() { return this[$gltfElementMap]; } /** * A map of individual Three.js objects to corresponding elements in the * source glTF. */ get threeObjectMap() { return this[$threeObjectMap]; } constructor(threeGLTF, gltf, threeObjectMap, gltfElementMap) { this[$threeGLTF] = threeGLTF; this[$gltf] = gltf; this[$gltfElementMap] = gltfElementMap; this[$threeObjectMap] = threeObjectMap; } } //# sourceMappingURL=correlated-scene-graph.js.map