Browse Source

GLTFLoader: Fix node parse (#25377)

* GLTFLoader: Fix node parse

This commit fixes a GLTFLoader node parse bug that
can cause an infinite loop in node parse - skin
parse - node parse.

The solution is adding ._loadNode() that parses a
single node without child nodes or skin to the parser.
.loadNode() and .loadSkin() call ._loadNode() to
get dependent nodes. ._loadNode() doesn't have
dependency with other nodes so infinite loop can
be avoided.

._loadNode() caches created nodes to avoid duplication
because it is called from two different methods,
.loadNode() and .loadSkin().

* GLTFLoader: Rename _loadNode to _loadNodeShallow

This commit renames _loadNode to _loadNodeShallow.

The behavior of the method is more clarified by
the new name so remove a commend that sounds like
duplicated with the method name (and also sounds
like a bit misleading).
Takahiro 2 years ago
parent
commit
68d4bd96cd
1 changed files with 90 additions and 49 deletions
  1. 90 49
      examples/jsm/loaders/GLTFLoader.js

+ 90 - 49
examples/jsm/loaders/GLTFLoader.js

@@ -2344,6 +2344,9 @@ class GLTFParser {
 		// BufferGeometry caching
 		this.primitiveCache = {};
 
+		// Node cache
+		this.nodeCache = {};
+
 		// Object3D instance caches
 		this.meshCache = { refs: {}, uses: {} };
 		this.cameraCache = { refs: {}, uses: {} };
@@ -2414,6 +2417,7 @@ class GLTFParser {
 
 		// Clear the loader cache
 		this.cache.removeAll();
+		this.nodeCache = {};
 
 		// Mark the special nodes/meshes in json for efficient parse
 		this._invokeAll( function ( ext ) {
@@ -3702,7 +3706,7 @@ class GLTFParser {
 
 		for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) {
 
-			pending.push( this.getDependency( 'node', skinDef.joints[ i ] ) );
+			pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) );
 
 		}
 
@@ -3721,6 +3725,9 @@ class GLTFParser {
 			const inverseBindMatrices = results.pop();
 			const jointNodes = results;
 
+			// Note that bones (joint nodes) may or may not be in the
+			// scene graph at this time.
+
 			const bones = [];
 			const boneInverses = [];
 
@@ -3969,18 +3976,91 @@ class GLTFParser {
 	 */
 	loadNode( nodeIndex ) {
 
+		const json = this.json;
+		const parser = this;
+
+		const nodeDef = json.nodes[ nodeIndex ];
+
+		return ( function () {
+
+			const nodePending = parser._loadNodeShallow( nodeIndex );
+
+			const childPending = [];
+			const childrenDef = nodeDef.children || [];
+
+			for ( let i = 0, il = childrenDef.length; i < il; i ++ ) {
+
+				childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) );
+
+			}
+
+			const skeletonPending = nodeDef.skin === undefined
+				? Promise.resolve( null )
+				: parser.getDependency( 'skin', nodeDef.skin );
+
+			return Promise.all( [
+				nodePending,
+				Promise.all( childPending ),
+				skeletonPending
+			] );
+
+		}() ).then( function ( results ) {
+
+			const node = results[ 0 ];
+			const children = results[ 1 ];
+			const skeleton = results[ 2 ];
+
+			if ( skeleton !== null ) {
+
+				// This full traverse should be fine because
+				// child glTF nodes have not been added to this node yet.
+				node.traverse( function ( mesh ) {
+
+					if ( ! mesh.isSkinnedMesh ) return;
+
+					mesh.bind( skeleton, _identityMatrix );
+
+				} );
+
+			}
+
+			for ( let i = 0, il = children.length; i < il; i ++ ) {
+
+				node.add( children[ i ] );
+
+			}
+
+			return node;
+
+		} );
+
+	}
+
+	// ._loadNodeShallow() parses a single node.
+	// skin and child nodes are created and added in .loadNode() (no '_' prefix).
+	_loadNodeShallow( nodeIndex ) {
+
 		const json = this.json;
 		const extensions = this.extensions;
 		const parser = this;
 
+		// This method is called from .loadNode() and .loadSkin().
+		// Cache a node to avoid duplication.
+
+		if ( this.nodeCache[ nodeIndex ] !== undefined ) {
+
+			return this.nodeCache[ nodeIndex ];
+
+		}
+
 		const nodeDef = json.nodes[ nodeIndex ];
 
 		// reserve node's name before its dependencies, so the root has the intended name.
 		const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : '';
 
-		return ( function () {
+		this.nodeCache[ nodeIndex ] = ( function () {
 
-			const objectPending = [];
+			const pending = [];
 
 			const meshPromise = parser._invokeOne( function ( ext ) {
 
@@ -3990,13 +4070,13 @@ class GLTFParser {
 
 			if ( meshPromise ) {
 
-				objectPending.push( meshPromise );
+				pending.push( meshPromise );
 
 			}
 
 			if ( nodeDef.camera !== undefined ) {
 
-				objectPending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {
+				pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {
 
 					return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera );
 
@@ -4010,34 +4090,13 @@ class GLTFParser {
 
 			} ).forEach( function ( promise ) {
 
-				objectPending.push( promise );
+				pending.push( promise );
 
 			} );
 
-			const childPending = [];
-			const childrenDef = nodeDef.children || [];
-
-			for ( let i = 0, il = childrenDef.length; i < il; i ++ ) {
-
-				childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) );
-
-			}
-
-			const skeletonPending = nodeDef.skin === undefined
-				? Promise.resolve( null )
-				: parser.getDependency( 'skin', nodeDef.skin );
-
-			return Promise.all( [
-				Promise.all( objectPending ),
-				Promise.all( childPending ),
-				skeletonPending
-			] );
-
-		}() ).then( function ( results ) {
+			return Promise.all( pending );
 
-			const objects = results[ 0 ];
-			const children = results[ 1 ];
-			const skeleton = results[ 2 ];
+		}() ).then( function ( objects ) {
 
 			let node;
 
@@ -4117,30 +4176,12 @@ class GLTFParser {
 
 			parser.associations.get( node ).nodes = nodeIndex;
 
-			if ( skeleton !== null ) {
-
-				// This full traverse should be fine because
-				// child glTF nodes have not been added to this node yet.
-				node.traverse( function ( mesh ) {
-
-					if ( ! mesh.isSkinnedMesh ) return;
-
-					mesh.bind( skeleton, _identityMatrix );
-
-				} );
-
-			}
-
-			for ( let i = 0, il = children.length; i < il; i ++ ) {
-
-				node.add( children[ i ] );
-
-			}
-
 			return node;
 
 		} );
 
+		return this.nodeCache[ nodeIndex ];
+
 	}
 
 	/**