diff --git a/examples/js/BufferGeometryUtils.js b/examples/js/BufferGeometryUtils.js index 6a57f1767c40c0b829c78b7a91ddf35397dbcda9..e6ea1b8b85992dce5f0c42572a25a6eb8a83c7b8 100644 --- a/examples/js/BufferGeometryUtils.js +++ b/examples/js/BufferGeometryUtils.js @@ -188,7 +188,7 @@ THREE.BufferGeometryUtils = { * @param {Array} geometries * @return {THREE.BufferGeometry} */ - mergeBufferGeometries: function ( geometries ) { + mergeBufferGeometries: function ( geometries, useGroups ) { var isIndexed = geometries[ 0 ].index !== null; @@ -200,6 +200,8 @@ THREE.BufferGeometryUtils = { var mergedGeometry = new THREE.BufferGeometry(); + var offset = 0; + for ( var i = 0; i < geometries.length; ++ i ) { var geometry = geometries[ i ]; @@ -242,6 +244,30 @@ THREE.BufferGeometryUtils = { } + if ( useGroups ) { + + var count; + + if ( isIndexed ) { + + count = geometry.index.count; + + } else if ( geometry.attributes.position !== undefined ) { + + count = geometry.attributes.position.count; + + } else { + + return null; + + } + + mergedGeometry.addGroup( offset, count, i ); + + offset += count; + + } + } // merge indices diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index a6a5db588879cce2c253eef0d1e9b2aa8a677042..799197b8b20bbd0751d97546aa0a8b1809d2ed29 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -1214,15 +1214,11 @@ THREE.GLTFLoader = ( function () { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets * - * @param {THREE.Mesh} mesh - * @param {GLTF.Mesh} meshDef - * @param {GLTF.Primitive} primitiveDef + * @param {THREE.Geometry} geometry + * @param {Array} targets * @param {Array} accessors */ - function addMorphTargets( mesh, meshDef, primitiveDef, accessors ) { - - var geometry = mesh.geometry; - var targets = primitiveDef.targets; + function addMorphTargets( geometry, targets, accessors ) { var hasMorphPosition = false; var hasMorphNormal = false; @@ -1330,6 +1326,14 @@ THREE.GLTFLoader = ( function () { if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + } + + /** + * @param {THREE.Mesh} mesh + * @param {GLTF.Mesh} meshDef + */ + function updateMorphTargets( mesh, meshDef ) { + mesh.updateMorphTargets(); if ( meshDef.weights !== undefined ) { @@ -1375,26 +1379,31 @@ THREE.GLTFLoader = ( function () { } - var attribA = a.attributes || {}; - var attribB = b.attributes || {}; - var keysA = Object.keys( attribA ); - var keysB = Object.keys( attribB ); + return isObjectEqual( a.attributes, b.attributes ); - if ( keysA.length !== keysB.length ) { + } - return false; + 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; } - for ( var i = 0, il = keysA.length; i < il; i ++ ) { + return true; - var key = keysA[ i ]; + } - if ( attribA[ key ] !== attribB[ key ] ) { + function isArrayEqual( a, b ) { - return false; + if ( a.length !== b.length ) return false; - } + for ( var i = 0, il = a.length; i < il; i ++ ) { + + if ( a[ i ] !== b[ i ] ) return false; } @@ -1408,11 +1417,35 @@ THREE.GLTFLoader = ( function () { var cached = cache[ i ]; - if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) { + if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) return cached.promise; - 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; } @@ -1445,6 +1478,47 @@ THREE.GLTFLoader = ( function () { } + /** + * 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 ) { @@ -1458,6 +1532,8 @@ THREE.GLTFLoader = ( function () { // BufferGeometry caching this.primitiveCache = []; + this.multiplePrimitivesCache = []; + this.multiPassGeometryCache = [] this.textureLoader = new THREE.TextureLoader( this.options.manager ); this.textureLoader.setCrossOrigin( this.options.crossOrigin ); @@ -2183,7 +2259,7 @@ THREE.GLTFLoader = ( function () { * @param {GLTF.Primitive} primitiveDef * @param {Array} accessors */ - function addPrimitiveAttributes ( geometry, primitiveDef, accessors ) { + function addPrimitiveAttributes( geometry, primitiveDef, accessors ) { var attributes = primitiveDef.attributes; @@ -2200,16 +2276,33 @@ THREE.GLTFLoader = ( function () { } - if ( primitiveDef.indices !== undefined && !geometry.index ) { + if ( primitiveDef.indices !== undefined && ! geometry.index ) { geometry.setIndex( accessors[ primitiveDef.indices ] ); } + if ( primitiveDef.targets !== undefined ) { + + addMorphTargets( geometry, primitiveDef.targets, accessors ); + + } + + if ( primitiveDef.extras !== undefined ) { + + geometry.userData = primitiveDef.extras; + + } + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry + * + * Creates BufferGeometries from primitives. + * If we can build a single BufferGeometry with .groups from multiple primitives, returns one BufferGeometry. + * Otherwise, returns BufferGeometries without .groups as many as primitives. + * * @param {Array} primitives * @return {Promise>} */ @@ -2219,6 +2312,22 @@ THREE.GLTFLoader = ( function () { var extensions = this.extensions; var cache = this.primitiveCache; + var isMultiPass = isMultiPassGeometry( primitives ); + var originalPrimitives; + + if ( isMultiPass ) { + + originalPrimitives = primitives; // save original primitives and use later + + // We build a single BufferGeometry with .groups from multiple primitives + // because all primitives share the same attributes/morph/mode and have indices. + + primitives = [ primitives[ 0 ] ]; + + // Sets .groups and combined indices to a geometry later in this method. + + } + return this.getDependencies( 'accessor' ).then( function ( accessors ) { var pending = []; @@ -2262,12 +2371,7 @@ THREE.GLTFLoader = ( function () { var geometryPromise = Promise.resolve( geometry ); // Cache this geometry - cache.push( { - - primitive: primitive, - promise: geometryPromise - - } ); + cache.push( { primitive: primitive, promise: geometryPromise } ); pending.push( geometryPromise ); @@ -2275,7 +2379,83 @@ THREE.GLTFLoader = ( function () { } - return Promise.all( pending ); + return Promise.all( pending ).then( function ( geometries ) { + + if ( isMultiPass ) { + + var baseGeometry = geometries[ 0 ]; + + // See if we've already created this combined geometry + var cache = parser.multiPassGeometryCache; + var cached = getCachedMultiPassGeometry( cache, baseGeometry, originalPrimitives ); + + if ( cached !== null ) return [ cached.geometry ]; + + // Cloning geometry because of index override. + // Attributes can be reused so cloning by myself here. + var geometry = new THREE.BufferGeometry(); + + geometry.name = baseGeometry.name; + geometry.userData = baseGeometry.userData; + + for ( var key in baseGeometry.attributes ) geometry.addAttribute( key, baseGeometry.attributes[ key ] ); + for ( var key in baseGeometry.morphAttributes ) geometry.morphAttributes[ key ] = baseGeometry.morphAttributes[ key ]; + + var indices = []; + var offset = 0; + + for ( var i = 0, il = originalPrimitives.length; i < il; i ++ ) { + + var accessor = accessors[ originalPrimitives[ i ].indices ]; + + for ( var j = 0, jl = accessor.count; j < jl; j ++ ) indices.push( accessor.array[ j ] ); + + geometry.addGroup( offset, accessor.count, i ); + + offset += accessor.count; + + } + + geometry.setIndex( indices ); + + cache.push( { geometry: geometry, baseGeometry: baseGeometry, primitives: originalPrimitives } ); + + return [ geometry ]; + + } else if ( geometries.length > 1 && THREE.BufferGeometryUtils !== undefined ) { + + // Tries to merge geometries with BufferGeometryUtils if possible + + for ( var i = 1, il = primitives.length; i < il; i ++ ) { + + // can't merge if draw mode is different + if ( primitives[ 0 ].mode !== primitives[ i ].mode ) return geometries; + + } + + // See if we've already created this combined geometry + var cache = parser.multiplePrimitivesCache; + var cached = getCachedCombinedGeometry( cache, geometries ); + + if ( cached ) { + + if ( cached.geometry !== null ) return [ cached.geometry ]; + + } else { + + var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries, true ); + + cache.push( { geometry: geometry, baseGeometries: geometries } ); + + if ( geometry !== null ) return [ geometry ]; + + } + + } + + return geometries; + + } ); } ); @@ -2301,188 +2481,220 @@ THREE.GLTFLoader = ( function () { ] ).then( function ( dependencies ) { - var group = new THREE.Group(); - var primitives = meshDef.primitives; + var originalMaterials = []; + + for ( var i = 0, il = primitives.length; i < il; i ++ ) { + + originalMaterials[ i ] = primitives[ i ].material === undefined + ? createDefaultMaterial() + : dependencies.materials[ primitives[ i ].material ]; + + } return scope.loadGeometries( primitives ).then( function ( geometries ) { - for ( var i = 0, il = primitives.length; i < il; i ++ ) { + var isMultiMaterial = geometries.length === 1 && geometries[ 0 ].groups.length > 0; - var primitive = primitives[ i ]; - var geometry = geometries[ i ]; + var meshes = []; - var material = primitive.material === undefined - ? createDefaultMaterial() - : dependencies.materials[ primitive.material ]; + for ( var i = 0, il = geometries.length; i < il; i ++ ) { - if ( material.aoMap - && geometry.attributes.uv2 === undefined - && geometry.attributes.uv !== undefined ) { + var geometry = geometries[ i ]; + var primitive = primitives[ i ]; - console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); - geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); + // 1. create Mesh - } + var mesh; - // If the material will be modified later on, clone it now. - var useVertexColors = geometry.attributes.color !== undefined; - var useFlatShading = geometry.attributes.normal === undefined; - var useSkinning = meshDef.isSkinnedMesh === true; - var useMorphTargets = primitive.targets !== undefined; + var material = isMultiMaterial ? originalMaterials : originalMaterials[ i ] - if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || + primitive.mode === undefined ) { - if ( material.isGLTFSpecularGlossinessMaterial ) { + // .isSkinnedMesh isn't in glTF spec. See .markDefs() + mesh = meshDef.isSkinnedMesh === true + ? new THREE.SkinnedMesh( geometry, material ) + : new THREE.Mesh( geometry, material ); - var specGlossExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; - material = specGlossExtension.cloneMaterial( material ); + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - } else { + mesh.drawMode = THREE.TriangleStripDrawMode; - material = material.clone(); + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + + mesh.drawMode = THREE.TriangleFanDrawMode; } - } + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + + mesh = new THREE.LineSegments( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + + mesh = new THREE.Line( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + + mesh = new THREE.LineLoop( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + + mesh = new THREE.Points( geometry, material ); - if ( useVertexColors ) { + } else { - material.vertexColors = THREE.VertexColors; - material.needsUpdate = true; + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); } - if ( useFlatShading ) { + if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { - material.flatShading = true; + updateMorphTargets( mesh, meshDef ); } - var mesh; + mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || - primitive.mode === undefined ) { + if ( geometries.length > 1 ) mesh.name += '_' + i; - if ( useSkinning ) { + if ( meshDef.extras !== undefined ) mesh.userData = meshDef.extras; - mesh = new THREE.SkinnedMesh( geometry, material ); - material.skinning = true; + meshes.push( mesh ); - } else { + // 2. update Material depending on Mesh and BufferGeometry - mesh = new THREE.Mesh( geometry, material ); + var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - } + var useVertexColors = geometry.attributes.color !== undefined; + var useFlatShading = geometry.attributes.normal === undefined; + var useSkinning = mesh.isSkinnedMesh === true; + var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; + var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { + for ( var j = 0, jl = materials.length; j < jl; j ++ ) { - mesh.drawMode = THREE.TriangleStripDrawMode; + var material = materials[ j ]; - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + if ( mesh.isPoints ) { - mesh.drawMode = THREE.TriangleFanDrawMode; + var cacheKey = 'PointsMaterial:' + material.uuid; - } + var pointsMaterial = scope.cache.get( cacheKey ); - } else if ( primitive.mode === WEBGL_CONSTANTS.LINES || - primitive.mode === WEBGL_CONSTANTS.LINE_STRIP || - primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + if ( ! pointsMaterial ) { - var cacheKey = 'LineBasicMaterial:' + material.uuid; + pointsMaterial = new THREE.PointsMaterial(); + THREE.Material.prototype.copy.call( pointsMaterial, material ); + pointsMaterial.color.copy( material.color ); + pointsMaterial.map = material.map; + pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet - var lineMaterial = scope.cache.get( cacheKey ); + scope.cache.add( cacheKey, pointsMaterial ); - if ( ! lineMaterial ) { + } - lineMaterial = new THREE.LineBasicMaterial(); - THREE.Material.prototype.copy.call( lineMaterial, material ); - lineMaterial.color.copy( material.color ); - lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet + material = pointsMaterial; - scope.cache.add( cacheKey, lineMaterial ); + } else if ( mesh.isLine ) { - } + var cacheKey = 'LineBasicMaterial:' + material.uuid; - material = lineMaterial; + var lineMaterial = scope.cache.get( cacheKey ); - if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + if ( ! lineMaterial ) { - mesh = new THREE.LineSegments( geometry, material ); + lineMaterial = new THREE.LineBasicMaterial(); + THREE.Material.prototype.copy.call( lineMaterial, material ); + lineMaterial.color.copy( material.color ); + lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + scope.cache.add( cacheKey, lineMaterial ); - mesh = new THREE.Line( geometry, material ); + } - } else { + material = lineMaterial; + + } + + // If the material will be modified later on, clone it now. + if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { - mesh = new THREE.LineLoop( geometry, material ); + material = material.isGLTFSpecularGlossinessMaterial + ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) + : material.clone(); } - } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + if ( useSkinning ) { - var cacheKey = 'PointsMaterial:' + material.uuid; + material.skinning = true; - var pointsMaterial = scope.cache.get( cacheKey ); + } - if ( ! pointsMaterial ) { + if ( useVertexColors ) { + + material.vertexColors = THREE.VertexColors; + material.needsUpdate = true; + + } - pointsMaterial = new THREE.PointsMaterial(); - THREE.Material.prototype.copy.call( pointsMaterial, material ); - pointsMaterial.color.copy( material.color ); - pointsMaterial.map = material.map; - pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet + if ( useFlatShading ) { - scope.cache.add( cacheKey, pointsMaterial ); + material.flatShading = true; } - material = pointsMaterial; + if ( useMorphTargets ) { - mesh = new THREE.Points( geometry, material ); + material.morphTargets = true; - } else { + } - throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); + if ( useMorphNormals ) { - } + material.morphNormals = true; - mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); + } - if ( useMorphTargets ) { + materials[ j ] = material; - addMorphTargets( mesh, meshDef, primitive, dependencies.accessors ); + // workarounds for mesh and geometry - material.morphTargets = true; + if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { - if ( mesh.geometry.morphAttributes.normal !== undefined ) material.morphNormals = true; + console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); + geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); - } + } - if ( meshDef.extras !== undefined ) mesh.userData = meshDef.extras; - if ( primitive.extras !== undefined ) mesh.geometry.userData = primitive.extras; + if ( material.isGLTFSpecularGlossinessMaterial ) { - // for Specular-Glossiness. - if ( material.isGLTFSpecularGlossinessMaterial === true ) { + // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update + mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; - mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; + } } - if ( primitives.length > 1 ) { + mesh.material = isMultiMaterial ? materials : materials[ 0 ]; - mesh.name += '_' + i; + } - group.add( mesh ); + if ( meshes.length === 1 ) { - } else { + return meshes[ 0 ]; - return mesh; + } - } + var group = new THREE.Group(); + + for ( var i = 0, il = meshes.length; i < il; i ++ ) { + + group.add( meshes[ i ] ); } @@ -2726,6 +2938,7 @@ THREE.GLTFLoader = ( function () { var node; + // .isBone isn't in glTF spec. See .markDefs if ( nodeDef.isBone === true ) { node = new THREE.Bone();