UNPKG

@google/model-viewer

Version:

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

164 lines 6.36 kB
/* @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 { Texture } from 'three'; import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js'; export const $prepared = Symbol('prepared'); export const $prepare = Symbol('prepare'); export const $preparedGLTF = Symbol('preparedGLTF'); export const $clone = Symbol('clone'); /** * Represents the preparation and enhancement of the output of a Three.js * GLTFLoader (a Three.js-flavor "GLTF"), to make it suitable for optimal, * correct viewing in a given presentation context and also make the cloning * process more explicit and legible. * * A GLTFInstance is API-compatible with a Three.js-flavor "GLTF", so it should * be considered to be interchangeable with the loaded result of a GLTFLoader. * * This basic implementation only implements trivial preparation and enhancement * of a GLTF. These operations are intended to be enhanced by inheriting * classes. */ export class GLTFInstance { /** * Prepares a given GLTF for presentation and future cloning. A GLTF that is * prepared can safely have this method invoked on it multiple times; it will * only be prepared once, including after being cloned. */ static prepare(source) { if (source.scene == null) { throw new Error('Model does not have a scene'); } if (source[$prepared]) { return source; } const prepared = this[$prepare](source); // NOTE: ES5 Symbol polyfill is not compatible with spread operator // so {...prepared, [$prepared]: true} does not work prepared[$prepared] = true; return prepared; } /** * Override in an inheriting class to apply specialty one-time preparations * for a given input GLTF. */ static [$prepare](source) { // TODO(#195,#1003): We don't currently support multiple scenes, so we don't // bother preparing extra scenes for now: const { scene } = source; const scenes = [scene]; return Object.assign(Object.assign({}, source), { scene, scenes }); } get parser() { return this[$preparedGLTF].parser; } get animations() { return this[$preparedGLTF].animations; } get scene() { return this[$preparedGLTF].scene; } get scenes() { return this[$preparedGLTF].scenes; } get cameras() { return this[$preparedGLTF].cameras; } get asset() { return this[$preparedGLTF].asset; } get userData() { return this[$preparedGLTF].userData; } constructor(preparedGLTF) { this[$preparedGLTF] = preparedGLTF; } /** * Creates and returns a copy of this instance. */ clone() { const GLTFInstanceConstructor = this.constructor; const clonedGLTF = this[$clone](); return new GLTFInstanceConstructor(clonedGLTF); } /** * Cleans up any retained memory that might not otherwise be released when * this instance is done being used. */ dispose() { this.scenes.forEach((scene) => { scene.traverse((object) => { const mesh = object; if (!mesh.material) { return; } const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]; materials.forEach(material => { // Explicitly dispose any textures assigned to this material for (const propertyName in material) { const texture = material[propertyName]; if (texture instanceof Texture) { const image = texture.source.data; if (image.close != null) { image.close(); } texture.dispose(); } } material.dispose(); }); mesh.geometry.dispose(); }); }); } /** * Override in an inheriting class to implement specialized cloning strategies */ [$clone]() { const source = this[$preparedGLTF]; // TODO(#195,#1003): We don't currently support multiple scenes, so we don't // bother cloning extra scenes for now: const scene = SkeletonUtils.clone(this.scene); cloneVariantMaterials(scene, this.scene); const scenes = [scene]; const userData = source.userData ? Object.assign({}, source.userData) : {}; return Object.assign(Object.assign({}, source), { scene, scenes, userData }); } } // Variant materials and original material instances are stored under // object.userData.variantMaterials/originalMaterial. // Three.js Object3D.clone() doesn't clone Three.js objects under // .userData so this function is a workaround. const cloneVariantMaterials = (dst, src) => { traversePair(dst, src, (dst, src) => { if (src.userData.variantMaterials !== undefined) { dst.userData.variantMaterials = new Map(src.userData.variantMaterials); } if (src.userData.variantData !== undefined) { dst.userData.variantData = src.userData.variantData; } if (src.userData.originalMaterial !== undefined) { dst.userData.originalMaterial = src.userData.originalMaterial; } }); }; const traversePair = (obj1, obj2, callback) => { callback(obj1, obj2); // Assume obj1 and obj2 have the same tree structure for (let i = 0; i < obj1.children.length; i++) { traversePair(obj1.children[i], obj2.children[i], callback); } }; //# sourceMappingURL=GLTFInstance.js.map