From 61260bc6ad94222b303b505f369885a848856e07 Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 21 Apr 2016 09:51:48 +0300 Subject: [PATCH] OBJLoader: MultiMaterial and geometry group support (#8691) * ObjLoader: Implement support for multiple materials inside a geometry/object. - Tracks material declaration occuring mid face declaration - If object has multiple materials MultiMaterial is creted and geometry groups are created. - This approach is better than splitting into separate objects: 1) we are creating as many objects the file defines (correctness) 2) The full geometry would be duplicated into two objects, afaik this uploads the geometry multiple times to the GPU (perf, i had test asset that had >400 submeshes with 1-6 geometry groups each). Fixes #8681 #8640 Updates #8203 (fixes OPs 2. point) * OBJLoader: Code cleanup and three.js style formatting. --- examples/js/loaders/OBJLoader.js | 155 +++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 20 deletions(-) diff --git a/examples/js/loaders/OBJLoader.js b/examples/js/loaders/OBJLoader.js index e2aa4a095a..841f7f87a3 100644 --- a/examples/js/loaders/OBJLoader.js +++ b/examples/js/loaders/OBJLoader.js @@ -89,24 +89,91 @@ THREE.OBJLoader.prototype = { } + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize(); + + } + this.object = { name : name || '', + fromDeclaration : ( fromDeclaration !== false ), + geometry : { vertices : [], normals : [], uvs : [] }, - material : { - name : '', - smooth : true + materials : [], + smooth : true, + + startMaterial : function( name, libraries ) { + + var previous = this._finalize( false ); + + var material = { + index : this.materials.length, + name : name || '', + mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth : ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd : -1, + groupCount : -1 + }; + + this.materials.push( material ); + + return material; + }, - fromDeclaration : ( fromDeclaration !== false ) + + currentMaterial : function() { + + if ( this.materials.length > 0 ) { + return this.materials[ this.materials.length - 1 ]; + } + + return undefined; + + }, + + _finalize : function( end ) { + + var lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end !== false && this.materials.length === 0 ) { + this.materials.push({ + name : '', + smooth : this.smooth + }); + } + + return lastMultiMaterial; + + } }; this.objects.push( this.object ); }, + finalize : function() { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize(); + + } + + }, + parseVertexIndex: function ( value, len ) { var index = parseInt( value, 10 ); @@ -464,7 +531,7 @@ THREE.OBJLoader.prototype = { // material - state.object.material.name = line.substring( 7 ).trim(); + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); } else if ( this.regexp.material_library_pattern.test( line ) ) { @@ -476,8 +543,22 @@ THREE.OBJLoader.prototype = { // smooth shading + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + var value = result[ 1 ].trim().toLowerCase(); - state.object.material.smooth = ( value === '1' || value === 'on' ); + state.object.smooth = ( value === '1' || value === 'on' ); + + var material = state.object.currentMaterial(); + if ( material ) { + + material.smooth = state.object.smooth; + + } } else { @@ -490,6 +571,8 @@ THREE.OBJLoader.prototype = { } + state.finalize(); + var container = new THREE.Group(); container.materialLibraries = [].concat( state.materialLibraries ); @@ -497,6 +580,7 @@ THREE.OBJLoader.prototype = { var object = state.objects[ i ]; var geometry = object.geometry; + var materials = object.materials; var isLine = ( geometry.type === 'Line' ); // Skip o/g line declarations that did not follow with any faces @@ -522,33 +606,64 @@ THREE.OBJLoader.prototype = { } - var material; + // Create materials + + var createdMaterials = []; + + for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { + + var sourceMaterial = materials[mi]; + var material = undefined; - if ( this.materials !== null ) { + if ( this.materials !== null ) { - material = this.materials.create( object.material.name ); + material = this.materials.create( sourceMaterial.name ); - // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. - if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { - var materialLine = new THREE.LineBasicMaterial(); - materialLine.copy( material ); - material = materialLine; + var materialLine = new THREE.LineBasicMaterial(); + materialLine.copy( material ); + material = materialLine; + + } } - } + if ( ! material ) { - if ( ! material ) { + material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); + material.name = sourceMaterial.name; - material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); - material.name = object.material.name; + } + + material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; + + createdMaterials.push(material); } - material.shading = object.material.smooth ? THREE.SmoothShading : THREE.FlatShading; + // Create mesh + + var mesh; + + if ( createdMaterials.length > 1 ) { + + for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { + + var sourceMaterial = materials[mi]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + var multiMaterial = new THREE.MultiMaterial( createdMaterials ); + mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.Line( buffergeometry, multiMaterial ) ); + + } else { + + mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.Line( buffergeometry, createdMaterials[ 0 ] ) ); + } - var mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, material ) : new THREE.Line( buffergeometry, material ) ); mesh.name = object.name; container.add( mesh ); -- GitLab