diff --git a/src/containers/Olympic/README.md b/src/containers/Olympic/README.md
index 13d0623..85343ce 100644
--- a/src/containers/Olympic/README.md
+++ b/src/containers/Olympic/README.md
@@ -1,4 +1,4 @@
-# Three.js实现3D冬奥会创意页面
+# Three.js实现3D冬奥主题会创意页面
@@ -11,4 +11,20 @@ https://anyconv.com/tw/max-zhuan-obj/
在blender2.8中怎么给模型贴图
-blender 模型贴图 https://jingyan.baidu.com/article/363872ecf6367f2f4ba16f95.html
\ No newline at end of file
+blender 模型贴图 https://jingyan.baidu.com/article/363872ecf6367f2f4ba16f95.html
+
+## 背景
+
+## 效果
+
+## 实现
+
+## 总结
+
+## 下期预告
+
+* 《Metahuman元人类!记一次Three.js人像优化》
+* 《y2k!3D千年虫风格,y2k的事你少管》
+* 《喜羊羊你可真厉害,沸羊羊我讨厌你》
+
+## 附录
\ No newline at end of file
diff --git a/src/containers/Olympic/images/bingdundun.png b/src/containers/Olympic/images/bingdundun.png
index 64d23e2..7a231ad 100644
Binary files a/src/containers/Olympic/images/bingdundun.png and b/src/containers/Olympic/images/bingdundun.png differ
diff --git a/src/containers/Olympic/images/bingdundun_0.png b/src/containers/Olympic/images/bingdundun_0.png
new file mode 100644
index 0000000..32cec4b
Binary files /dev/null and b/src/containers/Olympic/images/bingdundun_0.png differ
diff --git a/src/containers/Olympic/images/bingdundun_1.png b/src/containers/Olympic/images/bingdundun_1.png
new file mode 100644
index 0000000..0669625
Binary files /dev/null and b/src/containers/Olympic/images/bingdundun_1.png differ
diff --git a/src/containers/Olympic/images/blender.png b/src/containers/Olympic/images/blender.png
index 0f3a868..960cee1 100644
Binary files a/src/containers/Olympic/images/blender.png and b/src/containers/Olympic/images/blender.png differ
diff --git a/src/containers/Olympic/images/flag.png b/src/containers/Olympic/images/flag.png
new file mode 100644
index 0000000..2bff7bf
Binary files /dev/null and b/src/containers/Olympic/images/flag.png differ
diff --git a/src/containers/Olympic/images/flag_0.png b/src/containers/Olympic/images/flag_0.png
new file mode 100644
index 0000000..64c5e05
Binary files /dev/null and b/src/containers/Olympic/images/flag_0.png differ
diff --git a/src/containers/Olympic/images/panda.png b/src/containers/Olympic/images/panda.png
new file mode 100644
index 0000000..20c7cd3
Binary files /dev/null and b/src/containers/Olympic/images/panda.png differ
diff --git a/src/containers/Olympic/images/sky.jpg b/src/containers/Olympic/images/sky.jpg
index 33a250d..fb9b955 100644
Binary files a/src/containers/Olympic/images/sky.jpg and b/src/containers/Olympic/images/sky.jpg differ
diff --git a/src/containers/Olympic/images/snow.png b/src/containers/Olympic/images/snow.png
index 97b3f94..adf2edb 100644
Binary files a/src/containers/Olympic/images/snow.png and b/src/containers/Olympic/images/snow.png differ
diff --git a/src/containers/Olympic/images/tree.png b/src/containers/Olympic/images/tree.png
index 08ad74a..bb91b6b 100644
Binary files a/src/containers/Olympic/images/tree.png and b/src/containers/Olympic/images/tree.png differ
diff --git a/src/containers/Olympic/images/tree_0.png b/src/containers/Olympic/images/tree_0.png
new file mode 100644
index 0000000..e0753ae
Binary files /dev/null and b/src/containers/Olympic/images/tree_0.png differ
diff --git a/src/containers/Olympic/index.css b/src/containers/Olympic/index.css
new file mode 100644
index 0000000..140028a
--- /dev/null
+++ b/src/containers/Olympic/index.css
@@ -0,0 +1,28 @@
+.olympic_loading {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ background: #FFFFFF url('./images/panda.png') no-repeat left center;
+ background-size: auto 60%;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ font-size: 120px;
+ color: #2D2D2D;
+ text-shadow: 0 1px 0 hsl(174,5%,80%),
+ 0 2px 0 hsl(174,5%,75%),
+ 0 3px 0 hsl(174,5%,70%),
+ 0 4px 0 hsl(174,5%,66%),
+ 0 5px 0 hsl(174,5%,64%),
+ 0 6px 0 hsl(174,5%,62%),
+ 0 7px 0 hsl(174,5%,61%),
+ 0 8px 0 hsl(174,5%,60%),
+ 0 0 5px rgba(0,0,0,.05),
+ 0 1px 3px rgba(0,0,0,.2),
+ 0 3px 5px rgba(0,0,0,.2),
+ 0 5px 10px rgba(0,0,0,.2),
+ 0 10px 10px rgba(0,0,0,.2),
+ 0 20px 20px rgba(0,0,0,.3);
+}
diff --git a/src/containers/Olympic/index.js b/src/containers/Olympic/index.js
index 5be3b67..d07ed74 100644
--- a/src/containers/Olympic/index.js
+++ b/src/containers/Olympic/index.js
@@ -2,13 +2,18 @@
import React from 'react';
import Stats from "three/examples/jsm/libs/stats.module";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
-import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
+import Animations from '../../assets/utils/animations';
import landModel from './models/land.glb';
-import pandaModel from './models/panda.gltf';
+import treeModel from './models/panda.gltf';
import bingdundunModel from './models/bingdundun.glb';
+import flagModel from './models/flag.glb';
import skyTexture from './images/sky.jpg';
import snowTexture from './images/snow.png';
import treeTexture from './images/tree.png';
+import flagTexture from './images/flag.png';
+require('./libs/GLTFLoader.js');
+import './index.css';
export default class Olympic extends React.Component {
@@ -16,18 +21,22 @@ export default class Olympic extends React.Component {
super(props);
}
+ state = {
+ loadingProcess: 0
+ }
+
componentDidMount() {
this.initThree()
}
initThree = () => {
- var container, controls, stats;
+ var container, controls, stats, mixer;
var camera, scene, renderer, light, land = null, meshes = [], points = [];
- var fiveCyclesGroup = new THREE.Group();
+ var fiveCyclesGroup = new THREE.Group(), clock = new THREE.Clock();
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
-
+ var _this = this;
init();
animate();
function init() {
@@ -41,47 +50,74 @@ export default class Olympic extends React.Component {
scene = new THREE.Scene();
scene.background = new THREE.TextureLoader().load(skyTexture);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
- camera.position.set(0, -2, 20);
+ camera.position.set(0, 30, 100);
camera.lookAt(new THREE.Vector3(0, 0, 0));
- // var axes = new THREE.AxisHelper(30);
- // scene.add(axes);
-
// 性能工具
- stats = new Stats();
- document.documentElement.appendChild(stats.dom);
+ // stats = new Stats();
+ // document.documentElement.appendChild(stats.dom);
const cubeGeometry = new THREE.BoxGeometry(0.001, 0.001, 0.001);
const cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xdc161a });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
- cube.position.set(0, 0, 0,);
- light = new THREE.DirectionalLight(0xdddddd, 1);
- light.intensity = 1.2;
- light.position.set(-5, 8, 3);
+ cube.position.set(0, 0, 0);
+ light = new THREE.DirectionalLight(0xffffff, 1);
+ light.intensity = 1;
+ light.position.set(16, 16, 8);
light.castShadow = true;
light.target = cube;
light.shadow.mapSize.width = 512 * 12;
light.shadow.mapSize.height = 512 * 12;
- light.shadow.camera.top = 10;
- light.shadow.camera.bottom = - 5;
- light.shadow.camera.left = - 5;
- light.shadow.camera.right = 10;
+ light.shadow.camera.top = 40;
+ light.shadow.camera.bottom = -40;
+ light.shadow.camera.left = -40;
+ light.shadow.camera.right = 40;
scene.add(light);
- var ambientLight = new THREE.AmbientLight(0xdddddd);
+ // const lightHelper = new THREE.DirectionalLightHelper(light, 1, 'red');
+ // scene.add(lightHelper);
+ // const lightCameraHelper = new THREE.CameraHelper(light.shadow.camera);
+ // scene.add(lightCameraHelper);
+
+ var ambientLight = new THREE.AmbientLight(0xcfffff);
+ ambientLight.intensity = 1;
scene.add(ambientLight);
+ const manager = new THREE.LoadingManager();
+ manager.onStart = (url, loaded, total) => {};
+ manager.onLoad = () => { console.log('Loading complete!')};
+ manager.onProgress = async(url, loaded, total) => {
+ if (Math.floor(loaded / total * 100) === 100) {
+ _this.loadingProcessTimeout && clearTimeout(_this.loadingProcessTimeout);
+ _this.loadingProcessTimeout = setTimeout(() => {
+ _this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
+ Animations.animateCamera(camera, controls, { x: 0, y: -1, z: 20 }, { x: 0, y: 0, z: 0 }, 3600, () => {});
+ }, 800);
+ } else {
+ _this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
+ }
+ };
+
// 添加地面
- var loader = new GLTFLoader();
+ var loader = new THREE.GLTFLoader(manager);
loader.load(landModel, function (mesh) {
mesh.scene.traverse(function (child) {
if (child.isMesh) {
meshes.push(child)
+ child.material.metalness = .1;
+ child.material.roughness = .8;
+ // 地面
if (child.name === 'Mesh_2') {
- child.material.metalness = .1
- child.material.roughness = .8
- child.material.color = new THREE.Color(0xffffff)
+ child.material.metalness = .5;
+ child.receiveShadow = true;
}
+ // 围巾
+ if (child.name === 'Mesh_17') {
+ child.material.metalness = .2;
+ child.material.roughness = .8;
+ }
+ // 帽子
+ if (child.name === 'Mesh_17') {}
}
});
mesh.scene.rotation.y = Math.PI / 4;
@@ -91,6 +127,39 @@ export default class Olympic extends React.Component {
scene.add(land);
});
+ // 旗帜
+ loader.load(flagModel, mesh => {
+ mesh.scene.traverse(child => {
+ if (child.isMesh) {
+ meshes.push(child)
+ child.castShadow = true;
+ // 旗帜
+ if (child.name === 'mesh_0001') {
+ child.material.metalness = .1;
+ child.material.roughness = .1;
+ child.material.map = new THREE.TextureLoader().load(flagTexture);
+ }
+ // 旗杆
+ if (child.name === '柱体') {
+ child.material.metalness = .6;
+ child.material.roughness = 0;
+ child.material.refractionRatio = 1;
+ child.material.color = new THREE.Color(0xeeeeee);
+ }
+ }
+ });
+ mesh.scene.rotation.y = Math.PI / 24;
+ mesh.scene.position.set(2, -7, -1);
+ mesh.scene.scale.set(4, 4, 4);
+ // 动画
+ let meshAnimation = mesh.animations[0];
+ mixer = new THREE.AnimationMixer(mesh.scene);
+ let animationClip = meshAnimation;
+ let clipAction = mixer.clipAction(animationClip).play();
+ animationClip = clipAction.getClip();
+ scene.add(mesh.scene);
+ });
+
// bingdundun
loader.load(bingdundunModel, function (mesh) {
mesh.scene.traverse(function (child) {
@@ -98,99 +167,84 @@ export default class Olympic extends React.Component {
meshes.push(child)
if (child.name === 'oldtiger001') {
child.material.metalness = .5
+ child.material.roughness = .8
}
if (child.name === 'oldtiger002') {
child.material.transparent = true;
child.material.opacity = .5
+ child.material.metalness = .2
+ child.material.roughness = 0
+ child.material.refractionRatio = 1
+ child.castShadow = true;
}
}
});
mesh.scene.rotation.y = Math.PI / 24;
- mesh.scene.position.set(-8, -10, 0);
+ mesh.scene.position.set(-8, -12, 0);
mesh.scene.scale.set(24, 24, 24);
scene.add(mesh.scene);
});
// 添加树
- let pandaMaterial = new THREE.MeshPhysicalMaterial({
- map: new THREE.TextureLoader().load(treeTexture),
- transparent: true,
- side: THREE.DoubleSide,
- metalness: .2,
- roughness: .8,
- depthTest: true,
- depthWrite: false,
- skinning: false,
- fog: false,
- reflectivity: 0.1,
- refractionRatio: 0,
+ let treeMaterial = new THREE.MeshPhysicalMaterial({
+ map: new THREE.TextureLoader().load(treeTexture),
+ transparent: true,
+ side: THREE.DoubleSide,
+ metalness: .2,
+ roughness: .8,
+ depthTest: true,
+ depthWrite: false,
+ skinning: false,
+ fog: false,
+ reflectivity: 0.1,
+ refractionRatio: 0,
});
- let pandaCustomDepthMaterial = new THREE.MeshDepthMaterial({
- depthPacking: THREE.RGBADepthPacking,
- map: new THREE.TextureLoader().load(treeTexture),
- alphaTest: 0.5
+ let treeCustomDepthMaterial = new THREE.MeshDepthMaterial({
+ depthPacking: THREE.RGBADepthPacking,
+ map: new THREE.TextureLoader().load(treeTexture),
+ alphaTest: 0.5
});
- loader.load(pandaModel, function (mesh) {
+ loader.load(treeModel, function (mesh) {
mesh.scene.traverse(function (child) {
if (child.isMesh) {
meshes.push(child)
- child.material = pandaMaterial
- child.custromMaterial = pandaCustomDepthMaterial
+ child.material = treeMaterial;
+ child.custromMaterial = treeCustomDepthMaterial;
}
});
- mesh.scene.position.set(14, -8, 0);
+ mesh.scene.position.set(14, -9, 0);
mesh.scene.scale.set(16, 16, 16);
scene.add(mesh.scene);
let tree2 = mesh.scene.clone();
- tree2.position.set(8, -8, -14);
+ tree2.position.set(10, -8, -15);
tree2.scale.set(18, 18, 18);
scene.add(tree2)
-
let tree3 = mesh.scene.clone();
- tree3.position.set(-16, -8, -16);
- tree3.scale.set(18, 18, 18);
+ tree3.position.set(-18, -8, -16);
+ tree3.scale.set(22, 22, 22);
scene.add(tree3)
});
// 创建五环
const fiveCycles = [
- {
- key: 'cycle_0',
- color: 0x0885c2,
- position: { x: -250, y: 0, z: 0 }
- },
- {
- key: 'cycle_1',
- color: 0x000000,
- position: { x: -10, y: 0, z: 5 }
- },
- {
- key: 'cycle_2',
- color: 0xed334e,
- position: { x: 230, y: 0, z: 0 }
- },
- {
- key: 'cycle_3',
- color: 0xfbb132,
- position: { x: -125, y: -100, z: -5 }
- },
- {
- key: 'cycle_4',
- color: 0x1c8b3c,
- position: { x: 115, y: -100, z: 10 }
- }
+ { key: 'cycle_0', color: 0x0885c2, position: { x: -250, y: 0, z: 0 }},
+ { key: 'cycle_1', color: 0x000000, position: { x: -10, y: 0, z: 5 }},
+ { key: 'cycle_2', color: 0xed334e, position: { x: 230, y: 0, z: 0 }},
+ { key: 'cycle_3', color: 0xfbb132, position: { x: -125, y: -100, z: -5 }},
+ { key: 'cycle_4', color: 0x1c8b3c, position: { x: 115, y: -100, z: 10 }}
];
fiveCycles.map(item => {
let cycleMesh = new THREE.Mesh(new THREE.TorusGeometry(100, 10, 10, 50), new THREE.MeshLambertMaterial({
color: new THREE.Color(item.color),
side: THREE.DoubleSide
}));
+ cycleMesh.castShadow = true;
cycleMesh.position.set(item.position.x, item.position.y, item.position.z);
meshes.push(cycleMesh);
fiveCyclesGroup.add(cycleMesh);
});
- fiveCyclesGroup.scale.set(.03, .03, .03);
- fiveCyclesGroup.position.set(0, 8, -5);
+ fiveCyclesGroup.scale.set(.036, .036, .036);
+ fiveCyclesGroup.position.set(0, 10, -8);
scene.add(fiveCyclesGroup);
// 创建雪花
@@ -226,9 +280,12 @@ export default class Olympic extends React.Component {
controls.enableDamping = true;
controls.enablePan = false;
controls.enableZoom = false;
- // 最大仰角
+ // 垂直旋转角度限制
controls.minPolarAngle = 1.4;
controls.maxPolarAngle = 1.8;
+ // 水平旋转角度限制
+ controls.minAzimuthAngle = -.6;
+ controls.maxAzimuthAngle = .6;
window.addEventListener('resize', onWindowResize, false);
}
@@ -238,17 +295,13 @@ export default class Olympic extends React.Component {
renderer.setSize(window.innerWidth, window.innerHeight);
}
- function onDocumentMouseMove( event ) {
- mouseX = event.clientX - windowHalfX;
- mouseY = event.clientY - windowHalfY;
- }
-
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
stats && stats.update();
controls && controls.update();
- fiveCyclesGroup && (fiveCyclesGroup.rotation.y += .01)
+ TWEEN && TWEEN.update();
+ fiveCyclesGroup && (fiveCyclesGroup.rotation.y += .01);
let vertices = points.geometry.vertices;
vertices.forEach(function (v) {
v.y = v.y - (v.velocityY);
@@ -258,6 +311,8 @@ export default class Olympic extends React.Component {
});
// 顶点变动之后需要更新,否则无法实现雨滴特效
points.geometry.verticesNeedUpdate = true;
+ let time = clock.getDelta();
+ mixer && mixer.update(time);
}
// 增加点击事件,声明raycaster和mouse变量
@@ -280,7 +335,15 @@ export default class Olympic extends React.Component {
render () {
return (
-
+
+
+ {this.state.loadingProcess === 100 ? '' : (
+
+
{this.state.loadingProcess} %
+
+ )
+ }
+
)
}
}
\ No newline at end of file
diff --git a/src/containers/Olympic/libs/GLTFLoader.js b/src/containers/Olympic/libs/GLTFLoader.js
new file mode 100644
index 0000000..d9fe414
--- /dev/null
+++ b/src/containers/Olympic/libs/GLTFLoader.js
@@ -0,0 +1,3286 @@
+/* eslint-disable */
+/**
+ * @author Rich Tibbett / https://github.com/richtr
+ * @author mrdoob / http://mrdoob.com/
+ * @author Tony Parisi / http://www.tonyparisi.com/
+ * @author Takahiro / https://github.com/takahirox
+ * @author Don McCurdy / https://www.donmccurdy.com
+ */
+
+THREE.GLTFLoader = ( function () {
+
+ function GLTFLoader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+ this.dracoLoader = null;
+
+ }
+
+ GLTFLoader.prototype = {
+
+ constructor: GLTFLoader,
+
+ crossOrigin: 'anonymous',
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var resourcePath;
+
+ if ( this.resourcePath !== undefined ) {
+
+ resourcePath = this.resourcePath;
+
+ } else if ( this.path !== undefined ) {
+
+ resourcePath = this.path;
+
+ } else {
+
+ resourcePath = THREE.LoaderUtils.extractUrlBase( url );
+
+ }
+
+ // Tells the LoadingManager to track an extra item, which resolves after
+ // the model is fully loaded. This means the count of items loaded will
+ // be incorrect, but ensures manager.onLoad() does not fire early.
+ scope.manager.itemStart( url );
+
+ var _onError = function ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemEnd( url );
+ scope.manager.itemError( url );
+
+ };
+
+ var loader = new THREE.FileLoader( scope.manager );
+
+ loader.setPath( this.path );
+ loader.setResponseType( 'arraybuffer' );
+
+ loader.load( url, function ( data ) {
+
+ try {
+
+ scope.parse( data, resourcePath, function ( gltf ) {
+
+ onLoad( gltf );
+
+ scope.manager.itemEnd( url );
+
+ }, _onError );
+
+ } catch ( e ) {
+
+ _onError( e );
+
+ }
+
+ }, onProgress, _onError );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+ return this;
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+ return this;
+
+ },
+
+ setResourcePath: function ( value ) {
+
+ this.resourcePath = value;
+ return this;
+
+ },
+
+ setDRACOLoader: function ( dracoLoader ) {
+
+ this.dracoLoader = dracoLoader;
+ return this;
+
+ },
+
+ parse: function ( data, path, onLoad, onError ) {
+
+ var content;
+ var extensions = {};
+
+ if ( typeof data === 'string' ) {
+
+ content = data;
+
+ } else {
+
+ var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
+
+ if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
+
+ try {
+
+ extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
+
+ } catch ( error ) {
+
+ if ( onError ) onError( error );
+ return;
+
+ }
+
+ content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
+
+ } else {
+
+ content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
+
+ }
+
+ }
+
+ var json = JSON.parse( content );
+
+ if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
+
+ if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) );
+ return;
+
+ }
+
+ if ( json.extensionsUsed ) {
+
+ for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
+
+ var extensionName = json.extensionsUsed[ i ];
+ var extensionsRequired = json.extensionsRequired || [];
+
+ switch ( extensionName ) {
+
+ case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
+ extensions[ extensionName ] = new GLTFLightsExtension( json );
+ break;
+
+ case EXTENSIONS.KHR_MATERIALS_UNLIT:
+ extensions[ extensionName ] = new GLTFMaterialsUnlitExtension( json );
+ break;
+
+ case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
+ extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
+ break;
+
+ case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
+ extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
+ break;
+
+ case EXTENSIONS.MSFT_TEXTURE_DDS:
+ extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension();
+ break;
+
+ default:
+
+ if ( extensionsRequired.indexOf( extensionName ) >= 0 ) {
+
+ console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ var parser = new GLTFParser( json, extensions, {
+
+ path: path || this.resourcePath || '',
+ crossOrigin: this.crossOrigin,
+ manager: this.manager
+
+ } );
+
+ parser.parse( function ( scene, scenes, cameras, animations, json ) {
+
+ var glTF = {
+ scene: scene,
+ scenes: scenes,
+ cameras: cameras,
+ animations: animations,
+ asset: json.asset,
+ parser: parser,
+ userData: {}
+ };
+
+ addUnknownExtensionsToUserData( extensions, glTF, json );
+
+ onLoad( glTF );
+
+ }, onError );
+
+ }
+
+ };
+
+ /* GLTFREGISTRY */
+
+ function GLTFRegistry() {
+
+ var objects = {};
+
+ return {
+
+ get: function ( key ) {
+
+ return objects[ key ];
+
+ },
+
+ add: function ( key, object ) {
+
+ objects[ key ] = object;
+
+ },
+
+ remove: function ( key ) {
+
+ delete objects[ key ];
+
+ },
+
+ removeAll: function () {
+
+ objects = {};
+
+ }
+
+ };
+
+ }
+
+ /*********************************/
+ /********** EXTENSIONS ***********/
+ /*********************************/
+
+ var EXTENSIONS = {
+ KHR_BINARY_GLTF: 'KHR_binary_glTF',
+ KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
+ KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
+ KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
+ KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
+ MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
+ };
+
+ /**
+ * DDS Texture Extension
+ *
+ * Specification:
+ * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
+ *
+ */
+ function GLTFTextureDDSExtension() {
+
+ if ( ! THREE.DDSLoader ) {
+
+ throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' );
+
+ }
+
+ this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
+ this.ddsLoader = new THREE.DDSLoader();
+
+ }
+
+ /**
+ * Lights Extension
+ *
+ * Specification: PENDING
+ */
+ function GLTFLightsExtension( json ) {
+
+ this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
+
+ var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
+ this.lightDefs = extension.lights || [];
+
+ }
+
+ GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
+
+ var lightDef = this.lightDefs[ lightIndex ];
+ var lightNode;
+
+ var color = new THREE.Color( 0xffffff );
+ if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
+
+ var range = lightDef.range !== undefined ? lightDef.range : 0;
+
+ switch ( lightDef.type ) {
+
+ case 'directional':
+ lightNode = new THREE.DirectionalLight( color );
+ lightNode.target.position.set( 0, 0, -1 );
+ lightNode.add( lightNode.target );
+ break;
+
+ case 'point':
+ lightNode = new THREE.PointLight( color );
+ lightNode.distance = range;
+ break;
+
+ case 'spot':
+ lightNode = new THREE.SpotLight( color );
+ lightNode.distance = range;
+ // Handle spotlight properties.
+ lightDef.spot = lightDef.spot || {};
+ lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
+ lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
+ lightNode.angle = lightDef.spot.outerConeAngle;
+ lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
+ lightNode.target.position.set( 0, 0, -1 );
+ lightNode.add( lightNode.target );
+ break;
+
+ default:
+ throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' );
+
+ }
+
+ lightNode.decay = 2;
+
+ if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
+
+ lightNode.name = lightDef.name || ( 'light_' + lightIndex );
+
+ return Promise.resolve( lightNode );
+
+ };
+
+ /**
+ * Unlit Materials Extension (pending)
+ *
+ * PR: https://github.com/KhronosGroup/glTF/pull/1163
+ */
+ function GLTFMaterialsUnlitExtension( json ) {
+
+ this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
+
+ }
+
+ GLTFMaterialsUnlitExtension.prototype.getMaterialType = function ( material ) {
+
+ return THREE.MeshBasicMaterial;
+
+ };
+
+ GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, material, parser ) {
+
+ var pending = [];
+
+ materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
+ materialParams.opacity = 1.0;
+
+ var metallicRoughness = material.pbrMetallicRoughness;
+
+ if ( metallicRoughness ) {
+
+ if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
+
+ var array = metallicRoughness.baseColorFactor;
+
+ materialParams.color.fromArray( array );
+ materialParams.opacity = array[ 3 ];
+
+ }
+
+ if ( metallicRoughness.baseColorTexture !== undefined ) {
+
+ pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) );
+
+ }
+
+ }
+
+ return Promise.all( pending );
+
+ };
+
+ /* BINARY EXTENSION */
+
+ var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
+ var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
+ var BINARY_EXTENSION_HEADER_LENGTH = 12;
+ var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
+
+ function GLTFBinaryExtension( data ) {
+
+ this.name = EXTENSIONS.KHR_BINARY_GLTF;
+ this.content = null;
+ this.body = null;
+
+ var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
+
+ this.header = {
+ magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
+ version: headerView.getUint32( 4, true ),
+ length: headerView.getUint32( 8, true )
+ };
+
+ if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
+
+ throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
+
+ } else if ( this.header.version < 2.0 ) {
+
+ throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' );
+
+ }
+
+ var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
+ var chunkIndex = 0;
+
+ while ( chunkIndex < chunkView.byteLength ) {
+
+ var chunkLength = chunkView.getUint32( chunkIndex, true );
+ chunkIndex += 4;
+
+ var chunkType = chunkView.getUint32( chunkIndex, true );
+ chunkIndex += 4;
+
+ if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
+
+ var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
+ this.content = THREE.LoaderUtils.decodeText( contentArray );
+
+ } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
+
+ var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
+ this.body = data.slice( byteOffset, byteOffset + chunkLength );
+
+ }
+
+ // Clients must ignore chunks with unknown types.
+
+ chunkIndex += chunkLength;
+
+ }
+
+ if ( this.content === null ) {
+
+ throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
+
+ }
+
+ }
+
+ /**
+ * DRACO Mesh Compression Extension
+ *
+ * Specification: https://github.com/KhronosGroup/glTF/pull/874
+ */
+ function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
+
+ if ( ! dracoLoader ) {
+
+ throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
+
+ }
+
+ this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
+ this.json = json;
+ this.dracoLoader = dracoLoader;
+
+ }
+
+ GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
+
+ var json = this.json;
+ var dracoLoader = this.dracoLoader;
+ var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
+ var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
+ var threeAttributeMap = {};
+ var attributeNormalizedMap = {};
+ var attributeTypeMap = {};
+
+ for ( var attributeName in gltfAttributeMap ) {
+
+ if ( ! ( attributeName in ATTRIBUTES ) ) continue;
+
+ threeAttributeMap[ ATTRIBUTES[ attributeName ] ] = gltfAttributeMap[ attributeName ];
+
+ }
+
+ for ( attributeName in primitive.attributes ) {
+
+ if ( ATTRIBUTES[ attributeName ] !== undefined && gltfAttributeMap[ attributeName ] !== undefined ) {
+
+ var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
+ var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
+
+ attributeTypeMap[ ATTRIBUTES[ attributeName ] ] = componentType;
+ attributeNormalizedMap[ ATTRIBUTES[ attributeName ] ] = accessorDef.normalized === true;
+
+ }
+
+ }
+
+ return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
+
+ return new Promise( function ( resolve ) {
+
+ dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
+
+ for ( var attributeName in geometry.attributes ) {
+
+ var attribute = geometry.attributes[ attributeName ];
+ var normalized = attributeNormalizedMap[ attributeName ];
+
+ if ( normalized !== undefined ) attribute.normalized = normalized;
+
+ }
+
+ resolve( geometry );
+
+ }, threeAttributeMap, attributeTypeMap );
+
+ } );
+
+ } );
+
+ };
+
+ /**
+ * Specular-Glossiness Extension
+ *
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
+ */
+ function GLTFMaterialsPbrSpecularGlossinessExtension() {
+
+ return {
+
+ name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
+
+ specularGlossinessParams: [
+ 'color',
+ 'map',
+ 'lightMap',
+ 'lightMapIntensity',
+ 'aoMap',
+ 'aoMapIntensity',
+ 'emissive',
+ 'emissiveIntensity',
+ 'emissiveMap',
+ 'bumpMap',
+ 'bumpScale',
+ 'normalMap',
+ 'displacementMap',
+ 'displacementScale',
+ 'displacementBias',
+ 'specularMap',
+ 'specular',
+ 'glossinessMap',
+ 'glossiness',
+ 'alphaMap',
+ 'envMap',
+ 'envMapIntensity',
+ 'refractionRatio',
+ ],
+
+ getMaterialType: function () {
+
+ return THREE.ShaderMaterial;
+
+ },
+
+ extendParams: function ( params, material, parser ) {
+
+ var pbrSpecularGlossiness = material.extensions[ this.name ];
+
+ var shader = THREE.ShaderLib[ 'standard' ];
+
+ var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+ var specularMapParsFragmentChunk = [
+ '#ifdef USE_SPECULARMAP',
+ ' uniform sampler2D specularMap;',
+ '#endif'
+ ].join( '\n' );
+
+ var glossinessMapParsFragmentChunk = [
+ '#ifdef USE_GLOSSINESSMAP',
+ ' uniform sampler2D glossinessMap;',
+ '#endif'
+ ].join( '\n' );
+
+ var specularMapFragmentChunk = [
+ 'vec3 specularFactor = specular;',
+ '#ifdef USE_SPECULARMAP',
+ ' vec4 texelSpecular = texture2D( specularMap, vUv );',
+ ' texelSpecular = sRGBToLinear( texelSpecular );',
+ ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
+ ' specularFactor *= texelSpecular.rgb;',
+ '#endif'
+ ].join( '\n' );
+
+ var glossinessMapFragmentChunk = [
+ 'float glossinessFactor = glossiness;',
+ '#ifdef USE_GLOSSINESSMAP',
+ ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
+ ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
+ ' glossinessFactor *= texelGlossiness.a;',
+ '#endif'
+ ].join( '\n' );
+
+ var lightPhysicalFragmentChunk = [
+ 'PhysicalMaterial material;',
+ 'material.diffuseColor = diffuseColor.rgb;',
+ 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );',
+ 'material.specularColor = specularFactor.rgb;',
+ ].join( '\n' );
+
+ var fragmentShader = shader.fragmentShader
+ .replace( 'uniform float roughness;', 'uniform vec3 specular;' )
+ .replace( 'uniform float metalness;', 'uniform float glossiness;' )
+ .replace( '#include ', specularMapParsFragmentChunk )
+ .replace( '#include ', glossinessMapParsFragmentChunk )
+ .replace( '#include ', specularMapFragmentChunk )
+ .replace( '#include ', glossinessMapFragmentChunk )
+ .replace( '#include ', lightPhysicalFragmentChunk );
+
+ delete uniforms.roughness;
+ delete uniforms.metalness;
+ delete uniforms.roughnessMap;
+ delete uniforms.metalnessMap;
+
+ uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) };
+ uniforms.glossiness = { value: 0.5 };
+ uniforms.specularMap = { value: null };
+ uniforms.glossinessMap = { value: null };
+
+ params.vertexShader = shader.vertexShader;
+ params.fragmentShader = fragmentShader;
+ params.uniforms = uniforms;
+ params.defines = { 'STANDARD': '' };
+
+ params.color = new THREE.Color( 1.0, 1.0, 1.0 );
+ params.opacity = 1.0;
+
+ var pending = [];
+
+ if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
+
+ var array = pbrSpecularGlossiness.diffuseFactor;
+
+ params.color.fromArray( array );
+ params.opacity = array[ 3 ];
+
+ }
+
+ if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
+
+ pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture.index ) );
+
+ }
+
+ params.emissive = new THREE.Color( 0.0, 0.0, 0.0 );
+ params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
+ params.specular = new THREE.Color( 1.0, 1.0, 1.0 );
+
+ if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
+
+ params.specular.fromArray( pbrSpecularGlossiness.specularFactor );
+
+ }
+
+ if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
+
+ var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index;
+ pending.push( parser.assignTexture( params, 'glossinessMap', specGlossIndex ) );
+ pending.push( parser.assignTexture( params, 'specularMap', specGlossIndex ) );
+
+ }
+
+ return Promise.all( pending );
+
+ },
+
+ createMaterial: function ( params ) {
+
+ // setup material properties based on MeshStandardMaterial for Specular-Glossiness
+
+ var material = new THREE.ShaderMaterial( {
+ defines: params.defines,
+ vertexShader: params.vertexShader,
+ fragmentShader: params.fragmentShader,
+ uniforms: params.uniforms,
+ fog: true,
+ lights: true,
+ opacity: params.opacity,
+ transparent: params.transparent
+ } );
+
+ material.isGLTFSpecularGlossinessMaterial = true;
+
+ material.color = params.color;
+
+ material.map = params.map === undefined ? null : params.map;
+
+ material.lightMap = null;
+ material.lightMapIntensity = 1.0;
+
+ material.aoMap = params.aoMap === undefined ? null : params.aoMap;
+ material.aoMapIntensity = 1.0;
+
+ material.emissive = params.emissive;
+ material.emissiveIntensity = 1.0;
+ material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap;
+
+ material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap;
+ material.bumpScale = 1;
+
+ material.normalMap = params.normalMap === undefined ? null : params.normalMap;
+ if ( params.normalScale ) material.normalScale = params.normalScale;
+
+ material.displacementMap = null;
+ material.displacementScale = 1;
+ material.displacementBias = 0;
+
+ material.specularMap = params.specularMap === undefined ? null : params.specularMap;
+ material.specular = params.specular;
+
+ material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap;
+ material.glossiness = params.glossiness;
+
+ material.alphaMap = null;
+
+ material.envMap = params.envMap === undefined ? null : params.envMap;
+ material.envMapIntensity = 1.0;
+
+ material.refractionRatio = 0.98;
+
+ material.extensions.derivatives = true;
+
+ return material;
+
+ },
+
+ /**
+ * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can
+ * copy only properties it knows about or inherits, and misses many properties that would
+ * normally be defined by MeshStandardMaterial.
+ *
+ * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of
+ * loading a glTF model, but cloning later (e.g. by the user) would require these changes
+ * AND also updating `.onBeforeRender` on the parent mesh.
+ *
+ * @param {THREE.ShaderMaterial} source
+ * @return {THREE.ShaderMaterial}
+ */
+ cloneMaterial: function ( source ) {
+
+ var target = source.clone();
+
+ target.isGLTFSpecularGlossinessMaterial = true;
+
+ var params = this.specularGlossinessParams;
+
+ for ( var i = 0, il = params.length; i < il; i ++ ) {
+
+ target[ params[ i ] ] = source[ params[ i ] ];
+
+ }
+
+ return target;
+
+ },
+
+ // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer.
+ refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) {
+
+ if ( material.isGLTFSpecularGlossinessMaterial !== true ) {
+
+ return;
+
+ }
+
+ var uniforms = material.uniforms;
+ var defines = material.defines;
+
+ uniforms.opacity.value = material.opacity;
+
+ uniforms.diffuse.value.copy( material.color );
+ uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
+
+ uniforms.map.value = material.map;
+ uniforms.specularMap.value = material.specularMap;
+ uniforms.alphaMap.value = material.alphaMap;
+
+ uniforms.lightMap.value = material.lightMap;
+ uniforms.lightMapIntensity.value = material.lightMapIntensity;
+
+ uniforms.aoMap.value = material.aoMap;
+ uniforms.aoMapIntensity.value = material.aoMapIntensity;
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. specular map
+ // 3. normal map
+ // 4. bump map
+ // 5. alpha map
+ // 6. emissive map
+
+ var uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.specularMap ) {
+
+ uvScaleMap = material.specularMap;
+
+ } else if ( material.displacementMap ) {
+
+ uvScaleMap = material.displacementMap;
+
+ } else if ( material.normalMap ) {
+
+ uvScaleMap = material.normalMap;
+
+ } else if ( material.bumpMap ) {
+
+ uvScaleMap = material.bumpMap;
+
+ } else if ( material.glossinessMap ) {
+
+ uvScaleMap = material.glossinessMap;
+
+ } else if ( material.alphaMap ) {
+
+ uvScaleMap = material.alphaMap;
+
+ } else if ( material.emissiveMap ) {
+
+ uvScaleMap = material.emissiveMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ // backwards compatibility
+ if ( uvScaleMap.isWebGLRenderTarget ) {
+
+ uvScaleMap = uvScaleMap.texture;
+
+ }
+
+ if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+ uvScaleMap.updateMatrix();
+
+ }
+
+ uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+ }
+
+ uniforms.envMap.value = material.envMap;
+ uniforms.envMapIntensity.value = material.envMapIntensity;
+ uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? - 1 : 1;
+
+ uniforms.refractionRatio.value = material.refractionRatio;
+
+ uniforms.specular.value.copy( material.specular );
+ uniforms.glossiness.value = material.glossiness;
+
+ uniforms.glossinessMap.value = material.glossinessMap;
+
+ uniforms.emissiveMap.value = material.emissiveMap;
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.normalMap.value = material.normalMap;
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) {
+
+ defines.USE_GLOSSINESSMAP = '';
+ // set USE_ROUGHNESSMAP to enable vUv
+ defines.USE_ROUGHNESSMAP = '';
+
+ }
+
+ if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) {
+
+ delete defines.USE_GLOSSINESSMAP;
+ delete defines.USE_ROUGHNESSMAP;
+
+ }
+
+ }
+
+ };
+
+ }
+
+ /*********************************/
+ /********** INTERPOLATION ********/
+ /*********************************/
+
+ // Spline Interpolation
+ // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
+ function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+ }
+
+ GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype );
+ GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
+
+ GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
+
+ // Copies a sample value to the result buffer. See description of glTF
+ // CUBICSPLINE values layout in interpolate_() function below.
+
+ var result = this.resultBuffer,
+ values = this.sampleValues,
+ valueSize = this.valueSize,
+ offset = index * valueSize * 3 + valueSize;
+
+ for ( var i = 0; i !== valueSize; i ++ ) {
+
+ result[ i ] = values[ offset + i ];
+
+ }
+
+ return result;
+
+ };
+
+ GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
+
+ GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
+
+ GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
+
+ var result = this.resultBuffer;
+ var values = this.sampleValues;
+ var stride = this.valueSize;
+
+ var stride2 = stride * 2;
+ var stride3 = stride * 3;
+
+ var td = t1 - t0;
+
+ var p = ( t - t0 ) / td;
+ var pp = p * p;
+ var ppp = pp * p;
+
+ var offset1 = i1 * stride3;
+ var offset0 = offset1 - stride3;
+
+ var s0 = 2 * ppp - 3 * pp + 1;
+ var s1 = ppp - 2 * pp + p;
+ var s2 = - 2 * ppp + 3 * pp;
+ var s3 = ppp - pp;
+
+ // Layout of keyframe output values for CUBICSPLINE animations:
+ // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
+ for ( var i = 0; i !== stride; i ++ ) {
+
+ var p0 = values[ offset0 + i + stride ]; // splineVertex_k
+ var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
+ var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
+ var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
+
+ result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
+
+ }
+
+ return result;
+
+ };
+
+ /*********************************/
+ /********** INTERNALS ************/
+ /*********************************/
+
+ /* CONSTANTS */
+
+ var WEBGL_CONSTANTS = {
+ FLOAT: 5126,
+ //FLOAT_MAT2: 35674,
+ FLOAT_MAT3: 35675,
+ FLOAT_MAT4: 35676,
+ FLOAT_VEC2: 35664,
+ FLOAT_VEC3: 35665,
+ FLOAT_VEC4: 35666,
+ LINEAR: 9729,
+ REPEAT: 10497,
+ SAMPLER_2D: 35678,
+ POINTS: 0,
+ LINES: 1,
+ LINE_LOOP: 2,
+ LINE_STRIP: 3,
+ TRIANGLES: 4,
+ TRIANGLE_STRIP: 5,
+ TRIANGLE_FAN: 6,
+ UNSIGNED_BYTE: 5121,
+ UNSIGNED_SHORT: 5123
+ };
+
+ var WEBGL_TYPE = {
+ 5126: Number,
+ //35674: THREE.Matrix2,
+ 35675: THREE.Matrix3,
+ 35676: THREE.Matrix4,
+ 35664: THREE.Vector2,
+ 35665: THREE.Vector3,
+ 35666: THREE.Vector4,
+ 35678: THREE.Texture
+ };
+
+ var WEBGL_COMPONENT_TYPES = {
+ 5120: Int8Array,
+ 5121: Uint8Array,
+ 5122: Int16Array,
+ 5123: Uint16Array,
+ 5125: Uint32Array,
+ 5126: Float32Array
+ };
+
+ var WEBGL_FILTERS = {
+ 9728: THREE.NearestFilter,
+ 9729: THREE.LinearFilter,
+ 9984: THREE.NearestMipMapNearestFilter,
+ 9985: THREE.LinearMipMapNearestFilter,
+ 9986: THREE.NearestMipMapLinearFilter,
+ 9987: THREE.LinearMipMapLinearFilter
+ };
+
+ var WEBGL_WRAPPINGS = {
+ 33071: THREE.ClampToEdgeWrapping,
+ 33648: THREE.MirroredRepeatWrapping,
+ 10497: THREE.RepeatWrapping
+ };
+
+ var WEBGL_SIDES = {
+ 1028: THREE.BackSide, // Culling front
+ 1029: THREE.FrontSide // Culling back
+ //1032: THREE.NoSide // Culling front and back, what to do?
+ };
+
+ var WEBGL_DEPTH_FUNCS = {
+ 512: THREE.NeverDepth,
+ 513: THREE.LessDepth,
+ 514: THREE.EqualDepth,
+ 515: THREE.LessEqualDepth,
+ 516: THREE.GreaterEqualDepth,
+ 517: THREE.NotEqualDepth,
+ 518: THREE.GreaterEqualDepth,
+ 519: THREE.AlwaysDepth
+ };
+
+ var WEBGL_BLEND_EQUATIONS = {
+ 32774: THREE.AddEquation,
+ 32778: THREE.SubtractEquation,
+ 32779: THREE.ReverseSubtractEquation
+ };
+
+ var WEBGL_BLEND_FUNCS = {
+ 0: THREE.ZeroFactor,
+ 1: THREE.OneFactor,
+ 768: THREE.SrcColorFactor,
+ 769: THREE.OneMinusSrcColorFactor,
+ 770: THREE.SrcAlphaFactor,
+ 771: THREE.OneMinusSrcAlphaFactor,
+ 772: THREE.DstAlphaFactor,
+ 773: THREE.OneMinusDstAlphaFactor,
+ 774: THREE.DstColorFactor,
+ 775: THREE.OneMinusDstColorFactor,
+ 776: THREE.SrcAlphaSaturateFactor
+ // The followings are not supported by Three.js yet
+ //32769: CONSTANT_COLOR,
+ //32770: ONE_MINUS_CONSTANT_COLOR,
+ //32771: CONSTANT_ALPHA,
+ //32772: ONE_MINUS_CONSTANT_COLOR
+ };
+
+ var WEBGL_TYPE_SIZES = {
+ 'SCALAR': 1,
+ 'VEC2': 2,
+ 'VEC3': 3,
+ 'VEC4': 4,
+ 'MAT2': 4,
+ 'MAT3': 9,
+ 'MAT4': 16
+ };
+
+ var ATTRIBUTES = {
+ POSITION: 'position',
+ NORMAL: 'normal',
+ TEXCOORD_0: 'uv',
+ TEXCOORD0: 'uv', // deprecated
+ TEXCOORD: 'uv', // deprecated
+ TEXCOORD_1: 'uv2',
+ COLOR_0: 'color',
+ COLOR0: 'color', // deprecated
+ COLOR: 'color', // deprecated
+ WEIGHTS_0: 'skinWeight',
+ WEIGHT: 'skinWeight', // deprecated
+ JOINTS_0: 'skinIndex',
+ JOINT: 'skinIndex' // deprecated
+ };
+
+ var PATH_PROPERTIES = {
+ scale: 'scale',
+ translation: 'position',
+ rotation: 'quaternion',
+ weights: 'morphTargetInfluences'
+ };
+
+ var INTERPOLATION = {
+ CUBICSPLINE: THREE.InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE.
+ // KeyframeTrack.optimize() can't handle glTF Cubic Spline output values layout,
+ // using THREE.InterpolateSmooth for KeyframeTrack instantiation to prevent optimization.
+ // See KeyframeTrack.optimize() for the detail.
+ LINEAR: THREE.InterpolateLinear,
+ STEP: THREE.InterpolateDiscrete
+ };
+
+ var STATES_ENABLES = {
+ 2884: 'CULL_FACE',
+ 2929: 'DEPTH_TEST',
+ 3042: 'BLEND',
+ 3089: 'SCISSOR_TEST',
+ 32823: 'POLYGON_OFFSET_FILL',
+ 32926: 'SAMPLE_ALPHA_TO_COVERAGE'
+ };
+
+ var ALPHA_MODES = {
+ OPAQUE: 'OPAQUE',
+ MASK: 'MASK',
+ BLEND: 'BLEND'
+ };
+
+ var MIME_TYPE_FORMATS = {
+ 'image/png': THREE.RGBAFormat,
+ 'image/jpeg': THREE.RGBFormat
+ };
+
+ /* UTILITY FUNCTIONS */
+
+ function resolveURL( url, path ) {
+
+ // Invalid URL
+ if ( typeof url !== 'string' || url === '' ) return '';
+
+ // Absolute URL http://,https://,//
+ if ( /^(https?:)?\/\//i.test( url ) ) return url;
+
+ // Data URI
+ if ( /^data:.*,.*$/i.test( url ) ) return url;
+
+ // Blob URL
+ if ( /^blob:.*$/i.test( url ) ) return url;
+
+ // Relative URL
+ return path + url;
+
+ }
+
+ /**
+ * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
+ */
+ function createDefaultMaterial() {
+
+ return new THREE.MeshStandardMaterial( {
+ color: 0xFFFFFF,
+ emissive: 0x000000,
+ metalness: 1,
+ roughness: 1,
+ transparent: false,
+ depthTest: true,
+ side: THREE.FrontSide
+ } );
+
+ }
+
+ function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
+
+ // Add unknown glTF extensions to an object's userData.
+
+ for ( var name in objectDef.extensions ) {
+
+ if ( knownExtensions[ name ] === undefined ) {
+
+ object.userData.gltfExtensions = object.userData.gltfExtensions || {};
+ object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
+
+ }
+
+ }
+
+ }
+
+ /**
+ * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object
+ * @param {GLTF.definition} gltfDef
+ */
+ function assignExtrasToUserData( object, gltfDef ) {
+
+ if ( gltfDef.extras !== undefined ) {
+
+ if ( typeof gltfDef.extras === 'object' ) {
+
+ object.userData = gltfDef.extras;
+
+ } else {
+
+ console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
+ *
+ * @param {THREE.BufferGeometry} geometry
+ * @param {Array} targets
+ * @param {GLTFParser} parser
+ * @return {Promise}
+ */
+ function addMorphTargets( geometry, targets, parser ) {
+
+ var hasMorphPosition = false;
+ var hasMorphNormal = false;
+
+ for ( var i = 0, il = targets.length; i < il; i ++ ) {
+
+ var target = targets[ i ];
+
+ if ( target.POSITION !== undefined ) hasMorphPosition = true;
+ if ( target.NORMAL !== undefined ) hasMorphNormal = true;
+
+ if ( hasMorphPosition && hasMorphNormal ) break;
+
+ }
+
+ if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
+
+ var pendingPositionAccessors = [];
+ var pendingNormalAccessors = [];
+
+ for ( var i = 0, il = targets.length; i < il; i ++ ) {
+
+ var target = targets[ i ];
+
+ if ( hasMorphPosition ) {
+
+ var accessor = target.POSITION !== undefined
+ ? parser.getDependency( 'accessor', target.POSITION )
+ .then( function ( accessor ) {
+ // Cloning not to pollute original accessor below
+ return cloneBufferAttribute( accessor );
+ } )
+ : geometry.attributes.position;
+
+ pendingPositionAccessors.push( accessor );
+
+ }
+
+ if ( hasMorphNormal ) {
+
+ var accessor = target.NORMAL !== undefined
+ ? parser.getDependency( 'accessor', target.NORMAL )
+ .then( function ( accessor ) {
+ return cloneBufferAttribute( accessor );
+ } )
+ : geometry.attributes.normal;
+
+ pendingNormalAccessors.push( accessor );
+
+ }
+
+ }
+
+ return Promise.all( [
+ Promise.all( pendingPositionAccessors ),
+ Promise.all( pendingNormalAccessors )
+ ] ).then( function ( accessors ) {
+
+ var morphPositions = accessors[ 0 ];
+ var morphNormals = accessors[ 1 ];
+
+ for ( var i = 0, il = targets.length; i < il; i ++ ) {
+
+ var target = targets[ i ];
+ var attributeName = 'morphTarget' + i;
+
+ if ( hasMorphPosition ) {
+
+ // Three.js morph position is absolute value. The formula is
+ // basePosition
+ // + weight0 * ( morphPosition0 - basePosition )
+ // + weight1 * ( morphPosition1 - basePosition )
+ // ...
+ // while the glTF one is relative
+ // basePosition
+ // + weight0 * glTFmorphPosition0
+ // + weight1 * glTFmorphPosition1
+ // ...
+ // then we need to convert from relative to absolute here.
+
+ if ( target.POSITION !== undefined ) {
+
+ var positionAttribute = morphPositions[ i ];
+ positionAttribute.name = attributeName;
+
+ var position = geometry.attributes.position;
+
+ for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) {
+
+ positionAttribute.setXYZ(
+ j,
+ positionAttribute.getX( j ) + position.getX( j ),
+ positionAttribute.getY( j ) + position.getY( j ),
+ positionAttribute.getZ( j ) + position.getZ( j )
+ );
+
+ }
+
+ }
+
+ }
+
+ if ( hasMorphNormal ) {
+
+ // see target.POSITION's comment
+
+ if ( target.NORMAL !== undefined ) {
+
+ var normalAttribute = morphNormals[ i ];
+ normalAttribute.name = attributeName;
+
+ var normal = geometry.attributes.normal;
+
+ for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) {
+
+ normalAttribute.setXYZ(
+ j,
+ normalAttribute.getX( j ) + normal.getX( j ),
+ normalAttribute.getY( j ) + normal.getY( j ),
+ normalAttribute.getZ( j ) + normal.getZ( j )
+ );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
+ if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
+
+ return geometry;
+
+ } );
+
+ }
+
+ /**
+ * @param {THREE.Mesh} mesh
+ * @param {GLTF.Mesh} meshDef
+ */
+ function updateMorphTargets( mesh, meshDef ) {
+
+ mesh.updateMorphTargets();
+
+ if ( meshDef.weights !== undefined ) {
+
+ for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
+
+ mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
+
+ }
+
+ }
+
+ // .extras has user-defined data, so check that .extras.targetNames is an array.
+ if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
+
+ var targetNames = meshDef.extras.targetNames;
+
+ if ( mesh.morphTargetInfluences.length === targetNames.length ) {
+
+ mesh.morphTargetDictionary = {};
+
+ for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
+
+ mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
+
+ }
+
+ } else {
+
+ console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
+
+ }
+
+ }
+
+ }
+
+ function isPrimitiveEqual( a, b ) {
+
+ if ( a.indices !== b.indices ) {
+
+ return false;
+
+ }
+
+ return isObjectEqual( a.attributes, b.attributes );
+
+ }
+
+ function isObjectEqual( a, b ) {
+
+ if ( Object.keys( a ).length !== Object.keys( b ).length ) return false;
+
+ for ( var key in a ) {
+
+ if ( a[ key ] !== b[ key ] ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ function isArrayEqual( a, b ) {
+
+ if ( a.length !== b.length ) return false;
+
+ for ( var i = 0, il = a.length; i < il; i ++ ) {
+
+ if ( a[ i ] !== b[ i ] ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ function getCachedGeometry( cache, newPrimitive ) {
+
+ for ( var i = 0, il = cache.length; i < il; i ++ ) {
+
+ var cached = cache[ i ];
+
+ if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) return cached.promise;
+
+ }
+
+ return null;
+
+ }
+
+ function getCachedCombinedGeometry( cache, geometries ) {
+
+ for ( var i = 0, il = cache.length; i < il; i ++ ) {
+
+ var cached = cache[ i ];
+
+ if ( isArrayEqual( geometries, cached.baseGeometries ) ) return cached.geometry;
+
+ }
+
+ return null;
+
+ }
+
+ function getCachedMultiPassGeometry( cache, geometry, primitives ) {
+
+ for ( var i = 0, il = cache.length; i < il; i ++ ) {
+
+ var cached = cache[ i ];
+
+ if ( geometry === cached.baseGeometry && isArrayEqual( primitives, cached.primitives ) ) return cached.geometry;
+
+ }
+
+ return null;
+
+ }
+
+ function cloneBufferAttribute( attribute ) {
+
+ if ( attribute.isInterleavedBufferAttribute ) {
+
+ var count = attribute.count;
+ var itemSize = attribute.itemSize;
+ var array = attribute.array.slice( 0, count * itemSize );
+
+ for ( var i = 0; i < count; ++ i ) {
+
+ array[ i ] = attribute.getX( i );
+ if ( itemSize >= 2 ) array[ i + 1 ] = attribute.getY( i );
+ if ( itemSize >= 3 ) array[ i + 2 ] = attribute.getZ( i );
+ if ( itemSize >= 4 ) array[ i + 3 ] = attribute.getW( i );
+
+ }
+
+ return new THREE.BufferAttribute( array, itemSize, attribute.normalized );
+
+ }
+
+ return attribute.clone();
+
+ }
+
+ /**
+ * Checks if we can build a single Mesh with MultiMaterial from multiple primitives.
+ * Returns true if all primitives use the same attributes/morphAttributes/mode
+ * and also have index. Otherwise returns false.
+ *
+ * @param {Array} primitives
+ * @return {Boolean}
+ */
+ function isMultiPassGeometry( primitives ) {
+
+ if ( primitives.length < 2 ) return false;
+
+ var primitive0 = primitives[ 0 ];
+ var targets0 = primitive0.targets || [];
+
+ if ( primitive0.indices === undefined ) return false;
+
+ for ( var i = 1, il = primitives.length; i < il; i ++ ) {
+
+ var primitive = primitives[ i ];
+
+ if ( primitive0.mode !== primitive.mode ) return false;
+ if ( primitive.indices === undefined ) return false;
+ if ( ! isObjectEqual( primitive0.attributes, primitive.attributes ) ) return false;
+
+ var targets = primitive.targets || [];
+
+ if ( targets0.length !== targets.length ) return false;
+
+ for ( var j = 0, jl = targets0.length; j < jl; j ++ ) {
+
+ if ( ! isObjectEqual( targets0[ j ], targets[ j ] ) ) return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ /* GLTF PARSER */
+
+ function GLTFParser( json, extensions, options ) {
+
+ this.json = json || {};
+ this.extensions = extensions || {};
+ this.options = options || {};
+
+ // loader object cache
+ this.cache = new GLTFRegistry();
+
+ // BufferGeometry caching
+ this.primitiveCache = [];
+ this.multiplePrimitivesCache = [];
+ this.multiPassGeometryCache = [];
+
+ this.textureLoader = new THREE.TextureLoader( this.options.manager );
+ this.textureLoader.setCrossOrigin( this.options.crossOrigin );
+
+ this.fileLoader = new THREE.FileLoader( this.options.manager );
+ this.fileLoader.setResponseType( 'arraybuffer' );
+
+ }
+
+ GLTFParser.prototype.parse = function ( onLoad, onError ) {
+
+ var json = this.json;
+
+ // Clear the loader cache
+ this.cache.removeAll();
+
+ // Mark the special nodes/meshes in json for efficient parse
+ this.markDefs();
+
+ // Fire the callback on complete
+ this.getMultiDependencies( [
+
+ 'scene',
+ 'animation',
+ 'camera'
+
+ ] ).then( function ( dependencies ) {
+
+ var scenes = dependencies.scenes || [];
+ var scene = scenes[ json.scene || 0 ];
+ var animations = dependencies.animations || [];
+ var cameras = dependencies.cameras || [];
+
+ onLoad( scene, scenes, cameras, animations, json );
+
+ } ).catch( onError );
+
+ };
+
+ /**
+ * Marks the special nodes/meshes in json for efficient parse.
+ */
+ GLTFParser.prototype.markDefs = function () {
+
+ var nodeDefs = this.json.nodes || [];
+ var skinDefs = this.json.skins || [];
+ var meshDefs = this.json.meshes || [];
+
+ var meshReferences = {};
+ var meshUses = {};
+
+ // Nothing in the node definition indicates whether it is a Bone or an
+ // Object3D. Use the skins' joint references to mark bones.
+ for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
+
+ var joints = skinDefs[ skinIndex ].joints;
+
+ for ( var i = 0, il = joints.length; i < il; i ++ ) {
+
+ nodeDefs[ joints[ i ] ].isBone = true;
+
+ }
+
+ }
+
+ // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
+ // avoid having more than one THREE.Mesh with the same name, count
+ // references and rename instances below.
+ //
+ // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
+ for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
+
+ var nodeDef = nodeDefs[ nodeIndex ];
+
+ if ( nodeDef.mesh !== undefined ) {
+
+ if ( meshReferences[ nodeDef.mesh ] === undefined ) {
+
+ meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0;
+
+ }
+
+ meshReferences[ nodeDef.mesh ] ++;
+
+ // Nothing in the mesh definition indicates whether it is
+ // a SkinnedMesh or Mesh. Use the node's mesh reference
+ // to mark SkinnedMesh if node has skin.
+ if ( nodeDef.skin !== undefined ) {
+
+ meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
+
+ }
+
+ }
+
+ }
+
+ this.json.meshReferences = meshReferences;
+ this.json.meshUses = meshUses;
+
+ };
+
+ /**
+ * Requests the specified dependency asynchronously, with caching.
+ * @param {string} type
+ * @param {number} index
+ * @return {Promise}
+ */
+ GLTFParser.prototype.getDependency = function ( type, index ) {
+
+ var cacheKey = type + ':' + index;
+ var dependency = this.cache.get( cacheKey );
+
+ if ( ! dependency ) {
+
+ switch ( type ) {
+
+ case 'scene':
+ dependency = this.loadScene( index );
+ break;
+
+ case 'node':
+ dependency = this.loadNode( index );
+ break;
+
+ case 'mesh':
+ dependency = this.loadMesh( index );
+ break;
+
+ case 'accessor':
+ dependency = this.loadAccessor( index );
+ break;
+
+ case 'bufferView':
+ dependency = this.loadBufferView( index );
+ break;
+
+ case 'buffer':
+ dependency = this.loadBuffer( index );
+ break;
+
+ case 'material':
+ dependency = this.loadMaterial( index );
+ break;
+
+ case 'texture':
+ dependency = this.loadTexture( index );
+ break;
+
+ case 'skin':
+ dependency = this.loadSkin( index );
+ break;
+
+ case 'animation':
+ dependency = this.loadAnimation( index );
+ break;
+
+ case 'camera':
+ dependency = this.loadCamera( index );
+ break;
+
+ case 'light':
+ dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
+ break
+
+ default:
+ throw new Error( 'Unknown type: ' + type );
+
+ }
+
+ this.cache.add( cacheKey, dependency );
+
+ }
+
+ return dependency;
+
+ };
+
+ /**
+ * Requests all dependencies of the specified type asynchronously, with caching.
+ * @param {string} type
+ * @return {Promise>}
+ */
+ GLTFParser.prototype.getDependencies = function ( type ) {
+
+ var dependencies = this.cache.get( type );
+
+ if ( ! dependencies ) {
+
+ var parser = this;
+ var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
+
+ dependencies = Promise.all( defs.map( function ( def, index ) {
+
+ return parser.getDependency( type, index );
+
+ } ) );
+
+ this.cache.add( type, dependencies );
+
+ }
+
+ return dependencies;
+
+ };
+
+ /**
+ * Requests all multiple dependencies of the specified types asynchronously, with caching.
+ * @param {Array} types
+ * @return {Promise