2017-03-13 15:28:51 -04:00
'use strict' ;
2019-02-05 20:59:09 -05:00
const Cesium = require ( 'cesium' ) ;
const path = require ( 'path' ) ;
const Promise = require ( 'bluebird' ) ;
const ArrayStorage = require ( './ArrayStorage' ) ;
const loadMtl = require ( './loadMtl' ) ;
const outsideDirectory = require ( './outsideDirectory' ) ;
const readLines = require ( './readLines' ) ;
2017-04-20 14:41:39 -04:00
const Axis = Cesium . Axis ;
2019-02-05 20:59:09 -05:00
const Cartesian3 = Cesium . Cartesian3 ;
const ComponentDatatype = Cesium . ComponentDatatype ;
const CoplanarPolygonGeometryLibrary = Cesium . CoplanarPolygonGeometryLibrary ;
const defaultValue = Cesium . defaultValue ;
const defined = Cesium . defined ;
const PolygonPipeline = Cesium . PolygonPipeline ;
const RuntimeError = Cesium . RuntimeError ;
const WindingOrder = Cesium . WindingOrder ;
2017-04-20 14:41:39 -04:00
const Matrix4 = Cesium . Matrix4 ;
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)
2019-02-05 20:59:09 -05:00
const vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // v float float float
const normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // vn float float float
const uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/ ; // vt float float
const 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
2019-08-19 19:33:12 -04:00
const scratchCartesian = new Cartesian3 ( ) ;
2017-04-20 14:41:39 -04:00
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 ) {
2019-08-19 19:33:12 -04:00
const axisTransform = getAxisTransform ( options . inputUpAxis , options . outputUpAxis ) ;
2017-04-20 14:41:39 -04:00
2017-03-13 15:28:51 -04:00
// Global store of vertex attributes listed in the obj file
2019-04-07 17:17:59 -04:00
let globalPositions = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
let globalNormals = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
let globalUvs = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
2017-03-13 15:28:51 -04:00
// The current node, mesh, and primitive
2019-02-05 20:59:09 -05:00
let node ;
let mesh ;
let primitive ;
let activeMaterial ;
2017-03-13 15:28:51 -04:00
// All nodes seen in the obj
2019-02-05 20:59:09 -05:00
const nodes = [ ] ;
2017-03-13 15:28:51 -04:00
2018-08-30 15:24:34 -04:00
// Used to build the indices. The vertex cache is unique to each primitive.
2019-02-05 20:59:09 -05:00
let vertexCache = { } ;
const vertexCacheLimit = 1000000 ;
let vertexCacheCount = 0 ;
let vertexCount = 0 ;
2017-03-13 15:28:51 -04:00
// All mtl paths seen in the obj
2019-02-05 20:59:09 -05:00
let mtlPaths = [ ] ;
2017-03-13 15:28:51 -04:00
2017-06-15 17:57:05 -04:00
// Buffers for face data that spans multiple lines
2019-02-05 20:59:09 -05:00
let lineBuffer = '' ;
2017-06-27 16:31:14 -04:00
// Used for parsing face data
2019-02-05 20:59:09 -05:00
const faceVertices = [ ] ;
const facePositions = [ ] ;
const faceUvs = [ ] ;
const 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
}
2018-10-17 21:42:28 -04:00
function reusePrimitive ( callback ) {
2019-02-05 20:59:09 -05:00
const primitives = mesh . primitives ;
const primitivesLength = primitives . length ;
for ( let i = 0 ; i < primitivesLength ; ++ i ) {
2018-10-17 21:42:28 -04:00
if ( primitives [ i ] . material === activeMaterial ) {
if ( ! defined ( callback ) || callback ( primitives [ i ] ) ) {
primitive = primitives [ i ] ;
clearVertexCache ( ) ;
vertexCount = primitive . positions . length / 3 ;
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
addPrimitive ( ) ;
}
2016-07-22 16:17:27 -04:00
2018-10-17 21:42:28 -04:00
function useMaterial ( name ) {
activeMaterial = getName ( name ) ;
reusePrimitive ( ) ;
}
function faceAndPrimitiveMatch ( uvs , normals , primitive ) {
2019-04-07 17:17:59 -04:00
const faceHasUvs = defined ( uvs [ 0 ] ) ;
const faceHasNormals = defined ( normals [ 0 ] ) ;
2019-02-05 20:59:09 -05:00
const primitiveHasUvs = primitive . uvs . length > 0 ;
const primitiveHasNormals = primitive . normals . length > 0 ;
2018-10-17 21:42:28 -04:00
return primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals ;
}
function checkPrimitive ( uvs , normals ) {
2019-02-05 20:59:09 -05:00
const firstFace = primitive . indices . length === 0 ;
2018-10-17 21:42:28 -04:00
if ( ! firstFace && ! faceAndPrimitiveMatch ( uvs , normals , primitive ) ) {
reusePrimitive ( function ( primitive ) {
return faceAndPrimitiveMatch ( uvs , normals , primitive ) ;
} ) ;
}
}
2019-04-07 17:17:59 -04:00
function getIndexFromStart ( index , attributeData , components ) {
const i = parseInt ( index ) ;
2017-03-13 15:28:51 -04:00
if ( i < 0 ) {
// Negative vertex indexes reference the vertices immediately above it
2019-04-07 17:17:59 -04:00
return ( attributeData . length / components + i ) ;
}
return i - 1 ;
}
function correctAttributeIndices ( attributeIndices , attributeData , components ) {
const length = attributeIndices . length ;
for ( let i = 0 ; i < length ; ++ i ) {
if ( attributeIndices [ i ] . length === 0 ) {
attributeIndices [ i ] = undefined ;
} else {
attributeIndices [ i ] = getIndexFromStart ( attributeIndices [ i ] , attributeData , components ) ;
}
}
}
function correctVertices ( vertices , positions , uvs , normals ) {
const length = vertices . length ;
for ( let i = 0 ; i < length ; ++ i ) {
vertices [ i ] = defaultValue ( positions [ i ] , '' ) + '/' + defaultValue ( uvs [ i ] , '' ) + '/' + defaultValue ( normals [ i ] , '' ) ;
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 createVertex ( p , u , n ) {
// Positions
2019-10-26 20:15:10 -04:00
if ( defined ( p ) && ( globalPositions . length > 0 ) ) {
2019-10-27 15:00:35 -04:00
if ( p * 3 >= globalPositions . length ) {
throw new RuntimeError ( ` Position index ${ p } is out of bounds ` ) ;
}
2019-04-07 17:17:59 -04:00
const px = globalPositions . get ( p * 3 ) ;
const py = globalPositions . get ( p * 3 + 1 ) ;
const pz = globalPositions . get ( p * 3 + 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
2019-10-26 20:15:10 -04:00
if ( defined ( n ) && ( globalNormals . length > 0 ) ) {
2019-10-27 15:00:35 -04:00
if ( n * 3 >= globalNormals . length ) {
throw new RuntimeError ( ` Normal index ${ n } is out of bounds ` ) ;
}
2019-04-07 17:17:59 -04:00
const nx = globalNormals . get ( n * 3 ) ;
const ny = globalNormals . get ( n * 3 + 1 ) ;
const nz = globalNormals . get ( n * 3 + 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
2019-10-26 20:15:10 -04:00
if ( defined ( u ) && ( globalUvs . length > 0 ) ) {
2019-10-27 15:00:35 -04:00
if ( u * 2 >= globalUvs . length ) {
throw new RuntimeError ( ` UV index ${ u } is out of bounds ` ) ;
}
2019-04-07 17:17:59 -04:00
const ux = globalUvs . get ( u * 2 ) ;
const uy = globalUvs . get ( u * 2 + 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 ) {
2019-02-05 20:59:09 -05:00
let index = vertexCache [ v ] ;
2017-03-13 15:28:51 -04:00
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 ) {
2019-04-07 17:17:59 -04:00
const px = globalPositions . get ( index * 3 ) ;
const py = globalPositions . get ( index * 3 + 1 ) ;
const pz = globalPositions . get ( index * 3 + 2 ) ;
2017-06-15 17:57:05 -04:00
return Cartesian3 . fromElements ( px , py , pz , result ) ;
}
2018-10-11 16:33:49 -04:00
function getNormal ( index , result ) {
2019-04-07 17:17:59 -04:00
const nx = globalNormals . get ( index * 3 ) ;
const ny = globalNormals . get ( index * 3 + 1 ) ;
const nz = globalNormals . get ( index * 3 + 2 ) ;
2017-06-16 13:51:29 -04:00
return Cartesian3 . fromElements ( nx , ny , nz , result ) ;
}
2019-02-05 20:59:09 -05:00
const scratch1 = new Cartesian3 ( ) ;
const scratch2 = new Cartesian3 ( ) ;
const scratch3 = new Cartesian3 ( ) ;
const scratch4 = new Cartesian3 ( ) ;
const scratch5 = new Cartesian3 ( ) ;
const scratchCenter = new Cartesian3 ( ) ;
const scratchAxis1 = new Cartesian3 ( ) ;
const scratchAxis2 = new Cartesian3 ( ) ;
const scratchNormal = new Cartesian3 ( ) ;
const scratchPositions = [ new Cartesian3 ( ) , new Cartesian3 ( ) , new Cartesian3 ( ) , new Cartesian3 ( ) ] ;
const scratchVertexIndices = [ ] ;
const scratchPoints = [ ] ;
2018-10-11 16:33:49 -04:00
function checkWindingCorrect ( positionIndex1 , positionIndex2 , positionIndex3 , normalIndex ) {
2019-04-07 17:17:59 -04:00
if ( ! defined ( normalIndex ) ) {
2018-10-11 16:33:49 -04:00
// If no face normal, we have to assume the winding is correct.
return true ;
}
2019-02-05 20:59:09 -05:00
const normal = getNormal ( normalIndex , scratchNormal ) ;
const A = getPosition ( positionIndex1 , scratch1 ) ;
const B = getPosition ( positionIndex2 , scratch2 ) ;
const C = getPosition ( positionIndex3 , scratch3 ) ;
2017-06-16 13:51:29 -04:00
2019-02-05 20:59:09 -05:00
const BA = Cartesian3 . subtract ( B , A , scratch4 ) ;
const CA = Cartesian3 . subtract ( C , A , scratch5 ) ;
const 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 ) {
2019-04-07 17:17:59 -04:00
correctAttributeIndices ( positions , globalPositions , 3 ) ;
correctAttributeIndices ( normals , globalNormals , 3 ) ;
correctAttributeIndices ( uvs , globalUvs , 2 ) ;
correctVertices ( vertices , positions , uvs , normals ) ;
checkPrimitive ( uvs , faceNormals ) ;
2018-08-30 23:36:28 -04:00
2017-06-14 16:55:03 -04:00
if ( vertices . length === 3 ) {
2019-02-05 20:59:09 -05:00
const isWindingCorrect = checkWindingCorrect ( positions [ 0 ] , positions [ 1 ] , positions [ 2 ] , normals [ 0 ] ) ;
const index1 = addVertex ( vertices [ 0 ] , positions [ 0 ] , uvs [ 0 ] , normals [ 0 ] ) ;
const index2 = addVertex ( vertices [ 1 ] , positions [ 1 ] , uvs [ 1 ] , normals [ 1 ] ) ;
const 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
2019-02-05 20:59:09 -05:00
const points = scratchPoints ;
const vertexIndices = scratchVertexIndices ;
2018-10-11 16:33:49 -04:00
points . length = 0 ;
2017-06-27 16:31:14 -04:00
vertexIndices . length = 0 ;
2017-06-14 16:55:03 -04:00
2019-02-05 20:59:09 -05:00
for ( let i = 0 ; i < vertices . length ; ++ i ) {
const 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
}
2019-02-05 20:59:09 -05:00
const validGeometry = CoplanarPolygonGeometryLibrary . computeProjectTo2DArguments ( points , scratchCenter , scratchAxis1 , scratchAxis2 ) ;
2018-10-11 16:33:49 -04:00
if ( ! validGeometry ) {
return ;
}
2019-02-05 20:59:09 -05:00
const projectPoints = CoplanarPolygonGeometryLibrary . createProjectPointsTo2DFunction ( scratchCenter , scratchAxis1 , scratchAxis2 ) ;
const points2D = projectPoints ( points ) ;
const indices = PolygonPipeline . triangulate ( points2D ) ;
const isWindingCorrect = PolygonPipeline . computeWindingOrder2D ( points2D ) !== WindingOrder . CLOCKWISE ;
2017-06-15 11:11:27 -04:00
2019-02-05 20:59:09 -05:00
for ( let i = 0 ; i < indices . length - 2 ; i += 3 ) {
2018-10-11 16:33:49 -04:00
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 ( ) ;
2019-02-05 20:59:09 -05:00
let result ;
2017-03-13 15:28:51 -04:00
if ( ( line . length === 0 ) || ( line . charAt ( 0 ) === '#' ) ) {
// Don't process empty lines or comments
} else if ( /^o\s/i . test ( line ) ) {
2019-02-05 20:59:09 -05:00
const objectName = line . substring ( 2 ) . trim ( ) ;
2017-03-13 15:28:51 -04:00
addNode ( objectName ) ;
} else if ( /^g\s/i . test ( line ) ) {
2019-02-05 20:59:09 -05:00
const groupName = line . substring ( 2 ) . trim ( ) ;
2017-03-13 15:28:51 -04:00
addMesh ( groupName ) ;
2019-01-03 09:29:02 -05:00
} else if ( /^usemtl/i . test ( line ) ) {
2019-02-05 20:59:09 -05:00
const materialName = line . substring ( 7 ) . trim ( ) ;
2017-03-13 15:28:51 -04:00
useMaterial ( materialName ) ;
} else if ( /^mtllib/i . test ( line ) ) {
2019-02-05 20:59:09 -05:00
const mtllibLine = line . substring ( 7 ) . trim ( ) ;
2017-11-17 11:53:04 -05:00
mtlPaths = mtlPaths . concat ( getMtlPaths ( mtllibLine ) ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = vertexPattern . exec ( line ) ) !== null ) {
2017-04-20 14:41:39 -04:00
const position = scratchCartesian ;
position . x = parseFloat ( result [ 1 ] ) ;
position . y = parseFloat ( result [ 2 ] ) ;
position . z = parseFloat ( result [ 3 ] ) ;
if ( defined ( axisTransform ) ) {
Matrix4 . multiplyByPoint ( axisTransform , position , position ) ;
}
globalPositions . push ( position . x ) ;
globalPositions . push ( position . y ) ;
globalPositions . push ( position . z ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = normalPattern . exec ( line ) ) !== null ) {
2019-08-19 19:33:12 -04:00
const 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 ) ;
}
2017-04-20 14:41:39 -04:00
if ( defined ( axisTransform ) ) {
Matrix4 . multiplyByPointAsVector ( axisTransform , normal , normal ) ;
2018-03-20 22:38:37 -04:00
}
2019-04-07 17:17:59 -04:00
globalNormals . push ( normal . x ) ;
globalNormals . push ( normal . y ) ;
globalNormals . push ( normal . z ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = uvPattern . exec ( line ) ) !== null ) {
2019-04-07 17:17:59 -04:00
globalUvs . push ( parseFloat ( result [ 1 ] ) ) ;
globalUvs . 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 ( ) {
// Unload resources
2019-04-07 17:17:59 -04:00
globalPositions = undefined ;
globalNormals = undefined ;
globalUvs = undefined ;
2017-03-13 15:28:51 -04:00
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
2019-02-05 20:59:09 -05:00
const mtlPaths = [ ] ;
const splits = mtllibLine . split ( ' ' ) ;
const length = splits . length ;
let startIndex = 0 ;
for ( let 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 ;
}
2019-02-05 20:59:09 -05:00
const mtlPath = splits . slice ( startIndex , i + 1 ) . join ( ' ' ) ;
2017-11-17 11:53:04 -05:00
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
}
2019-02-05 20:59:09 -05:00
const name = path . basename ( objPath , path . extname ( objPath ) ) ;
2017-07-29 13:23:33 -04:00
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 ) ;
}
2019-01-03 09:29:02 -05:00
assignUnnamedMaterial ( nodes , materials ) ;
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 , '/' ) ;
2019-10-26 20:42:12 -04:00
return path . normalize ( path . resolve ( objDirectory , mtlPath ) ) ;
2018-08-30 10:42:10 -04:00
}
2017-07-29 13:23:33 -04:00
function loadMtls ( mtlPaths , objPath , options ) {
2019-02-05 20:59:09 -05:00
const objDirectory = path . dirname ( objPath ) ;
let 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 ) ;
2019-02-05 20:59:09 -05:00
const 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 ) {
2019-02-05 20:59:09 -05:00
const defaultMaterial = materials [ 0 ] . name ;
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const meshes = nodes [ i ] . meshes ;
const meshesLength = meshes . length ;
for ( let j = 0 ; j < meshesLength ; ++ j ) {
const primitives = meshes [ j ] . primitives ;
const primitivesLength = primitives . length ;
for ( let k = 0 ; k < primitivesLength ; ++ k ) {
const primitive = primitives [ k ] ;
2018-03-06 19:22:06 -05:00
primitive . material = defaultValue ( primitive . material , defaultMaterial ) ;
}
}
}
}
2019-01-03 09:29:02 -05:00
function assignUnnamedMaterial ( nodes , materials ) {
// If there is a material that doesn't have a name, assign that
// material to any primitives whose material is undefined.
2019-02-10 09:45:46 -05:00
const unnamedMaterial = materials . find ( function ( material ) {
2019-01-03 09:29:02 -05:00
return material . name . length === 0 ;
} ) ;
if ( ! defined ( unnamedMaterial ) ) {
return ;
}
2019-02-10 09:45:46 -05:00
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const meshes = nodes [ i ] . meshes ;
const meshesLength = meshes . length ;
for ( let j = 0 ; j < meshesLength ; ++ j ) {
const primitives = meshes [ j ] . primitives ;
const primitivesLength = primitives . length ;
for ( let k = 0 ; k < primitivesLength ; ++ k ) {
const primitive = primitives [ k ] ;
2019-01-03 09:29:02 -05:00
if ( ! defined ( primitive . material ) ) {
primitive . material = unnamedMaterial . name ;
}
}
}
}
}
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 ) {
2019-02-05 20:59:09 -05:00
const meshesLength = meshes . length ;
for ( let i = 0 ; i < meshesLength ; ++ i ) {
2017-03-13 15:28:51 -04:00
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 ) {
2019-02-05 20:59:09 -05:00
const final = [ ] ;
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const node = nodes [ i ] ;
const meshes = removeEmptyMeshes ( node . meshes ) ;
2017-03-13 15:28:51 -04:00
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
2019-02-05 20:59:09 -05:00
const meshesLength = meshes . length ;
for ( let j = 0 ; j < meshesLength ; ++ j ) {
const mesh = meshes [ j ] ;
const convertedNode = new Node ( ) ;
2017-03-13 15:28:51 -04:00
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 ) {
2019-02-05 20:59:09 -05:00
const itemsLength = items . length ;
for ( let i = 0 ; i < itemsLength ; ++ i ) {
const item = items [ i ] ;
let name = defaultValue ( item . name , defaultName ) ;
const occurrences = usedNames [ name ] ;
2017-03-13 15:28:51 -04:00
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 ) {
2019-02-05 20:59:09 -05:00
const usedNames = { } ;
2017-03-13 15:28:51 -04:00
setDefaultNames ( nodes , 'Node' , usedNames ) ;
2019-02-05 20:59:09 -05:00
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const node = nodes [ i ] ;
2017-03-13 15:28:51 -04:00
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
}
2017-04-20 14:41:39 -04:00
function getAxisTransform ( inputUpAxis , outputUpAxis ) {
if ( inputUpAxis === 'X' && outputUpAxis === 'Y' ) {
return Axis . X _UP _TO _Y _UP ;
} else if ( inputUpAxis === 'X' && outputUpAxis === 'Z' ) {
return Axis . X _UP _TO _Z _UP ;
} else if ( inputUpAxis === 'Y' && outputUpAxis === 'X' ) {
return Axis . Y _UP _TO _X _UP ;
} else if ( inputUpAxis === 'Y' && outputUpAxis === 'Z' ) {
return Axis . Y _UP _TO _Z _UP ;
} else if ( inputUpAxis === 'Z' && outputUpAxis === 'X' ) {
return Axis . Z _UP _TO _X _UP ;
} else if ( inputUpAxis === 'Z' && outputUpAxis === 'Y' ) {
return Axis . Z _UP _TO _Y _UP ;
}
}