| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| THREE.LWOLoader = ( function () { |
|
|
| var lwoTree; |
|
|
| function LWOLoader( manager ) { |
|
|
| this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; |
|
|
| } |
|
|
| LWOLoader.prototype = { |
|
|
| constructor: LWOLoader, |
|
|
| crossOrigin: 'anonymous', |
|
|
| load: function ( url, onLoad, onProgress, onError ) { |
|
|
| var self = this; |
|
|
| var path = ( self.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : self.path; |
|
|
| |
| var modelName = url.split( path ).pop().split( '.' )[ 0 ]; |
|
|
| var loader = new THREE.FileLoader( this.manager ); |
| loader.setPath( self.path ); |
| loader.setResponseType( 'arraybuffer' ); |
|
|
| loader.load( url, function ( buffer ) { |
|
|
| |
| onLoad( self.parse( buffer, path, modelName ) ); |
| |
|
|
| }, 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; |
|
|
| }, |
|
|
| parse: function ( iffBuffer, path, modelName ) { |
|
|
| lwoTree = new IFFParser().parse( iffBuffer ); |
|
|
| |
|
|
| var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); |
|
|
| return new LWOTreeParser( textureLoader ).parse( modelName ); |
|
|
| } |
|
|
| }; |
|
|
| |
| function LWOTreeParser( textureLoader ) { |
|
|
| this.textureLoader = textureLoader; |
|
|
| } |
|
|
| LWOTreeParser.prototype = { |
|
|
| constructor: LWOTreeParser, |
|
|
| parse: function ( modelName ) { |
|
|
| this.materials = new MaterialParser( this.textureLoader ).parse(); |
| this.defaultLayerName = modelName; |
|
|
| this.meshes = this.parseLayers(); |
|
|
| return { |
| materials: this.materials, |
| meshes: this.meshes, |
| }; |
|
|
| }, |
|
|
| parseLayers() { |
|
|
| |
| var meshes = []; |
|
|
| |
| var finalMeshes = []; |
|
|
| var geometryParser = new GeometryParser(); |
|
|
| var self = this; |
| lwoTree.layers.forEach( function ( layer ) { |
|
|
| var geometry = geometryParser.parse( layer.geometry, layer ); |
|
|
| var mesh = self.parseMesh( geometry, layer ); |
|
|
| meshes[ layer.number ] = mesh; |
|
|
| if ( layer.parent === - 1 ) finalMeshes.push( mesh ); |
| else meshes[ layer.parent ].add( mesh ); |
|
|
|
|
| } ); |
|
|
| this.applyPivots( finalMeshes ); |
|
|
| return finalMeshes; |
|
|
| }, |
|
|
| parseMesh( geometry, layer ) { |
|
|
| var mesh; |
|
|
| var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); |
|
|
| this.duplicateUVs( geometry, materials ); |
|
|
| if ( layer.geometry.type === 'points' ) mesh = new THREE.Points( geometry, materials ); |
| else if ( layer.geometry.type === 'lines' ) mesh = new THREE.LineSegments( geometry, materials ); |
| else mesh = new THREE.Mesh( geometry, materials ); |
|
|
| if ( layer.name ) mesh.name = layer.name; |
| else mesh.name = this.defaultLayerName + '_layer_' + layer.number; |
|
|
| mesh.userData.pivot = layer.pivot; |
|
|
| return mesh; |
|
|
| }, |
|
|
| |
| applyPivots( meshes ) { |
|
|
| meshes.forEach( function ( mesh ) { |
|
|
| mesh.traverse( function ( child ) { |
|
|
| var pivot = child.userData.pivot; |
|
|
| child.position.x += pivot[ 0 ]; |
| child.position.y += pivot[ 1 ]; |
| child.position.z += pivot[ 2 ]; |
|
|
| if ( child.parent ) { |
|
|
| var parentPivot = child.parent.userData.pivot; |
|
|
| child.position.x -= parentPivot[ 0 ]; |
| child.position.y -= parentPivot[ 1 ]; |
| child.position.z -= parentPivot[ 2 ]; |
|
|
| } |
|
|
| } ); |
|
|
| } ); |
|
|
| }, |
|
|
| getMaterials( namesArray, type ) { |
|
|
| var materials = []; |
|
|
| var self = this; |
|
|
| namesArray.forEach( function ( name, i ) { |
|
|
| materials[ i ] = self.getMaterialByName( name ); |
|
|
| } ); |
|
|
| |
| if ( type === 'points' || type === 'lines' ) { |
|
|
| materials.forEach( function ( mat, i ) { |
|
|
| var spec = { |
| color: mat.color, |
| }; |
|
|
| if ( type === 'points' ) { |
|
|
| spec.size = 0.1; |
| spec.map = mat.map; |
| spec.morphTargets = mat.morphTargets; |
| materials[ i ] = new THREE.PointsMaterial( spec ); |
|
|
| } else if ( type === 'lines' ) { |
|
|
| materials[ i ] = new THREE.LineBasicMaterial( spec ); |
|
|
| } |
|
|
| } ); |
|
|
| } |
|
|
| |
| var filtered = materials.filter( Boolean ); |
| if ( filtered.length === 1 ) return filtered[ 0 ]; |
|
|
| return materials; |
|
|
| }, |
|
|
| getMaterialByName( name ) { |
|
|
| return this.materials.filter( function ( m ) { |
|
|
| return m.name === name; |
|
|
| } )[ 0 ]; |
|
|
| }, |
|
|
| |
| duplicateUVs( geometry, materials ) { |
|
|
| var duplicateUVs = false; |
|
|
| if ( ! Array.isArray( materials ) ) { |
|
|
| if ( materials.aoMap ) duplicateUVs = true; |
|
|
| } else { |
|
|
| materials.forEach( function ( material ) { |
|
|
| if ( material.aoMap ) duplicateUVs = true; |
|
|
| } ); |
|
|
| } |
|
|
| if ( ! duplicateUVs ) return; |
|
|
| geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); |
|
|
| }, |
|
|
| }; |
|
|
| function MaterialParser( textureLoader ) { |
|
|
| this.textureLoader = textureLoader; |
|
|
| } |
|
|
| MaterialParser.prototype = { |
|
|
| constructor: MaterialParser, |
|
|
| parse: function () { |
|
|
| var materials = []; |
| this.textures = {}; |
|
|
| for ( var name in lwoTree.materials ) { |
|
|
| materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) ); |
|
|
| } |
|
|
| return materials; |
|
|
| }, |
|
|
| parseMaterial( materialData, name, textures ) { |
|
|
| var params = { |
| name: name, |
| side: this.getSide( materialData.attributes ), |
| flatShading: this.getSmooth( materialData.attributes ), |
| }; |
|
|
| var connections = this.parseConnections( materialData.connections, materialData.nodes ); |
|
|
| var maps = this.parseTextureNodes( connections.maps ); |
|
|
| this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); |
|
|
| var attributes = this.parseAttributes( connections.attributes, maps ); |
|
|
| this.parseEnvMap( connections, maps, attributes ); |
|
|
| params = Object.assign( maps, params ); |
| params = Object.assign( params, attributes ); |
|
|
| var type = connections.attributes.Roughness ? 'Standard' : 'Phong'; |
|
|
| return new THREE[ 'Mesh' + type + 'Material' ]( params ); |
|
|
| }, |
|
|
| |
| |
| |
| getSide( attributes ) { |
|
|
| if ( ! attributes.side ) return THREE.BackSide; |
|
|
| switch ( attributes.side ) { |
|
|
| case 0: |
| case 1: |
| return THREE.BackSide; |
| case 2: return THREE.FrontSide; |
| case 3: return THREE.DoubleSide; |
|
|
| } |
|
|
| }, |
|
|
| getSmooth( attributes ) { |
|
|
| if ( ! attributes.smooth ) return true; |
| return ! attributes.smooth; |
|
|
| }, |
|
|
| parseConnections( connections, nodes ) { |
|
|
| var materialConnections = { |
| maps: {} |
| }; |
|
|
| var inputName = connections.inputName; |
| var inputNodeName = connections.inputNodeName; |
| var nodeName = connections.nodeName; |
|
|
| var self = this; |
| inputName.forEach( function ( name, index ) { |
|
|
| if ( name === 'Material' ) { |
|
|
| var matNode = self.getNodeByRefName( inputNodeName[ index ], nodes ); |
| materialConnections.attributes = matNode.attributes; |
| materialConnections.envMap = matNode.fileName; |
| materialConnections.name = inputNodeName[ index ]; |
|
|
| } |
|
|
| } ); |
|
|
| nodeName.forEach( function ( name, index ) { |
|
|
| if ( name === materialConnections.name ) { |
|
|
| materialConnections.maps[ inputName[ index ] ] = self.getNodeByRefName( inputNodeName[ index ], nodes ); |
|
|
| } |
|
|
| } ); |
|
|
| return materialConnections; |
|
|
| }, |
|
|
| getNodeByRefName( refName, nodes ) { |
|
|
| for ( var name in nodes ) { |
|
|
| if ( nodes[ name ].refName === refName ) return nodes[ name ]; |
|
|
| } |
|
|
| }, |
|
|
| parseTextureNodes( textureNodes ) { |
|
|
| var maps = {}; |
|
|
| for ( name in textureNodes ) { |
|
|
| var node = textureNodes[ name ]; |
| var path = node.fileName; |
|
|
| if ( ! path ) return; |
|
|
| var texture = this.loadTexture( path ); |
|
|
| if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode ); |
| if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode ); |
|
|
| switch ( name ) { |
|
|
| case 'Color': |
| maps.map = texture; |
| break; |
| case 'Roughness': |
| maps.roughnessMap = texture; |
| maps.roughness = 0.5; |
| break; |
| case 'Specular': |
| maps.specularMap = texture; |
| maps.specular = 0xffffff; |
| break; |
| case 'Luminous': |
| maps.emissiveMap = texture; |
| maps.emissive = 0x808080; |
| break; |
| case 'Metallic': |
| maps.metalnessMap = texture; |
| maps.metalness = 0.5; |
| break; |
| case 'Transparency': |
| case 'Alpha': |
| maps.alphaMap = texture; |
| maps.transparent = true; |
| break; |
| case 'Normal': |
| maps.normalMap = texture; |
| if ( node.amplitude !== undefined ) maps.normalScale = new THREE.Vector2( node.amplitude, node.amplitude ); |
| break; |
| case 'Bump': |
| maps.bumpMap = texture; |
| break; |
|
|
| } |
|
|
| } |
|
|
| |
| if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap; |
|
|
| return maps; |
|
|
| }, |
|
|
| |
| |
| parseAttributeImageMaps( attributes, textures, maps ) { |
|
|
| for ( var name in attributes ) { |
|
|
| var attribute = attributes[ name ]; |
|
|
| if ( attribute.maps ) { |
|
|
| var mapData = attribute.maps[ 0 ]; |
|
|
| var path = this.getTexturePathByIndex( mapData.imageIndex, textures ); |
| if ( ! path ) return; |
|
|
| var texture = this.loadTexture( path ); |
|
|
| if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w ); |
| if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h ); |
|
|
| switch ( name ) { |
|
|
| case 'Color': |
| maps.map = texture; |
| break; |
| case 'Diffuse': |
| maps.aoMap = texture; |
| break; |
| case 'Roughness': |
| maps.roughnessMap = texture; |
| maps.roughness = 1; |
| break; |
| case 'Specular': |
| maps.specularMap = texture; |
| maps.specular = 0xffffff; |
| break; |
| case 'Luminosity': |
| maps.emissiveMap = texture; |
| maps.emissive = 0x808080; |
| break; |
| case 'Metallic': |
| maps.metalnessMap = texture; |
| maps.metalness = 1; |
| break; |
| case 'Transparency': |
| case 'Alpha': |
| maps.alphaMap = texture; |
| maps.transparent = true; |
| break; |
| case 'Normal': |
| maps.normalMap = texture; |
| break; |
| case 'Bump': |
| maps.bumpMap = texture; |
| break; |
|
|
| } |
|
|
| } |
|
|
| } |
|
|
| }, |
|
|
| parseAttributes( attributes, maps ) { |
|
|
| var params = {}; |
|
|
| |
| if ( attributes.Color && ! maps.map ) { |
|
|
| params.color = new THREE.Color().fromArray( attributes.Color.value ); |
|
|
| } else params.color = new THREE.Color(); |
|
|
|
|
| if ( attributes.Transparency && attributes.Transparency.value !== 0 ) { |
|
|
| params.opacity = 1 - attributes.Transparency.value; |
| params.transparent = true; |
|
|
| } |
|
|
| if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1; |
|
|
| if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 1 / attributes[ 'Refraction Index' ].value; |
|
|
| this.parseStandardAttributes( params, attributes, maps ); |
| this.parsePhongAttributes( params, attributes, maps ); |
|
|
| return params; |
|
|
| }, |
|
|
| parseStandardAttributes( params, attributes, maps ) { |
|
|
| if ( attributes.Luminous && attributes.Luminous.value !== 0 && attributes[ 'Luminous Color' ] ) { |
|
|
| var emissiveColor = attributes[ 'Luminous Color' ].value.map( function ( val ) { |
|
|
| return val * attributes.Luminous.value; |
|
|
| } ); |
|
|
| params.emissive = new THREE.Color().fromArray( emissiveColor ); |
|
|
| } |
| if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value; |
| if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value; |
|
|
| }, |
|
|
| parsePhongAttributes( params, attributes, maps ) { |
|
|
| if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value ); |
|
|
| if ( attributes.Reflection ) { |
|
|
| params.reflectivity = attributes.Reflection.value; |
| params.combine = THREE.AddOperation; |
|
|
| } |
|
|
| if ( attributes.Luminosity && ! maps.emissiveMap ) params.emissive = new THREE.Color().setScalar( attributes.Luminosity.value ); |
|
|
| if ( attributes.Glossiness !== undefined ) params.shininess = 5 + Math.pow( attributes.Glossiness.value * 7, 6 ); |
|
|
| |
| if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) params.specular = new THREE.Color().setScalar( attributes.Specular.value * 1.5 ); |
|
|
| }, |
|
|
| parseEnvMap( connections, maps, attributes ) { |
|
|
| if ( connections.envMap ) { |
|
|
| var envMap = this.loadTexture( connections.envMap ); |
|
|
| if ( attributes.transparent && attributes.opacity < 0.999 ) { |
|
|
| envMap.mapping = THREE.EquirectangularRefractionMapping; |
|
|
| |
| if ( attributes.reflectivity !== undefined ) { |
|
|
| delete attributes.reflectivity; |
| delete attributes.combine; |
|
|
| } |
|
|
| if ( attributes.metalness !== undefined ) { |
|
|
| delete attributes.metalness; |
|
|
| } |
|
|
| } else envMap.mapping = THREE.EquirectangularReflectionMapping; |
|
|
| maps.envMap = envMap; |
|
|
| } |
|
|
| }, |
|
|
| |
| getTexturePathByIndex( index ) { |
|
|
| var fileName = ''; |
|
|
| if ( ! lwoTree.textures ) return fileName; |
|
|
| lwoTree.textures.forEach( function ( texture ) { |
|
|
| if ( texture.index === index ) fileName = texture.fileName; |
|
|
| } ); |
|
|
| return fileName; |
|
|
| }, |
|
|
| loadTexture( path ) { |
|
|
| if ( ! path ) return null; |
|
|
| return this.textureLoader.load( this.cleanPath( path ) ); |
|
|
| }, |
|
|
| |
| |
| |
| |
| cleanPath( path ) { |
|
|
| if ( path.indexOf( 'Images' ) === 0 ) return './' + path; |
| return path.split( '/' ).pop().split( '\\' ).pop(); |
|
|
| }, |
|
|
| |
| getWrappingType( num ) { |
|
|
| switch ( num ) { |
|
|
| case 0: |
| console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' ); |
| return THREE.ClampToEdgeWrapping; |
| case 1: return THREE.RepeatWrapping; |
| case 2: return THREE.MirroredRepeatWrapping; |
| case 3: return THREE.ClampToEdgeWrapping; |
|
|
| } |
|
|
| }, |
|
|
| getType( nodeData ) { |
|
|
| if ( nodeData.roughness ) return 'Standard'; |
| return 'Phong'; |
|
|
| }, |
|
|
| }; |
|
|
| function GeometryParser() {} |
|
|
| GeometryParser.prototype = { |
|
|
| constructor: GeometryParser, |
|
|
| parse( geoData, layer ) { |
|
|
| var geometry = new THREE.BufferGeometry(); |
|
|
| geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) ); |
|
|
| var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); |
| geometry.setIndex( indices ); |
|
|
| this.parseGroups( geometry, geoData ); |
|
|
| geometry.computeVertexNormals(); |
|
|
| this.parseUVs( geometry, layer, indices ); |
| this.parseMorphTargets( geometry, layer, indices ); |
|
|
| |
| geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); |
|
|
| |
| |
| |
|
|
| return geometry; |
|
|
| }, |
|
|
| |
| splitIndices( indices, polygonDimensions ) { |
|
|
| var remappedIndices = []; |
|
|
| var i = 0; |
| polygonDimensions.forEach( function ( dim ) { |
|
|
| if ( dim < 4 ) { |
|
|
| for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); |
|
|
| } else if ( dim === 4 ) { |
|
|
| remappedIndices.push( |
| indices[ i ], |
| indices[ i + 1 ], |
| indices[ i + 2 ], |
|
|
| indices[ i ], |
| indices[ i + 2 ], |
| indices[ i + 3 ] |
|
|
| ); |
|
|
| } else if ( dim > 4 ) console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' ); |
|
|
| i += dim; |
|
|
| } ); |
|
|
| return remappedIndices; |
|
|
| }, |
|
|
| |
| parseGroups( geometry, geoData ) { |
|
|
| var tags = lwoTree.tags; |
| var matNames = []; |
|
|
| var elemSize = 3; |
| if ( geoData.type === 'lines' ) elemSize = 2; |
| if ( geoData.type === 'points' ) elemSize = 1; |
|
|
| var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); |
|
|
| var indexNum = 0; |
| var indexPairs = {}; |
|
|
| var prevMaterialIndex; |
|
|
| var prevStart = 0; |
| var currentCount = 0; |
|
|
| for ( var i = 0; i < remappedIndices.length; i += 2 ) { |
|
|
| var materialIndex = remappedIndices[ i + 1 ]; |
|
|
| if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ]; |
|
|
| if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex; |
|
|
| if ( materialIndex !== prevMaterialIndex ) { |
|
|
| var currentIndex; |
| if ( indexPairs[ tags[ prevMaterialIndex ] ] ) { |
|
|
| currentIndex = indexPairs[ tags[ prevMaterialIndex ] ]; |
|
|
| } else { |
|
|
| currentIndex = indexNum; |
| indexPairs[ tags[ prevMaterialIndex ] ] = indexNum; |
| matNames[ indexNum ] = tags[ prevMaterialIndex ]; |
| indexNum ++; |
|
|
| } |
|
|
| geometry.addGroup( prevStart, currentCount, currentIndex ); |
|
|
| prevStart += currentCount; |
|
|
| prevMaterialIndex = materialIndex; |
| currentCount = 0; |
|
|
| } |
|
|
| currentCount += elemSize; |
|
|
| } |
|
|
| |
| if ( geometry.groups.length > 0 ) { |
|
|
| var currentIndex; |
| if ( indexPairs[ tags[ materialIndex ] ] ) { |
|
|
| currentIndex = indexPairs[ tags[ materialIndex ] ]; |
|
|
| } else { |
|
|
| currentIndex = indexNum; |
| indexPairs[ tags[ materialIndex ] ] = indexNum; |
| matNames[ indexNum ] = tags[ materialIndex ]; |
|
|
| } |
|
|
| geometry.addGroup( prevStart, currentCount, currentIndex ); |
|
|
| } |
|
|
| |
| geometry.userData.matNames = matNames; |
|
|
| }, |
|
|
| splitMaterialIndices( polygonDimensions, indices ) { |
|
|
| var remappedIndices = []; |
|
|
| polygonDimensions.forEach( function ( dim, i ) { |
|
|
| if ( dim <= 3 ) { |
|
|
| remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); |
|
|
| } else if ( dim === 4 ) { |
|
|
| remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] ); |
|
|
| } |
|
|
| } ); |
|
|
| return remappedIndices; |
|
|
| }, |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| parseUVs( geometry, layer ) { |
|
|
| |
| var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { |
|
|
| return 0; |
|
|
| } ); |
|
|
| for ( var name in layer.uvs ) { |
|
|
| var uvs = layer.uvs[ name ].uvs; |
| var uvIndices = layer.uvs[ name ].uvIndices; |
|
|
| uvIndices.forEach( function ( i, j ) { |
|
|
| remappedUVs[ i * 2 ] = uvs[ j * 2 ]; |
| remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ]; |
|
|
| } ); |
|
|
| } |
|
|
| geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) ); |
|
|
| }, |
|
|
| parseMorphTargets( geometry, layer ) { |
|
|
| var num = 0; |
| for ( var name in layer.morphTargets ) { |
|
|
| var remappedPoints = geometry.attributes.position.array.slice(); |
|
|
| if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = []; |
|
|
| var morphPoints = layer.morphTargets[ name ].points; |
| var morphIndices = layer.morphTargets[ name ].indices; |
| var type = layer.morphTargets[ name ].type; |
|
|
| morphIndices.forEach( function ( i, j ) { |
|
|
| if ( type === 'relative' ) { |
|
|
| remappedPoints[ i * 3 ] += morphPoints[ j * 3 ]; |
| remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ]; |
| remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ]; |
|
|
| } else { |
|
|
| remappedPoints[ i * 3 ] = morphPoints[ j * 3 ]; |
| remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ]; |
| remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ]; |
|
|
| } |
|
|
| } ); |
|
|
| geometry.morphAttributes.position[ num ] = new THREE.Float32BufferAttribute( remappedPoints, 3 ); |
| geometry.morphAttributes.position[ num ].name = name; |
|
|
| num ++; |
|
|
| } |
|
|
| }, |
|
|
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
| |
| function IFFParser() {} |
|
|
| IFFParser.prototype = { |
|
|
| constructor: IFFParser, |
|
|
| parse: function ( buffer ) { |
|
|
| |
| |
|
|
| this.reader = new DataViewReader( buffer ); |
|
|
| this.tree = { |
| materials: {}, |
| layers: [], |
| tags: [], |
| textures: [], |
| }; |
|
|
| |
| this.currentLayer = this.tree; |
| this.currentForm = this.tree; |
|
|
| |
| while ( ! this.reader.endOfFile() ) this.parseBlock(); |
|
|
| return this.tree; |
|
|
| }, |
|
|
| parseBlock() { |
|
|
| var blockID = this.reader.getIDTag(); |
| var length = this.reader.getUint32(); |
|
|
| |
| switch ( blockID ) { |
|
|
| case 'FORM': |
| this.parseForm( length ); |
| break; |
|
|
| |
|
|
| |
| case 'ICON': |
| case 'VMPA': |
| case 'BBOX': |
| |
| |
|
|
| |
| case 'NORM': |
|
|
| |
| case 'PRE ': |
| case 'POST': |
| case 'KEY ': |
| case 'SPAN': |
|
|
| |
| case 'TIME': |
| case 'CLRS': |
| case 'CLRA': |
| case 'FILT': |
| case 'DITH': |
| case 'CONT': |
| case 'BRIT': |
| case 'SATR': |
| case 'HUE ': |
| case 'GAMM': |
| case 'NEGA': |
| case 'IFLT': |
| case 'PFLT': |
|
|
| |
| case 'PROJ': |
| case 'AXIS': |
| case 'AAST': |
| case 'PIXB': |
| case 'STCK': |
|
|
| |
| case 'VALU': |
|
|
| |
| case 'PNAM': |
| case 'INAM': |
| case 'GRST': |
| case 'GREN': |
| case 'GRPT': |
| case 'FKEY': |
| case 'IKEY': |
|
|
| |
| case 'CSYS': |
|
|
| |
| case 'OPAQ': |
| case 'CMAP': |
|
|
| |
| |
| case 'NLOC': |
| case 'NZOM': |
| case 'NVER': |
| case 'NSRV': |
| case 'NCRD': |
| case 'NMOD': |
| case 'NPRW': |
| case 'NPLA': |
| case 'VERS': |
| case 'ENUM': |
| case 'FLAG': |
| case 'TAG ': |
|
|
| |
| case 'CGMD': |
| case 'CGTY': |
| case 'CGST': |
| case 'CGEN': |
| case 'CGTS': |
| case 'CGTE': |
| case 'OSMP': |
| case 'OMDE': |
| case 'OUTR': |
| this.reader.skip( length ); |
| break; |
|
|
| |
| case 'DIFF': |
| case 'TRNL': |
| case 'REFL': |
| case 'GLOS': |
| case 'SHRP': |
| case 'RFOP': |
| case 'RSAN': |
| case 'TROP': |
| case 'RBLR': |
| case 'TBLR': |
| case 'CLRH': |
| case 'CLRF': |
| case 'ADTR': |
| case 'GLOW': |
| case 'LINE': |
| case 'ALPH': |
| case 'LINE': |
| case 'VCOL': |
| case 'ENAB': |
| this.reader.skip( length ); |
| break; |
|
|
| |
| case 'IPIX': |
| case 'IMIP': |
| case 'IMOD': |
| case 'AMOD': |
| case 'IINV': |
| case 'INCR': |
| case 'IAXS': |
| case 'IFOT': |
| case 'ITIM': |
| case 'IWRL': |
| case 'IUTI': |
| case 'IINX': |
| case 'IINY': |
| case 'IINZ': |
| case 'IREF': |
| if ( length === 4 ) this.currentNode[ blockID ] = this.reader.getInt32(); |
| else this.reader.skip( length ); |
| break; |
|
|
| case 'OTAG': |
| this.parseObjectTag(); |
| break; |
|
|
| case 'LAYR': |
| this.parseLayer( length ); |
| break; |
|
|
| case 'PNTS': |
| this.parsePoints( length ); |
| break; |
|
|
| case 'VMAP': |
| this.parseVertexMapping( length ); |
| break; |
|
|
| case 'POLS': |
| this.parsePolygonList( length ); |
| break; |
|
|
| case 'TAGS': |
| this.parseTagStrings( length ); |
| break; |
|
|
| case 'PTAG': |
| this.parsePolygonTagMapping( length ); |
| break; |
|
|
| case 'VMAD': |
| this.parseVertexMapping( length, true ); |
| break; |
|
|
| |
| case 'DESC': |
| this.currentForm.description = this.reader.getString(); |
| break; |
|
|
| case 'TEXT': |
| case 'CMNT': |
| case 'NCOM': |
| this.currentForm.comment = this.reader.getString(); |
| break; |
|
|
| |
| case 'NAME': |
| this.currentForm.channelName = this.reader.getString(); |
| break; |
|
|
| |
|
|
| case 'WRAP': |
| this.currentForm.wrap = { w: this.reader.getUint16(), h: this.reader.getUint16() }; |
| break; |
|
|
| case 'IMAG': |
| var index = this.reader.getVariableLengthIndex(); |
| this.currentForm.imageIndex = index; |
| break; |
|
|
| |
|
|
| case 'OREF': |
| this.currentForm.referenceObject = this.reader.getString(); |
| break; |
|
|
| case 'ROID': |
| this.currentForm.referenceObjectID = this.reader.getUint32(); |
| break; |
|
|
| |
|
|
| case 'SSHN': |
| this.currentSurface.surfaceShaderName = this.reader.getString(); |
| break; |
|
|
| case 'AOVN': |
| this.currentSurface.surfaceCustomAOVName = this.reader.getString(); |
| break; |
|
|
| |
|
|
| case 'NSTA': |
| this.currentForm.disabled = this.reader.getUint16(); |
| break; |
|
|
| case 'NRNM': |
| this.currentForm.realName = this.reader.getString(); |
| break; |
|
|
| case 'NNME': |
| this.currentForm.refName = this.reader.getString(); |
| this.currentSurface.nodes[ this.currentForm.refName ] = this.currentForm; |
| break; |
|
|
| |
| case 'INME': |
| if ( ! this.currentForm.nodeName ) this.currentForm.nodeName = []; |
| this.currentForm.nodeName.push( this.reader.getString() ); |
| break; |
|
|
| case 'IINN': |
| if ( ! this.currentForm.inputNodeName ) this.currentForm.inputNodeName = []; |
| this.currentForm.inputNodeName.push( this.reader.getString() ); |
| break; |
|
|
| case 'IINM': |
| if ( ! this.currentForm.inputName ) this.currentForm.inputName = []; |
| this.currentForm.inputName.push( this.reader.getString() ); |
| break; |
|
|
| case 'IONM': |
| if ( ! this.currentForm.inputOutputName ) this.currentForm.inputOutputName = []; |
| this.currentForm.inputOutputName.push( this.reader.getString() ); |
| break; |
|
|
| case 'FNAM': |
| this.currentForm.fileName = this.reader.getString(); |
| break; |
|
|
| case 'CHAN': |
| if ( length === 4 ) this.currentForm.textureChannel = this.reader.getIDTag(); |
| else this.reader.skip( length ); |
| break; |
|
|
| |
|
|
| case 'SMAN': |
| var maxSmoothingAngle = this.reader.getFloat32(); |
| this.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true; |
| break; |
|
|
| case 'ENAB': |
| this.currentForm.enabled = this.reader.getUint16(); |
| break; |
|
|
| |
| case 'COLR': |
| this.currentSurface.attributes.color = this.reader.getFloat32Array( 3 ); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'LUMI': |
| this.currentSurface.attributes.luminosityLevel = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'SPEC': |
| this.currentSurface.attributes.specularLevel = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'REFL': |
| this.currentSurface.attributes.reflectivity = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'TRAN': |
| this.currentSurface.attributes.opacity = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'BUMP': |
| this.currentSurface.attributes.bumpStrength = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'SIDE': |
| this.currentSurface.attributes.side = this.reader.getUint16(); |
| break; |
|
|
| case 'RIMG': |
| this.currentSurface.attributes.reflectionMap = this.reader.getVariableLengthIndex(); |
| break; |
|
|
| case 'RIND': |
| this.currentSurface.attributes.refractiveIndex = this.reader.getFloat32(); |
| this.reader.skip( 2 ); |
| break; |
|
|
| case 'TIMG': |
| this.currentSurface.attributes.refractionMap = this.reader.getVariableLengthIndex(); |
| break; |
|
|
| case 'IMAP': |
| this.currentSurface.attributes.imageMapIndex = this.reader.getUint32(); |
| break; |
|
|
| case 'IUVI': |
| this.currentNode.UVChannel = this.reader.getString( length ); |
| break; |
|
|
| case 'IUTL': |
| this.currentNode.widthWrappingMode = this.reader.getUint32(); |
| break; |
| case 'IVTL': |
| this.currentNode.heightWrappingMode = this.reader.getUint32(); |
| break; |
|
|
| default: |
| this.parseUnknownCHUNK( blockID, length ); |
|
|
| } |
|
|
| if ( this.reader.offset >= this.currentFormEnd ) { |
|
|
| this.currentForm = this.parentForm; |
|
|
| } |
|
|
| }, |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| parseForm( length ) { |
|
|
| var type = this.reader.getIDTag(); |
|
|
| switch ( type ) { |
|
|
| |
| |
|
|
| case 'ISEQ': |
| case 'ANIM': |
| case 'STCC': |
| case 'VPVL': |
| case 'VPRM': |
| case 'NROT': |
| case 'WRPW': |
| case 'WRPH': |
| case 'FUNC': |
| case 'FALL': |
| case 'OPAC': |
| case 'GRAD': |
| case 'ENVS': |
| case 'VMOP': |
| case 'VMBG': |
|
|
| |
| case 'OMAX': |
| case 'STEX': |
| case 'CKBG': |
| case 'CKEY': |
| case 'VMLA': |
| case 'VMLB': |
| this.skipForm( length ); |
| break; |
|
|
| |
| |
| case 'META': |
| case 'NNDS': |
| case 'NODS': |
| case 'NDTA': |
| case 'ADAT': |
| case 'AOVS': |
| case 'BLOK': |
|
|
| |
| case 'IBGC': |
| case 'IOPC': |
| case 'IIMG': |
| case 'TXTR': |
| |
| break; |
|
|
| case 'IFAL': |
| case 'ISCL': |
| case 'IPOS': |
| case 'IROT': |
| case 'IBMP': |
| case 'IUTD': |
| case 'IVTD': |
| this.parseTextureNodeAttribute( type ); |
| break; |
|
|
| case 'LWO3': |
| this.tree.format = type; |
| break; |
|
|
| case 'ENVL': |
| this.parseEnvelope( length ); |
| break; |
|
|
| |
|
|
| case 'CLIP': |
| this.parseClip( length ); |
| break; |
|
|
| case 'STIL': |
| this.parseImage(); |
| break; |
|
|
| case 'XREF': |
| this.reader.skip( 8 ); |
| this.currentForm.referenceTexture = { |
| index: this.reader.getUint32(), |
| refName: this.reader.getString() |
| }; |
| break; |
|
|
| |
|
|
| case 'IMST': |
| this.parseImageStateForm( length ); |
| break; |
|
|
| |
|
|
| case 'SURF': |
| this.parseSurfaceForm( length ); |
| break; |
|
|
| case 'VALU': |
| this.parseValueForm( length ); |
| break; |
|
|
| case 'NTAG': |
| this.parseSubNode( length ); |
| break; |
|
|
| case 'NNDS': |
| this.setupForm( 'nodes', length ); |
| break; |
|
|
| case 'ATTR': |
| case 'SATR': |
| this.setupForm( 'attributes', length ); |
| break; |
|
|
| case 'NCON': |
| this.parseConnections( length ); |
| break; |
|
|
| case 'SSHA': |
| this.parentForm = this.currentForm; |
| this.currentForm = this.currentSurface; |
| this.setupForm( 'surfaceShader', length ); |
| break; |
|
|
| case 'SSHD': |
| this.setupForm( 'surfaceShaderData', length ); |
| break; |
|
|
| case 'ENTR': |
| this.parseEntryForm( length ); |
| break; |
|
|
| |
|
|
| case 'IMAP': |
| this.parseImageMap( length ); |
| break; |
|
|
| case 'TAMP': |
| this.parseXVAL( 'amplitude', length ); |
| break; |
|
|
| |
|
|
| case 'TMAP': |
| this.setupForm( 'textureMap', length ); |
| break; |
|
|
| case 'CNTR': |
| this.parseXVAL3( 'center', length ); |
| break; |
|
|
| case 'SIZE': |
| this.parseXVAL3( 'scale', length ); |
| break; |
|
|
| case 'ROTA': |
| this.parseXVAL3( 'rotation', length ); |
| break; |
|
|
| default: |
| this.parseUnknownForm( type, length ); |
|
|
| } |
|
|
| }, |
|
|
| setupForm( type, length ) { |
|
|
| if ( ! this.currentForm ) this.currentForm = this.currentNode; |
|
|
| this.currentFormEnd = this.reader.offset + length; |
| this.parentForm = this.currentForm; |
|
|
| if ( ! this.currentForm[ type ] ) { |
|
|
| this.currentForm[ type ] = {}; |
| this.currentForm = this.currentForm[ type ]; |
|
|
|
|
| } else { |
|
|
| |
| console.warn( 'LWOLoader: form already exists on parent: ', type, this.currentForm ); |
|
|
| this.currentForm = this.currentForm[ type ]; |
|
|
| } |
|
|
|
|
| }, |
|
|
| skipForm( length ) { |
|
|
| this.reader.skip( length - 4 ); |
|
|
| }, |
|
|
| parseUnknownForm( type, length ) { |
|
|
| console.warn( 'LWOLoader: unknown FORM encountered: ' + type, length ); |
|
|
| printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 ); |
| this.reader.skip( length - 4 ); |
|
|
| }, |
|
|
| parseSurfaceForm( length ) { |
|
|
| this.reader.skip( 8 ); |
|
|
| var name = this.reader.getString(); |
|
|
| var surface = { |
| attributes: {}, |
| connections: {}, |
| name: name, |
| nodes: {}, |
| source: this.reader.getString(), |
| }; |
|
|
| this.tree.materials[ name ] = surface; |
| this.currentSurface = surface; |
|
|
| this.parentForm = this.tree.materials; |
| this.currentForm = surface; |
| this.currentFormEnd = this.reader.offset + length; |
|
|
| }, |
|
|
| parseSubNode( length ) { |
|
|
| |
| |
| |
|
|
| this.reader.skip( 8 ); |
| var name = this.reader.getString(); |
|
|
| var node = { |
| name: name |
| }; |
| this.currentForm = node; |
| this.currentNode = node; |
|
|
| this.currentFormEnd = this.reader.offset + length; |
|
|
|
|
| }, |
|
|
| |
| parseConnections( length ) { |
|
|
| this.currentFormEnd = this.reader.offset + length; |
| this.parentForm = this.currentForm; |
|
|
| this.currentForm = this.currentSurface.connections; |
|
|
| }, |
|
|
| |
| parseEntryForm( length ) { |
|
|
| this.reader.skip( 8 ); |
| var name = this.reader.getString(); |
| this.currentForm = this.currentNode.attributes; |
|
|
| this.setupForm( name, length ); |
|
|
| }, |
|
|
| |
| |
| parseValueForm() { |
|
|
| this.reader.skip( 8 ); |
|
|
| var valueType = this.reader.getString(); |
|
|
| if ( valueType === 'double' ) { |
|
|
| this.currentForm.value = this.reader.getUint64(); |
|
|
| } else if ( valueType === 'int' ) { |
|
|
| this.currentForm.value = this.reader.getUint32(); |
|
|
| } else if ( valueType === 'vparam' ) { |
|
|
| this.reader.skip( 24 ); |
| this.currentForm.value = this.reader.getFloat64(); |
|
|
| } else if ( valueType === 'vparam3' ) { |
|
|
| this.reader.skip( 24 ); |
| this.currentForm.value = this.reader.getFloat64Array( 3 ); |
|
|
|
|
| } |
|
|
| }, |
|
|
| |
| |
| parseImageStateForm() { |
|
|
| this.reader.skip( 8 ); |
|
|
| this.currentForm.mipMapLevel = this.reader.getFloat32(); |
|
|
| }, |
|
|
| |
| parseImageMap( length ) { |
|
|
| this.currentFormEnd = this.reader.offset + length; |
| this.parentForm = this.currentForm; |
|
|
| if ( ! this.currentForm.maps ) this.currentForm.maps = []; |
|
|
| var map = {}; |
| this.currentForm.maps.push( map ); |
| this.currentForm = map; |
|
|
| this.reader.skip( 10 ); |
|
|
| }, |
|
|
| parseTextureNodeAttribute( type ) { |
|
|
| this.reader.skip( 28 ); |
|
|
| this.reader.skip( 20 ); |
|
|
| switch ( type ) { |
|
|
| case 'ISCL': |
| this.currentNode.scale = this.reader.getFloat32Array( 3 ); |
| break; |
| case 'IPOS': |
| this.currentNode.position = this.reader.getFloat32Array( 3 ); |
| break; |
| case 'IROT': |
| this.currentNode.rotation = this.reader.getFloat32Array( 3 ); |
| break; |
| case 'IFAL': |
| this.currentNode.falloff = this.reader.getFloat32Array( 3 ); |
| break; |
|
|
| case 'IBMP': |
| this.currentNode.amplitude = this.reader.getFloat32(); |
| break; |
| case 'IUTD': |
| this.currentNode.uTiles = this.reader.getFloat32(); |
| break; |
| case 'IVTD': |
| this.currentNode.vTiles = this.reader.getFloat32(); |
| break; |
|
|
| } |
|
|
| this.reader.skip( 2 ); |
|
|
|
|
| }, |
|
|
| |
| parseEnvelope( length ) { |
|
|
| this.reader.skip( length - 4 ); |
|
|
| }, |
|
|
| |
| |
| |
|
|
| |
| |
| parseClip( length ) { |
|
|
| var tag = this.reader.getIDTag(); |
|
|
| |
| if ( tag === 'FORM' ) { |
|
|
| this.reader.skip( 16 ); |
|
|
| this.currentNode.fileName = this.reader.getString(); |
|
|
| return; |
|
|
| } |
|
|
| |
| this.reader.setOffset( this.reader.offset - 4 ); |
|
|
| this.currentFormEnd = this.reader.offset + length; |
| this.parentForm = this.currentForm; |
|
|
| this.reader.skip( 8 ); |
|
|
| var texture = { |
| index: this.reader.getUint32() |
| }; |
| this.tree.textures.push( texture ); |
| this.currentForm = texture; |
|
|
| }, |
|
|
| parseImage() { |
|
|
| this.reader.skip( 8 ); |
| this.currentForm.fileName = this.reader.getString(); |
|
|
| }, |
|
|
| parseXVAL( type, length ) { |
|
|
| var endOffset = this.reader.offset + length - 4; |
| this.reader.skip( 8 ); |
|
|
| this.currentForm[ type ] = this.reader.getFloat32(); |
|
|
| this.reader.setOffset( endOffset ); |
|
|
| }, |
|
|
| parseXVAL3( type, length ) { |
|
|
| var endOffset = this.reader.offset + length - 4; |
| this.reader.skip( 8 ); |
|
|
| this.currentForm[ type ] = { |
| x: this.reader.getFloat32(), |
| y: this.reader.getFloat32(), |
| z: this.reader.getFloat32(), |
| }; |
|
|
| this.reader.setOffset( endOffset ); |
|
|
| }, |
|
|
| |
| |
| parseObjectTag() { |
|
|
| if ( ! this.tree.objectTags ) this.tree.objectTags = {}; |
|
|
| this.tree.objectTags[ this.reader.getIDTag() ] = { |
| tagString: this.reader.getString() |
| }; |
|
|
| }, |
|
|
| |
| |
| parseLayer( length ) { |
|
|
| var layer = { |
| number: this.reader.getUint16(), |
| flags: this.reader.getUint16(), |
| pivot: this.reader.getFloat32Array( 3 ), |
| name: this.reader.getString(), |
| }; |
|
|
| this.tree.layers.push( layer ); |
| this.currentLayer = layer; |
|
|
| var parsedLength = 16 + stringOffset( this.currentLayer.name ); |
|
|
| |
| this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; |
|
|
| }, |
|
|
| |
| |
| |
| parsePoints( length ) { |
|
|
| this.currentPoints = []; |
| for ( var i = 0; i < length / 4; i += 3 ) { |
|
|
| |
| this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() ); |
|
|
| } |
|
|
| }, |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| parseVertexMapping( length, discontinuous ) { |
|
|
| var finalOffset = this.reader.offset + length; |
|
|
| var channelName = this.reader.getString(); |
|
|
| if ( this.reader.offset === finalOffset ) { |
|
|
| |
| this.currentForm.UVChannel = channelName; |
| return; |
|
|
| } |
|
|
| |
| this.reader.setOffset( this.reader.offset - stringOffset( channelName ) ); |
|
|
| var type = this.reader.getIDTag(); |
|
|
| this.reader.getUint16(); |
| var name = this.reader.getString(); |
|
|
| var remainingLength = length - 6 - stringOffset( name ); |
|
|
| switch ( type ) { |
|
|
| case 'TXUV': |
| this.parseUVMapping( name, finalOffset, discontinuous ); |
| break; |
| case 'MORF': |
| case 'SPOT': |
| this.parseMorphTargets( name, finalOffset, type ); |
| break; |
| |
| case 'APSL': |
| case 'NORM': |
| case 'WGHT': |
| case 'MNVW': |
| case 'PICK': |
| case 'RGB ': |
| case 'RGBA': |
| this.reader.skip( remainingLength ); |
| break; |
| default: |
| console.warn( 'LWOLoader: unknown vertex map type: ' + type ); |
| this.reader.skip( remainingLength ); |
|
|
| } |
|
|
| }, |
|
|
| parseUVMapping( name, finalOffset, discontinuous ) { |
|
|
| var uvIndices = []; |
| var polyIndices = []; |
| var uvs = []; |
|
|
| while ( this.reader.offset < finalOffset ) { |
|
|
| uvIndices.push( this.reader.getVariableLengthIndex() ); |
|
|
| if ( discontinuous ) polyIndices.push( this.reader.getVariableLengthIndex() ); |
|
|
| uvs.push( this.reader.getFloat32(), this.reader.getFloat32() ); |
|
|
| } |
|
|
| if ( discontinuous ) { |
|
|
| if ( ! this.currentLayer.discontinuousUVs ) this.currentLayer.discontinuousUVs = {}; |
|
|
| this.currentLayer.discontinuousUVs[ name ] = { |
| uvIndices: uvIndices, |
| polyIndices: polyIndices, |
| uvs: uvs, |
| }; |
|
|
| } else { |
|
|
| if ( ! this.currentLayer.uvs ) this.currentLayer.uvs = {}; |
|
|
| this.currentLayer.uvs[ name ] = { |
| uvIndices: uvIndices, |
| uvs: uvs, |
| }; |
|
|
| } |
|
|
| }, |
|
|
| parseMorphTargets( name, finalOffset, type ) { |
|
|
| var indices = []; |
| var points = []; |
|
|
| type = ( type === 'MORF' ) ? 'relative' : 'absolute'; |
|
|
| while ( this.reader.offset < finalOffset ) { |
|
|
| indices.push( this.reader.getVariableLengthIndex() ); |
| |
| points.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() ); |
|
|
| } |
|
|
| if ( ! this.currentLayer.morphTargets ) this.currentLayer.morphTargets = {}; |
|
|
| this.currentLayer.morphTargets[ name ] = { |
| indices: indices, |
| points: points, |
| type: type, |
| }; |
|
|
| }, |
|
|
| |
| |
| parsePolygonList( length ) { |
|
|
| var finalOffset = this.reader.offset + length; |
| var type = this.reader.getIDTag(); |
|
|
| var indices = []; |
|
|
| |
| var polygonDimensions = []; |
|
|
| while ( this.reader.offset < finalOffset ) { |
|
|
| var numverts = this.reader.getUint16(); |
|
|
| |
| numverts = numverts & 1023; |
| polygonDimensions.push( numverts ); |
|
|
| for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() ); |
|
|
| } |
|
|
| var geometryData = { |
| type: type, |
| vertexIndices: indices, |
| polygonDimensions: polygonDimensions, |
| points: this.currentPoints |
| }; |
|
|
| |
| if ( polygonDimensions[ 0 ] === 1 ) geometryData.type = 'points'; |
| else if ( polygonDimensions[ 0 ] === 2 ) geometryData.type = 'lines'; |
|
|
| this.currentLayer.geometry = geometryData; |
|
|
| }, |
|
|
| |
| |
| parseTagStrings( length ) { |
|
|
| this.tree.tags = this.reader.getStringArray( length ); |
|
|
| }, |
|
|
| |
| |
| parsePolygonTagMapping( length ) { |
|
|
| var finalOffset = this.reader.offset + length; |
| var type = this.reader.getIDTag(); |
| if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset ); |
| else { |
|
|
| this.reader.skip( length - 4 ); |
|
|
| } |
|
|
| }, |
|
|
| parseMaterialIndices( finalOffset ) { |
|
|
| |
| this.currentLayer.geometry.materialIndices = []; |
|
|
| var initialMatIndex; |
|
|
| while ( this.reader.offset < finalOffset ) { |
|
|
| var polygonIndex = this.reader.getVariableLengthIndex(); |
| var materialIndex = this.reader.getUint16(); |
|
|
| if ( ! initialMatIndex ) initialMatIndex = materialIndex; |
|
|
| this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex ); |
|
|
| } |
|
|
| }, |
|
|
| parseUnknownCHUNK( blockID, length ) { |
|
|
| console.warn( 'LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length ); |
|
|
| |
| |
|
|
| var data = this.reader.getString( length ); |
|
|
| this.currentForm[ blockID ] = data; |
|
|
| } |
|
|
| }; |
|
|
| function DataViewReader( buffer ) { |
|
|
| |
| |
|
|
| this.dv = new DataView( buffer ); |
| this.offset = 0; |
|
|
| } |
|
|
| DataViewReader.prototype = { |
|
|
| constructor: DataViewReader, |
|
|
| size: function () { |
|
|
| return this.dv.buffer.byteLength; |
|
|
| }, |
|
|
| setOffset( offset ) { |
|
|
| if ( offset > 0 && offset < this.dv.buffer.byteLength ) { |
|
|
| this.offset = offset; |
|
|
| } else { |
|
|
| console.error( 'LWOLoader: invalid buffer offset' ); |
|
|
| } |
|
|
| }, |
|
|
| endOfFile: function () { |
|
|
| if ( this.offset >= this.size() ) return true; |
| return false; |
|
|
| }, |
|
|
| skip: function ( length ) { |
|
|
| this.offset += length; |
|
|
| }, |
|
|
| getUint8: function () { |
|
|
| var value = this.dv.getUint8( this.offset ); |
| this.offset += 1; |
| return value; |
|
|
| }, |
|
|
| getUint16: function () { |
|
|
| var value = this.dv.getUint16( this.offset ); |
| this.offset += 2; |
| return value; |
|
|
| }, |
|
|
| getInt32: function () { |
|
|
| var value = this.dv.getInt32( this.offset, false ); |
| this.offset += 4; |
| return value; |
|
|
| }, |
|
|
| getUint32: function () { |
|
|
| var value = this.dv.getUint32( this.offset, false ); |
| this.offset += 4; |
| return value; |
|
|
| }, |
|
|
| getUint64: function () { |
|
|
| var low, high; |
|
|
| high = this.getUint32(); |
| low = this.getUint32(); |
| return high * 0x100000000 + low; |
|
|
| }, |
|
|
| getFloat32: function () { |
|
|
| var value = this.dv.getFloat32( this.offset, false ); |
| this.offset += 4; |
| return value; |
|
|
| }, |
|
|
| getFloat32Array: function ( size ) { |
|
|
| var a = []; |
|
|
| for ( var i = 0; i < size; i ++ ) { |
|
|
| a.push( this.getFloat32() ); |
|
|
| } |
|
|
| return a; |
|
|
| }, |
|
|
| getFloat64: function () { |
|
|
| var value = this.dv.getFloat64( this.offset, this.littleEndian ); |
| this.offset += 8; |
| return value; |
|
|
| }, |
|
|
| getFloat64Array: function ( size ) { |
|
|
| var a = []; |
|
|
| for ( var i = 0; i < size; i ++ ) { |
|
|
| a.push( this.getFloat64() ); |
|
|
| } |
|
|
| return a; |
|
|
| }, |
|
|
| |
| |
| |
| |
| |
| |
| getVariableLengthIndex() { |
|
|
| var firstByte = this.getUint8(); |
|
|
| if ( firstByte === 255 ) { |
|
|
| return this.getUint8() * 65536 + this.getUint8() * 256 + this.getUint8(); |
|
|
| } |
|
|
| return firstByte * 256 + this.getUint8(); |
|
|
| }, |
|
|
| |
| getIDTag() { |
|
|
| return this.getString( 4 ); |
|
|
| }, |
|
|
| getString: function ( size ) { |
|
|
| if ( size === 0 ) return; |
|
|
| |
| var a = []; |
|
|
| if ( size ) { |
|
|
| for ( var i = 0; i < size; i ++ ) { |
|
|
| a[ i ] = this.getUint8(); |
|
|
| } |
|
|
| } else { |
|
|
| var currentChar; |
| var len = 0; |
|
|
| while ( currentChar !== 0 ) { |
|
|
| currentChar = this.getUint8(); |
| if ( currentChar !== 0 ) a.push( currentChar ); |
| len ++; |
|
|
| } |
|
|
| if ( ! isEven( len + 1 ) ) this.getUint8(); |
|
|
| } |
|
|
| return THREE.LoaderUtils.decodeText( new Uint8Array( a ) ); |
|
|
| }, |
|
|
| getStringArray: function ( size ) { |
|
|
| var a = this.getString( size ); |
| a = a.split( '\0' ); |
|
|
| return a.filter( Boolean ); |
|
|
| } |
|
|
| }; |
|
|
| |
|
|
| function isEven( num ) { |
|
|
| return num % 2; |
|
|
| } |
|
|
| |
| |
| function stringOffset( string ) { |
|
|
| return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 ); |
|
|
| } |
|
|
| |
| |
| function printBuffer( buffer, from, to ) { |
|
|
| console.log( THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ) ); |
|
|
| } |
|
|
| return LWOLoader; |
|
|
| } )(); |
|
|