Skip to content

Commit

Permalink
Merge pull request #19799 from donmccurdy/bug-node-instancing
Browse files Browse the repository at this point in the history
GLTFLoader: Clone reused light and camera instances.
  • Loading branch information
mrdoob authored Jul 5, 2020
2 parents cc7852f + 14576a9 commit 0964f06
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 80 deletions.
114 changes: 74 additions & 40 deletions examples/js/loaders/GLTFLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,11 @@ THREE.GLTFLoader = ( function () {
// BufferGeometry caching
this.primitiveCache = {};

// Object3D instance caches
this.meshCache = {refs: {}, uses: {}};
this.cameraCache = {refs: {}, uses: {}};
this.lightCache = {refs: {}, uses: {}};

// Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the
// expensive work of uploading a texture to the GPU off the main thread.
if ( typeof createImageBitmap !== 'undefined' && /Firefox/.test( navigator.userAgent ) === false ) {
Expand Down Expand Up @@ -1515,7 +1520,7 @@ THREE.GLTFLoader = ( function () {
this.cache.removeAll();

// Mark the special nodes/meshes in json for efficient parse
this.markDefs();
this._markDefs();

Promise.all( [

Expand Down Expand Up @@ -1548,15 +1553,12 @@ THREE.GLTFLoader = ( function () {
/**
* Marks the special nodes/meshes in json for efficient parse.
*/
GLTFParser.prototype.markDefs = function () {
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 ++ ) {
Expand All @@ -1571,24 +1573,15 @@ THREE.GLTFLoader = ( function () {

}

// 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.
// Iterate over all nodes, marking references to shared resources,
// as well as skeleton joints.
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 ] ++;
this._addNodeRef( this.meshCache, nodeDef.mesh );

// Nothing in the mesh definition indicates whether it is
// a SkinnedMesh or Mesh. Use the node's mesh reference
Expand All @@ -1601,13 +1594,60 @@ THREE.GLTFLoader = ( function () {

}

if ( nodeDef.camera !== undefined ) {

this._addNodeRef( this.cameraCache, nodeDef.camera );

}

if ( nodeDef.extensions
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {

this._addNodeRef( this.lightCache, nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light );

}

}

this.json.meshReferences = meshReferences;
this.json.meshUses = meshUses;
};

/**
* Counts references to shared node / Object3D resources. These resources
* can be reused, or "instantiated", at multiple nodes in the scene
* hierarchy. Mesh, Camera, and Light instances are instantiated and must
* be marked. Non-scenegraph resources (like Materials, Geometries, and
* Textures) can be reused directly and are not marked here.
*
* Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
*/
GLTFParser.prototype._addNodeRef = function ( cache, index ) {

if ( index === undefined ) return;

if ( cache.refs[ index ] === undefined ) {

cache.refs[ index ] = cache.uses[ index ] = 0;

}

cache.refs[ index ] ++ ;

};

/** Returns a reference to a shared resource, cloning it if necessary. */
GLTFParser.prototype._getNodeRef = function ( cache, index, object ) {

if ( cache.refs[ index ] <= 1 ) return object;

var ref = object.clone();

ref.name += '_instance_' + ( cache.uses[ index ] ++ );

return ref;

}

GLTFParser.prototype._invokeOne = function ( func ) {

var extensions = Object.values( this.plugins );
Expand Down Expand Up @@ -2796,7 +2836,7 @@ THREE.GLTFLoader = ( function () {
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
primitive.mode === undefined ) {

// .isSkinnedMesh isn't in glTF spec. See .markDefs()
// .isSkinnedMesh isn't in glTF spec. See ._markDefs()
mesh = meshDef.isSkinnedMesh === true
? new THREE.SkinnedMesh( geometry, material )
: new THREE.Mesh( geometry, material );
Expand Down Expand Up @@ -3147,9 +3187,6 @@ THREE.GLTFLoader = ( function () {
var extensions = this.extensions;
var parser = this;

var meshReferences = json.meshReferences;
var meshUses = json.meshUses;

var nodeDef = json.nodes[ nodeIndex ];

return ( function () {
Expand All @@ -3160,20 +3197,7 @@ THREE.GLTFLoader = ( function () {

pending.push( parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) {

var node;

if ( meshReferences[ nodeDef.mesh ] > 1 ) {

var instanceNum = meshUses[ nodeDef.mesh ] ++;

node = mesh.clone();
node.name += '_instance_' + instanceNum;

} else {

node = mesh;

}
var node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh );

// if weights are provided on the node, override weights on the mesh.
if ( nodeDef.weights !== undefined ) {
Expand All @@ -3200,15 +3224,25 @@ THREE.GLTFLoader = ( function () {

if ( nodeDef.camera !== undefined ) {

pending.push( parser.getDependency( 'camera', nodeDef.camera ) );
pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {

return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera );

} ) );

}

if ( nodeDef.extensions
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {

pending.push( parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ) );
var lightIndex = nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light;

pending.push( parser.getDependency( 'light', lightIndex ).then( function ( light ) {

return parser._getNodeRef( parser.lightCache, lightIndex, light );

} ) );

}

Expand All @@ -3218,7 +3252,7 @@ THREE.GLTFLoader = ( function () {

var node;

// .isBone isn't in glTF spec. See .markDefs
// .isBone isn't in glTF spec. See ._markDefs
if ( nodeDef.isBone === true ) {

node = new THREE.Bone();
Expand Down
Loading

0 comments on commit 0964f06

Please sign in to comment.