2017-03-13 15:28:51 -04:00
'use strict' ;
2016-07-22 14:09:13 -04:00
var Cesium = require ( 'cesium' ) ;
2015-10-16 17:32:23 -04:00
var path = require ( 'path' ) ;
2017-03-13 15:28:51 -04:00
var Promise = require ( 'bluebird' ) ;
2016-07-22 14:09:13 -04:00
2017-03-13 15:28:51 -04:00
var ArrayStorage = require ( './ArrayStorage' ) ;
2017-04-12 16:55:03 -04:00
var loadMtl = require ( './loadMtl' ) ;
2017-07-29 13:23:33 -04:00
var outsideDirectory = require ( './outsideDirectory' ) ;
2017-03-13 15:28:51 -04:00
var readLines = require ( './readLines' ) ;
2016-07-22 14:09:13 -04:00
2017-04-20 14:41:39 -04:00
var Cartesian3 = Cesium . Cartesian3 ;
2017-03-13 15:28:51 -04:00
var ComponentDatatype = Cesium . ComponentDatatype ;
2018-10-11 16:33:49 -04:00
var CoplanarPolygonGeometryLibrary = Cesium . CoplanarPolygonGeometryLibrary ;
2017-03-13 15:28:51 -04:00
var defaultValue = Cesium . defaultValue ;
2016-06-09 13:33:08 -04:00
var defined = Cesium . defined ;
2017-06-14 16:55:03 -04:00
var PolygonPipeline = Cesium . PolygonPipeline ;
2017-03-13 15:28:51 -04:00
var RuntimeError = Cesium . RuntimeError ;
2017-06-14 16:55:03 -04:00
var WindingOrder = Cesium . WindingOrder ;
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
module . exports = loadObj ;
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
// Object name (o) -> node
// Group name (g) -> mesh
// Material name (usemtl) -> primitive
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function Node ( ) {
this . name = undefined ;
this . meshes = [ ] ;
2015-10-16 17:32:23 -04:00
}
2017-03-13 15:28:51 -04:00
function Mesh ( ) {
this . name = undefined ;
this . primitives = [ ] ;
}
function Primitive ( ) {
this . material = undefined ;
this . indices = new ArrayStorage ( ComponentDatatype . UNSIGNED _INT ) ;
2018-08-30 15:24:34 -04:00
this . positions = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
this . normals = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
this . uvs = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
2017-03-13 15:28:51 -04:00
}
2015-10-19 17:03:50 -04:00
2017-03-13 15:28:51 -04:00
// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
2017-06-15 11:11:27 -04:00
var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // v float float float
var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // vn float float float
var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // vt float float
2017-06-27 16:31:14 -04:00
var facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g ; // for any face format "f v", "f v/v", "f v//v", "f v/v/v"
2017-03-13 15:28:51 -04:00
/ * *
* Parse an obj file .
*
* @ param { String } objPath Path to the obj file .
2017-07-29 13:23:33 -04:00
* @ param { Object } options The options object passed along from lib / obj2gltf . js
* @ returns { Promise } A promise resolving to the obj data , which includes an array of nodes containing geometry information and an array of materials .
2017-03-13 15:28:51 -04:00
*
* @ private
* /
2017-03-17 16:05:51 -04:00
function loadObj ( objPath , options ) {
2017-03-13 15:28:51 -04:00
// Global store of vertex attributes listed in the obj file
var positions = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
var normals = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
var uvs = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
// The current node, mesh, and primitive
var node ;
var mesh ;
var primitive ;
2017-12-28 16:16:26 -05:00
var activeMaterial ;
2017-03-13 15:28:51 -04:00
// All nodes seen in the obj
var nodes = [ ] ;
2018-08-30 15:24:34 -04:00
// Used to build the indices. The vertex cache is unique to each primitive.
2017-03-13 15:28:51 -04:00
var vertexCache = { } ;
var vertexCacheLimit = 1000000 ;
var vertexCacheCount = 0 ;
var vertexCount = 0 ;
// All mtl paths seen in the obj
var mtlPaths = [ ] ;
2017-06-15 17:57:05 -04:00
// Buffers for face data that spans multiple lines
2017-06-27 16:31:14 -04:00
var lineBuffer = '' ;
// Used for parsing face data
2017-07-27 13:45:37 -04:00
var faceVertices = [ ] ;
var facePositions = [ ] ;
var faceUvs = [ ] ;
var faceNormals = [ ] ;
2017-06-27 16:31:14 -04:00
2018-10-11 13:20:50 -04:00
function clearVertexCache ( ) {
vertexCache = { } ;
vertexCacheCount = 0 ;
}
2017-03-13 15:28:51 -04:00
function getName ( name ) {
return ( name === '' ? undefined : name ) ;
}
function addNode ( name ) {
node = new Node ( ) ;
node . name = getName ( name ) ;
nodes . push ( node ) ;
addMesh ( ) ;
}
function addMesh ( name ) {
mesh = new Mesh ( ) ;
mesh . name = getName ( name ) ;
node . meshes . push ( mesh ) ;
addPrimitive ( ) ;
}
function addPrimitive ( ) {
primitive = new Primitive ( ) ;
2017-12-28 16:16:26 -05:00
primitive . material = activeMaterial ;
2017-03-13 15:28:51 -04:00
mesh . primitives . push ( primitive ) ;
2018-08-30 23:36:28 -04:00
// Clear the vertex cache for each new primitive
2018-10-11 13:20:50 -04:00
clearVertexCache ( ) ;
2018-08-30 23:36:28 -04:00
vertexCount = 0 ;
2017-03-13 15:28:51 -04:00
}
function useMaterial ( name ) {
var material = getName ( name ) ;
2017-12-28 16:16:26 -05:00
activeMaterial = material ;
// Look to see if this material has already been used by a primitive in the mesh
2017-03-13 15:28:51 -04:00
var primitives = mesh . primitives ;
var primitivesLength = primitives . length ;
for ( var i = 0 ; i < primitivesLength ; ++ i ) {
2017-03-17 11:40:54 -04:00
if ( primitives [ i ] . material === material ) {
primitive = primitives [ i ] ;
2018-10-11 13:20:50 -04:00
clearVertexCache ( ) ;
2018-10-10 18:27:56 -04:00
vertexCount = primitive . positions . length / 3 ;
2017-03-13 15:28:51 -04:00
return ;
2016-07-22 16:17:27 -04:00
}
2016-06-22 16:16:29 -04:00
}
2017-03-13 15:28:51 -04:00
// Add a new primitive with this material
addPrimitive ( ) ;
}
2016-07-22 16:17:27 -04:00
2017-03-13 15:28:51 -04:00
function getOffset ( a , attributeData , components ) {
var i = parseInt ( a ) ;
if ( i < 0 ) {
// Negative vertex indexes reference the vertices immediately above it
return ( attributeData . length / components + i ) * components ;
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
return ( i - 1 ) * components ;
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function createVertex ( p , u , n ) {
// Positions
2017-06-27 16:31:14 -04:00
if ( p . length > 0 ) {
2017-03-13 15:28:51 -04:00
var pi = getOffset ( p , positions , 3 ) ;
var px = positions . get ( pi + 0 ) ;
var py = positions . get ( pi + 1 ) ;
var pz = positions . get ( pi + 2 ) ;
2018-08-30 15:24:34 -04:00
primitive . positions . push ( px ) ;
primitive . positions . push ( py ) ;
primitive . positions . push ( pz ) ;
2016-06-09 13:33:08 -04:00
}
2016-07-22 16:17:27 -04:00
2017-03-13 15:28:51 -04:00
// Normals
2017-06-27 16:31:14 -04:00
if ( n . length > 0 ) {
2017-03-13 15:28:51 -04:00
var ni = getOffset ( n , normals , 3 ) ;
var nx = normals . get ( ni + 0 ) ;
var ny = normals . get ( ni + 1 ) ;
var nz = normals . get ( ni + 2 ) ;
2018-08-30 15:24:34 -04:00
primitive . normals . push ( nx ) ;
primitive . normals . push ( ny ) ;
primitive . normals . push ( nz ) ;
2017-03-13 15:28:51 -04:00
}
2016-07-22 16:17:27 -04:00
2017-03-13 15:28:51 -04:00
// UVs
2017-06-27 16:31:14 -04:00
if ( u . length > 0 ) {
2017-03-13 15:28:51 -04:00
var ui = getOffset ( u , uvs , 2 ) ;
var ux = uvs . get ( ui + 0 ) ;
var uy = uvs . get ( ui + 1 ) ;
2018-08-30 15:24:34 -04:00
primitive . uvs . push ( ux ) ;
primitive . uvs . push ( uy ) ;
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function addVertex ( v , p , u , n ) {
var index = vertexCache [ v ] ;
if ( ! defined ( index ) ) {
index = vertexCount ++ ;
vertexCache [ v ] = index ;
createVertex ( p , u , n ) ;
// Prevent the vertex cache from growing too large. As a result of clearing the cache there
// may be some duplicate vertices.
vertexCacheCount ++ ;
if ( vertexCacheCount > vertexCacheLimit ) {
2018-10-11 13:20:50 -04:00
clearVertexCache ( ) ;
2016-07-22 16:17:27 -04:00
}
2017-03-13 15:28:51 -04:00
}
return index ;
}
2015-10-19 17:03:50 -04:00
2018-10-11 16:33:49 -04:00
function getPosition ( index , result ) {
2017-06-15 17:57:05 -04:00
var pi = getOffset ( index , positions , 3 ) ;
var px = positions . get ( pi + 0 ) ;
var py = positions . get ( pi + 1 ) ;
var pz = positions . get ( pi + 2 ) ;
return Cartesian3 . fromElements ( px , py , pz , result ) ;
}
2018-10-11 16:33:49 -04:00
function getNormal ( index , result ) {
2017-06-16 13:51:29 -04:00
var ni = getOffset ( index , normals , 3 ) ;
var nx = normals . get ( ni + 0 ) ;
var ny = normals . get ( ni + 1 ) ;
var nz = normals . get ( ni + 2 ) ;
return Cartesian3 . fromElements ( nx , ny , nz , result ) ;
}
2017-06-15 11:11:27 -04:00
var scratch1 = new Cartesian3 ( ) ;
var scratch2 = new Cartesian3 ( ) ;
2017-06-16 13:51:29 -04:00
var scratch3 = new Cartesian3 ( ) ;
2017-08-08 11:59:02 -04:00
var scratch4 = new Cartesian3 ( ) ;
var scratch5 = new Cartesian3 ( ) ;
2018-10-11 16:33:49 -04:00
var scratchCenter = new Cartesian3 ( ) ;
var scratchAxis1 = new Cartesian3 ( ) ;
var scratchAxis2 = new Cartesian3 ( ) ;
var scratchNormal = new Cartesian3 ( ) ;
var scratchPositions = [ new Cartesian3 ( ) , new Cartesian3 ( ) , new Cartesian3 ( ) , new Cartesian3 ( ) ] ;
var scratchVertexIndices = [ ] ;
var scratchPoints = [ ] ;
function checkWindingCorrect ( positionIndex1 , positionIndex2 , positionIndex3 , normalIndex ) {
if ( normalIndex . length === 0 ) {
// If no face normal, we have to assume the winding is correct.
return true ;
}
var normal = getNormal ( normalIndex , scratchNormal ) ;
var A = getPosition ( positionIndex1 , scratch1 ) ;
var B = getPosition ( positionIndex2 , scratch2 ) ;
var C = getPosition ( positionIndex3 , scratch3 ) ;
2017-06-16 13:51:29 -04:00
2017-08-08 11:59:02 -04:00
var BA = Cartesian3 . subtract ( B , A , scratch4 ) ;
var CA = Cartesian3 . subtract ( C , A , scratch5 ) ;
var cross = Cartesian3 . cross ( BA , CA , scratch3 ) ;
2017-06-16 13:51:29 -04:00
return ( Cartesian3 . dot ( normal , cross ) >= 0 ) ;
}
function addTriangle ( index1 , index2 , index3 , correctWinding ) {
if ( correctWinding ) {
primitive . indices . push ( index1 ) ;
primitive . indices . push ( index2 ) ;
primitive . indices . push ( index3 ) ;
} else {
2017-03-13 15:28:51 -04:00
primitive . indices . push ( index1 ) ;
primitive . indices . push ( index3 ) ;
2017-06-16 13:51:29 -04:00
primitive . indices . push ( index2 ) ;
}
}
2017-06-13 20:38:38 -04:00
function addFace ( vertices , positions , uvs , normals ) {
2018-10-11 16:33:49 -04:00
var i ;
var isWindingCorrect ;
2018-08-30 23:36:28 -04:00
var firstFace = primitive . indices . length === 0 ;
var faceHasUvs = uvs [ 0 ] . length > 0 ;
var faceHasNormals = normals [ 0 ] . length > 0 ;
var primitiveHasUvs = primitive . uvs . length > 0 ;
var primitiveHasNormals = primitive . normals . length > 0 ;
if ( ! firstFace && ( faceHasUvs !== primitiveHasUvs || faceHasNormals !== primitiveHasNormals ) ) {
// Discard faces that don't use the same attributes
return ;
}
2017-06-14 16:55:03 -04:00
if ( vertices . length === 3 ) {
2018-10-11 16:33:49 -04:00
isWindingCorrect = checkWindingCorrect ( positions [ 0 ] , positions [ 1 ] , positions [ 2 ] , normals [ 0 ] ) ;
2017-06-27 16:31:14 -04:00
var index1 = addVertex ( vertices [ 0 ] , positions [ 0 ] , uvs [ 0 ] , normals [ 0 ] ) ;
var index2 = addVertex ( vertices [ 1 ] , positions [ 1 ] , uvs [ 1 ] , normals [ 1 ] ) ;
var index3 = addVertex ( vertices [ 2 ] , positions [ 2 ] , uvs [ 2 ] , normals [ 2 ] ) ;
2017-06-26 12:47:08 -04:00
addTriangle ( index1 , index2 , index3 , isWindingCorrect ) ;
2017-06-14 16:55:03 -04:00
} else { // Triangulate if the face is not a triangle
2018-10-11 16:33:49 -04:00
var points = scratchPoints ;
var vertexIndices = scratchVertexIndices ;
points . length = 0 ;
2017-06-27 16:31:14 -04:00
vertexIndices . length = 0 ;
2017-06-14 16:55:03 -04:00
2017-06-15 11:11:27 -04:00
for ( i = 0 ; i < vertices . length ; ++ i ) {
2017-06-27 16:31:14 -04:00
var index = addVertex ( vertices [ i ] , positions [ i ] , uvs [ i ] , normals [ i ] ) ;
2017-06-14 16:55:03 -04:00
vertexIndices . push ( index ) ;
2018-10-11 16:33:49 -04:00
if ( i === scratchPositions . length ) {
scratchPositions . push ( new Cartesian3 ( ) ) ;
2017-07-27 13:45:37 -04:00
}
2018-10-11 16:33:49 -04:00
points . push ( getPosition ( positions [ i ] , scratchPositions [ i ] ) ) ;
2017-06-14 16:55:03 -04:00
}
2018-10-11 16:33:49 -04:00
var validGeometry = CoplanarPolygonGeometryLibrary . computeProjectTo2DArguments ( points , scratchCenter , scratchAxis1 , scratchAxis2 ) ;
if ( ! validGeometry ) {
return ;
}
var projectPoints = CoplanarPolygonGeometryLibrary . createProjectPointsTo2DFunction ( scratchCenter , scratchAxis1 , scratchAxis2 ) ;
var points2D = projectPoints ( points ) ;
var indices = PolygonPipeline . triangulate ( points2D ) ;
isWindingCorrect = PolygonPipeline . computeWindingOrder2D ( points2D ) !== WindingOrder . CLOCKWISE ;
2017-06-15 11:11:27 -04:00
2018-10-11 16:33:49 -04:00
for ( i = 0 ; i < indices . length - 2 ; i += 3 ) {
addTriangle ( vertexIndices [ indices [ i ] ] , vertexIndices [ indices [ i + 1 ] ] , vertexIndices [ indices [ i + 2 ] ] , isWindingCorrect ) ;
2017-06-14 16:55:03 -04:00
}
2016-07-22 16:17:27 -04:00
}
2017-03-13 15:28:51 -04:00
}
2016-06-09 13:33:08 -04:00
2017-03-13 15:28:51 -04:00
function parseLine ( line ) {
line = line . trim ( ) ;
var result ;
if ( ( line . length === 0 ) || ( line . charAt ( 0 ) === '#' ) ) {
// Don't process empty lines or comments
} else if ( /^o\s/i . test ( line ) ) {
var objectName = line . substring ( 2 ) . trim ( ) ;
addNode ( objectName ) ;
} else if ( /^g\s/i . test ( line ) ) {
var groupName = line . substring ( 2 ) . trim ( ) ;
addMesh ( groupName ) ;
} else if ( /^usemtl\s/i . test ( line ) ) {
var materialName = line . substring ( 7 ) . trim ( ) ;
useMaterial ( materialName ) ;
} else if ( /^mtllib/i . test ( line ) ) {
2017-11-17 11:53:04 -05:00
var mtllibLine = line . substring ( 7 ) . trim ( ) ;
mtlPaths = mtlPaths . concat ( getMtlPaths ( mtllibLine ) ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = vertexPattern . exec ( line ) ) !== null ) {
2017-07-29 13:23:33 -04:00
positions . push ( parseFloat ( result [ 1 ] ) ) ;
positions . push ( parseFloat ( result [ 2 ] ) ) ;
positions . push ( parseFloat ( result [ 3 ] ) ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = normalPattern . exec ( line ) ) !== null ) {
2018-03-20 22:38:37 -04:00
var normal = Cartesian3 . fromElements ( parseFloat ( result [ 1 ] ) , parseFloat ( result [ 2 ] ) , parseFloat ( result [ 3 ] ) , scratchNormal ) ;
if ( Cartesian3 . equals ( normal , Cartesian3 . ZERO ) ) {
Cartesian3 . clone ( Cartesian3 . UNIT _Z , normal ) ;
} else {
Cartesian3 . normalize ( normal , normal ) ;
}
normals . push ( normal . x ) ;
normals . push ( normal . y ) ;
normals . push ( normal . z ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = uvPattern . exec ( line ) ) !== null ) {
uvs . push ( parseFloat ( result [ 1 ] ) ) ;
uvs . push ( 1.0 - parseFloat ( result [ 2 ] ) ) ; // Flip y so 0.0 is the bottom of the image
2017-06-15 17:57:05 -04:00
} else { // face line or invalid line
2017-06-26 12:23:34 -04:00
// Because face lines can contain n vertices, we use a line buffer in case the face data spans multiple lines.
// If there's a line continuation don't create face yet
if ( line . slice ( - 1 ) === '\\' ) {
2017-06-27 16:31:14 -04:00
lineBuffer += line . substring ( 0 , line . length - 1 ) ;
2017-06-26 12:23:34 -04:00
return ;
}
lineBuffer += line ;
2017-06-27 16:31:14 -04:00
if ( lineBuffer . substring ( 0 , 2 ) === 'f ' ) {
2017-07-24 16:42:03 -04:00
while ( ( result = facePattern . exec ( lineBuffer ) ) !== null ) {
2017-06-27 16:31:14 -04:00
faceVertices . push ( result [ 0 ] ) ;
facePositions . push ( result [ 1 ] ) ;
faceUvs . push ( result [ 2 ] ) ;
faceNormals . push ( result [ 3 ] ) ;
}
2017-12-12 09:34:35 -05:00
if ( faceVertices . length > 2 ) {
addFace ( faceVertices , facePositions , faceUvs , faceNormals ) ;
}
2017-06-27 16:31:14 -04:00
faceVertices . length = 0 ;
facePositions . length = 0 ;
faceNormals . length = 0 ;
faceUvs . length = 0 ;
2017-06-13 20:38:38 -04:00
}
2017-06-27 16:31:14 -04:00
lineBuffer = '' ;
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
// Create a default node in case there are no o/g/usemtl lines in the obj
addNode ( ) ;
// Parse the obj file
return readLines ( objPath , parseLine )
. then ( function ( ) {
2017-09-21 10:37:50 -04:00
// Add hasNormals to options object for loadMtl
options . hasNormals = normals . length > 0 ;
2017-03-13 15:28:51 -04:00
// Unload resources
positions = undefined ;
normals = undefined ;
uvs = undefined ;
2017-07-29 13:23:33 -04:00
// Load materials and textures
2018-08-30 23:04:45 -04:00
return finishLoading ( nodes , mtlPaths , objPath , defined ( activeMaterial ) , options ) ;
2016-07-22 16:17:27 -04:00
} ) ;
2017-03-13 15:28:51 -04:00
}
2016-07-22 16:17:27 -04:00
2017-11-17 11:53:04 -05:00
function getMtlPaths ( mtllibLine ) {
// Handle paths with spaces. E.g. mtllib my material file.mtl
var mtlPaths = [ ] ;
var splits = mtllibLine . split ( ' ' ) ;
var length = splits . length ;
var startIndex = 0 ;
for ( var i = 0 ; i < length ; ++ i ) {
2017-11-29 13:49:30 -05:00
if ( path . extname ( splits [ i ] ) !== '.mtl' ) {
2017-11-17 11:53:04 -05:00
continue ;
}
var mtlPath = splits . slice ( startIndex , i + 1 ) . join ( ' ' ) ;
mtlPaths . push ( mtlPath ) ;
startIndex = i + 1 ;
}
return mtlPaths ;
}
2018-08-30 23:04:45 -04:00
function finishLoading ( nodes , mtlPaths , objPath , usesMaterials , options ) {
2017-03-13 15:28:51 -04:00
nodes = cleanNodes ( nodes ) ;
if ( nodes . length === 0 ) {
2017-07-29 13:23:33 -04:00
throw new RuntimeError ( objPath + ' does not have any geometry data' ) ;
2017-03-13 15:28:51 -04:00
}
2017-07-29 13:23:33 -04:00
var name = path . basename ( objPath , path . extname ( objPath ) ) ;
return loadMtls ( mtlPaths , objPath , options )
2017-03-13 15:28:51 -04:00
. then ( function ( materials ) {
2018-08-30 23:04:45 -04:00
if ( materials . length > 0 && ! usesMaterials ) {
assignDefaultMaterial ( nodes , materials , usesMaterials ) ;
}
2017-07-29 13:23:33 -04:00
return {
nodes : nodes ,
materials : materials ,
name : name
} ;
2016-06-22 10:07:08 -04:00
} ) ;
2017-03-13 15:28:51 -04:00
}
2018-08-30 10:42:10 -04:00
function normalizeMtlPath ( mtlPath , objDirectory ) {
mtlPath = mtlPath . replace ( /\\/g , '/' ) ;
return path . normalize ( path . join ( objDirectory , mtlPath ) ) ;
}
2017-07-29 13:23:33 -04:00
function loadMtls ( mtlPaths , objPath , options ) {
2017-04-10 17:57:56 -04:00
var objDirectory = path . dirname ( objPath ) ;
2017-05-03 17:59:24 -04:00
var materials = [ ] ;
2017-12-28 16:16:26 -05:00
// Remove duplicates
mtlPaths = mtlPaths . filter ( function ( value , index , self ) {
return self . indexOf ( value ) === index ;
} ) ;
2017-03-13 15:28:51 -04:00
return Promise . map ( mtlPaths , function ( mtlPath ) {
2018-08-30 10:42:10 -04:00
mtlPath = normalizeMtlPath ( mtlPath , objDirectory ) ;
var shallowPath = path . join ( objDirectory , path . basename ( mtlPath ) ) ;
2017-07-29 13:23:33 -04:00
if ( options . secure && outsideDirectory ( mtlPath , objDirectory ) ) {
2017-11-29 14:21:59 -05:00
// Try looking for the .mtl in the same directory as the obj
2017-12-21 22:19:52 -05:00
options . logger ( 'The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.' ) ;
2017-11-29 14:21:59 -05:00
return loadMtl ( shallowPath , options )
. then ( function ( materialsInMtl ) {
materials = materials . concat ( materialsInMtl ) ;
} )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger ( 'Could not read material file at ' + shallowPath + '. Using default material instead.' ) ;
2017-11-29 14:21:59 -05:00
} ) ;
2017-04-04 16:45:21 -04:00
}
2017-11-17 15:07:52 -05:00
2017-05-04 17:58:13 -04:00
return loadMtl ( mtlPath , options )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
2017-11-17 15:07:52 -05:00
// Try looking for the .mtl in the same directory as the obj
2017-12-21 22:19:52 -05:00
options . logger ( error . message ) ;
options . logger ( 'Could not read material file at ' + mtlPath + '. Attempting to read the material file from within the obj directory instead.' ) ;
2017-11-17 15:07:52 -05:00
return loadMtl ( shallowPath , options ) ;
} )
2017-03-13 15:28:51 -04:00
. then ( function ( materialsInMtl ) {
2017-05-03 17:59:24 -04:00
materials = materials . concat ( materialsInMtl ) ;
2017-04-04 16:45:21 -04:00
} )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger ( 'Could not read material file at ' + shallowPath + '. Using default material instead.' ) ;
2017-03-13 15:28:51 -04:00
} ) ;
2017-04-10 17:57:56 -04:00
} , { concurrency : 10 } )
2017-05-03 17:59:24 -04:00
. then ( function ( ) {
return materials ;
} ) ;
2017-03-13 15:28:51 -04:00
}
2018-03-06 19:22:06 -05:00
function assignDefaultMaterial ( nodes , materials ) {
var defaultMaterial = materials [ 0 ] . name ;
var nodesLength = nodes . length ;
for ( var i = 0 ; i < nodesLength ; ++ i ) {
var meshes = nodes [ i ] . meshes ;
var meshesLength = meshes . length ;
for ( var j = 0 ; j < meshesLength ; ++ j ) {
var primitives = meshes [ j ] . primitives ;
var primitivesLength = primitives . length ;
for ( var k = 0 ; k < primitivesLength ; ++ k ) {
var primitive = primitives [ k ] ;
primitive . material = defaultValue ( primitive . material , defaultMaterial ) ;
}
}
}
}
2017-03-13 15:28:51 -04:00
function removeEmptyMeshes ( meshes ) {
2017-04-10 17:57:56 -04:00
return meshes . filter ( function ( mesh ) {
// Remove empty primitives
mesh . primitives = mesh . primitives . filter ( function ( primitive ) {
2018-08-30 15:24:34 -04:00
return primitive . indices . length > 0 && primitive . positions . length > 0 ;
2017-04-10 17:57:56 -04:00
} ) ;
2018-08-30 15:24:34 -04:00
// Valid meshes must have at least one primitive
return ( mesh . primitives . length > 0 ) ;
2017-04-10 17:57:56 -04:00
} ) ;
2017-03-13 15:28:51 -04:00
}
2016-08-08 11:35:21 -04:00
2017-03-13 15:28:51 -04:00
function meshesHaveNames ( meshes ) {
var meshesLength = meshes . length ;
for ( var i = 0 ; i < meshesLength ; ++ i ) {
if ( defined ( meshes [ i ] . name ) ) {
return true ;
}
}
return false ;
2016-06-22 10:07:08 -04:00
}
2017-03-13 15:28:51 -04:00
function removeEmptyNodes ( nodes ) {
var final = [ ] ;
var nodesLength = nodes . length ;
for ( var i = 0 ; i < nodesLength ; ++ i ) {
var node = nodes [ i ] ;
var meshes = removeEmptyMeshes ( node . meshes ) ;
if ( meshes . length === 0 ) {
continue ;
}
node . meshes = meshes ;
if ( ! defined ( node . name ) && meshesHaveNames ( meshes ) ) {
// If the obj has groups (g) but not object groups (o) then convert meshes to nodes
var meshesLength = meshes . length ;
for ( var j = 0 ; j < meshesLength ; ++ j ) {
var mesh = meshes [ j ] ;
var convertedNode = new Node ( ) ;
convertedNode . name = mesh . name ;
convertedNode . meshes = [ mesh ] ;
final . push ( convertedNode ) ;
2016-07-22 14:09:13 -04:00
}
2017-03-13 15:28:51 -04:00
} else {
final . push ( node ) ;
}
}
return final ;
}
2016-06-09 13:33:08 -04:00
2017-03-13 15:28:51 -04:00
function setDefaultNames ( items , defaultName , usedNames ) {
var itemsLength = items . length ;
for ( var i = 0 ; i < itemsLength ; ++ i ) {
var item = items [ i ] ;
var name = defaultValue ( item . name , defaultName ) ;
var occurrences = usedNames [ name ] ;
if ( defined ( occurrences ) ) {
usedNames [ name ] ++ ;
name = name + '_' + occurrences ;
} else {
usedNames [ name ] = 1 ;
}
item . name = name ;
}
}
2016-07-22 14:09:13 -04:00
2017-03-13 15:28:51 -04:00
function setDefaults ( nodes ) {
var usedNames = { } ;
setDefaultNames ( nodes , 'Node' , usedNames ) ;
var nodesLength = nodes . length ;
for ( var i = 0 ; i < nodesLength ; ++ i ) {
var node = nodes [ i ] ;
setDefaultNames ( node . meshes , node . name + '-Mesh' , usedNames ) ;
}
}
function cleanNodes ( nodes ) {
nodes = removeEmptyNodes ( nodes ) ;
setDefaults ( nodes ) ;
return nodes ;
2016-06-09 13:33:08 -04:00
}