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-06-14 16:55:03 -04:00
var Cartesian2 = Cesium . Cartesian2 ;
2017-04-20 14:41:39 -04:00
var Cartesian3 = Cesium . Cartesian3 ;
2017-03-13 15:28:51 -04:00
var ComponentDatatype = Cesium . ComponentDatatype ;
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 IntersectionTests = Cesium . IntersectionTests ;
var Matrix3 = Cesium . Matrix3 ;
var OrientedBoundingBox = Cesium . OrientedBoundingBox ;
var Plane = Cesium . Plane ;
var PolygonPipeline = Cesium . PolygonPipeline ;
var Ray = Cesium . Ray ;
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 = [ ] ;
this . positions = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
this . normals = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
this . uvs = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
}
function Primitive ( ) {
this . material = undefined ;
this . indices = new ArrayStorage ( ComponentDatatype . UNSIGNED _INT ) ;
}
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 = [ ] ;
// Used to build the indices. The vertex cache is unique to each mesh.
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
var vertexIndices = [ ] ;
2017-06-15 17:57:05 -04:00
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 ( ) ;
// Clear the vertex cache for each new mesh
vertexCache = { } ;
vertexCacheCount = 0 ;
vertexCount = 0 ;
}
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 ) ;
}
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 ] ;
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 ) ;
mesh . positions . push ( px ) ;
mesh . positions . push ( py ) ;
mesh . 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 ) ;
mesh . normals . push ( nx ) ;
mesh . normals . push ( ny ) ;
mesh . normals . push ( nz ) ;
}
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 ) ;
mesh . uvs . push ( ux ) ;
mesh . 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 ) {
vertexCacheCount = 0 ;
vertexCache = { } ;
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
2017-07-27 13:45:37 -04:00
// Given a set of 3D points, project them onto whichever axis will produce the least distortion.
var scratchIntersectionPoint = new Cartesian3 ( ) ;
var scratchXAxis = new Cartesian3 ( ) ;
var scratchYAxis = new Cartesian3 ( ) ;
var scratchZAxis = new Cartesian3 ( ) ;
var scratchOrigin = new Cartesian3 ( ) ;
var scratchNormal = new Cartesian3 ( ) ;
var scratchRay = new Ray ( ) ;
var scratchPlane = new Plane ( Cesium . Cartesian3 . UNIT _X , 0 ) ;
var scratchPositions2D = [ new Cartesian2 ( ) , new Cartesian2 ( ) , new Cartesian2 ( ) ] ;
function projectTo2D ( positions ) {
var positions2D = [ ] ;
var obb = OrientedBoundingBox . fromPoints ( positions ) ;
var halfAxes = obb . halfAxes ;
Matrix3 . getColumn ( halfAxes , 0 , scratchXAxis ) ;
Matrix3 . getColumn ( halfAxes , 1 , scratchYAxis ) ;
Matrix3 . getColumn ( halfAxes , 2 , scratchZAxis ) ;
var xMag = Cartesian3 . magnitude ( scratchXAxis ) ;
var yMag = Cartesian3 . magnitude ( scratchYAxis ) ;
var zMag = Cartesian3 . magnitude ( scratchZAxis ) ;
var min = Math . min ( xMag , yMag , zMag ) ;
var i ;
// If all the points are on a line, just remove one of the zero dimensions
if ( xMag === 0 && ( yMag === 0 || zMag === 0 ) ) {
for ( i = 0 ; i < positions . length ; i ++ ) {
if ( i === scratchPositions2D . length ) {
scratchPositions2D . push ( new Cartesian2 ( ) ) ;
}
positions2D [ i ] = new Cartesian2 . fromElements ( positions [ i ] . y , positions [ i ] . z , scratchPositions2D [ i ] ) ;
}
return positions2D ;
} else if ( yMag === 0 && zMag === 0 ) {
for ( i = 0 ; i < positions . length ; i ++ ) {
if ( i === scratchPositions2D . length ) {
scratchPositions2D . push ( new Cartesian2 ( ) ) ;
}
positions2D [ i ] = new Cartesian2 . fromElements ( positions [ i ] . x , positions [ i ] . y , scratchPositions2D [ i ] ) ;
}
return positions2D ;
}
var center = obb . center ;
var planeXAxis ;
var planeYAxis ;
if ( min === xMag ) {
if ( ! scratchXAxis . equals ( Cartesian3 . ZERO ) ) {
Cartesian3 . add ( center , scratchXAxis , scratchOrigin ) ;
Cartesian3 . normalize ( scratchXAxis , scratchNormal ) ;
}
planeXAxis = Cartesian3 . normalize ( scratchYAxis , scratchYAxis ) ;
planeYAxis = Cartesian3 . normalize ( scratchZAxis , scratchZAxis ) ;
} else if ( min === yMag ) {
if ( ! scratchYAxis . equals ( Cartesian3 . ZERO ) ) {
Cartesian3 . add ( center , scratchYAxis , scratchOrigin ) ;
Cartesian3 . normalize ( scratchYAxis , scratchNormal ) ;
}
planeXAxis = Cartesian3 . normalize ( scratchXAxis , scratchXAxis ) ;
planeYAxis = Cartesian3 . normalize ( scratchZAxis , scratchZAxis ) ;
} else {
if ( ! scratchZAxis . equals ( Cartesian3 . ZERO ) ) {
Cartesian3 . add ( center , scratchZAxis , scratchOrigin ) ;
Cartesian3 . normalize ( scratchZAxis , scratchNormal ) ;
}
planeXAxis = Cartesian3 . normalize ( scratchXAxis , scratchXAxis ) ;
planeYAxis = Cartesian3 . normalize ( scratchYAxis , scratchYAxis ) ;
}
if ( min === 0 ) {
scratchNormal = Cartesian3 . cross ( planeXAxis , planeYAxis , scratchNormal ) ;
scratchNormal = Cartesian3 . normalize ( scratchNormal , scratchNormal ) ;
}
Plane . fromPointNormal ( scratchOrigin , scratchNormal , scratchPlane ) ;
scratchRay . direction = scratchNormal ;
for ( i = 0 ; i < positions . length ; i ++ ) {
scratchRay . origin = positions [ i ] ;
2017-03-13 15:28:51 -04:00
2017-07-27 13:45:37 -04:00
var intersectionPoint = IntersectionTests . rayPlane ( scratchRay , scratchPlane , scratchIntersectionPoint ) ;
if ( ! defined ( intersectionPoint ) ) {
Cartesian3 . negate ( scratchRay . direction , scratchRay . direction ) ;
intersectionPoint = IntersectionTests . rayPlane ( scratchRay , scratchPlane , scratchIntersectionPoint ) ;
}
var v = Cartesian3 . subtract ( intersectionPoint , scratchOrigin , intersectionPoint ) ;
var x = Cartesian3 . dot ( planeXAxis , v ) ;
var y = Cartesian3 . dot ( planeYAxis , v ) ;
2017-03-13 15:28:51 -04:00
2017-07-27 13:45:37 -04:00
if ( i === scratchPositions2D . length ) {
scratchPositions2D . push ( new Cartesian2 ( ) ) ;
}
positions2D [ i ] = new Cartesian2 . fromElements ( x , y , scratchPositions2D [ i ] ) ;
}
return positions2D ;
}
2017-06-15 17:57:05 -04:00
function get3DPoint ( index , result ) {
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 ) ;
}
2017-06-16 13:51:29 -04:00
function get3DNormal ( index , result ) {
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
// Given a sequence of three points A B C, determine whether vector BC
// "turns" clockwise (positive) or counter-clockwise (negative) from vector AB
var scratch1 = new Cartesian3 ( ) ;
var scratch2 = new Cartesian3 ( ) ;
function getTurnDirection ( pointA , pointB , pointC ) {
var vector1 = Cartesian2 . subtract ( pointA , pointB , scratch1 ) ;
var vector2 = Cartesian2 . subtract ( pointC , pointB , scratch2 ) ;
return vector1 . x * vector2 . y - vector1 . y * vector2 . x ;
}
// Given the cartesian 2 vertices of a polygon, determine if convex
function isConvex ( positions2D ) {
2017-07-24 16:42:03 -04:00
var turnDirection = getTurnDirection ( positions2D [ 0 ] , positions2D [ 1 ] , positions2D [ 2 ] ) ;
for ( var i = 1 ; i < positions2D . length - 2 ; ++ i ) {
2017-12-12 09:34:35 -05:00
var currentTurnDirection = getTurnDirection ( positions2D [ i ] , positions2D [ i + 1 ] , positions2D [ i + 2 ] ) ;
2017-06-15 11:11:27 -04:00
if ( turnDirection * currentTurnDirection < 0 ) {
return false ;
}
2017-07-24 16:42:03 -04:00
}
return true ;
2017-06-15 11:11:27 -04:00
}
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 ( ) ;
2017-06-16 13:51:29 -04:00
// Checks if winding order matches the given normal.
2017-06-26 12:47:08 -04:00
function checkWindingCorrect ( positionIndex1 , positionIndex2 , positionIndex3 , normal ) {
2017-06-16 13:51:29 -04:00
var A = get3DPoint ( positionIndex1 , scratch1 ) ;
var B = get3DPoint ( positionIndex2 , scratch2 ) ;
var C = get3DPoint ( positionIndex3 , scratch3 ) ;
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-07-27 13:45:37 -04:00
var scratchPositions3D = [ new Cartesian3 ( ) , new Cartesian3 ( ) , new Cartesian3 ( ) ] ;
2017-06-13 20:38:38 -04:00
function addFace ( vertices , positions , uvs , normals ) {
2017-06-26 12:47:08 -04:00
var isWindingCorrect = true ;
2017-06-16 13:51:29 -04:00
var faceNormal ;
// If normals are defined, find a face normal to use in winding order sanitization.
// If no face normal, we have to assume the winding is correct.
2017-06-27 16:31:14 -04:00
if ( normals [ 0 ] . length > 0 ) {
2017-06-16 13:51:29 -04:00
faceNormal = get3DNormal ( normals [ 0 ] , scratchNormal ) ;
2017-06-26 12:47:08 -04:00
isWindingCorrect = checkWindingCorrect ( positions [ 0 ] , positions [ 1 ] , positions [ 2 ] , faceNormal ) ;
2017-06-16 13:51:29 -04:00
}
2017-06-13 20:38:38 -04:00
2017-06-14 16:55:03 -04:00
if ( vertices . length === 3 ) {
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
2017-07-27 13:45:37 -04:00
var positions3D = [ ] ;
2017-06-27 16:31:14 -04:00
vertexIndices . length = 0 ;
2017-06-14 16:55:03 -04:00
var i ;
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 ) ;
2017-06-15 11:11:27 -04:00
// Collect the vertex positions as 3D points
2017-07-27 13:45:37 -04:00
if ( i === scratchPositions3D . length ) {
scratchPositions3D . push ( new Cartesian3 ( ) ) ;
}
positions3D . push ( get3DPoint ( positions [ i ] , scratchPositions3D [ i ] ) ) ;
2017-06-14 16:55:03 -04:00
}
2017-06-15 11:11:27 -04:00
var positions2D = projectTo2D ( positions3D ) ;
2017-06-14 16:55:03 -04:00
2017-06-15 11:11:27 -04:00
if ( isConvex ( positions2D ) ) {
for ( i = 1 ; i < vertices . length - 1 ; ++ i ) {
2017-06-26 12:47:08 -04:00
addTriangle ( vertexIndices [ 0 ] , vertexIndices [ i ] , vertexIndices [ i + 1 ] , isWindingCorrect ) ;
2017-06-15 11:11:27 -04:00
}
} else {
2017-06-16 13:51:29 -04:00
// Since the projection doesn't preserve winding order, reverse the order of
2017-06-15 11:11:27 -04:00
// the vertices before triangulating to enforce counter clockwise.
2017-06-16 13:51:29 -04:00
var projectedWindingOrder = PolygonPipeline . computeWindingOrder2D ( positions2D ) ;
if ( projectedWindingOrder === WindingOrder . CLOCKWISE ) {
2017-06-15 11:11:27 -04:00
positions2D . reverse ( ) ;
}
// Use an ear-clipping algorithm to triangulate
var positionIndices = PolygonPipeline . triangulate ( positions2D ) ;
2017-06-16 13:51:29 -04:00
for ( i = 0 ; i < positionIndices . length - 2 ; i += 3 ) {
2017-06-26 12:47:08 -04:00
addTriangle ( vertexIndices [ positionIndices [ i ] ] , vertexIndices [ positionIndices [ i + 1 ] ] , vertexIndices [ positionIndices [ i + 2 ] ] , isWindingCorrect ) ;
2017-06-15 11:11:27 -04:00
}
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
2017-03-17 16:05:51 -04:00
return finishLoading ( nodes , mtlPaths , objPath , 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 ;
}
2017-03-17 16:05:51 -04:00
function finishLoading ( nodes , mtlPaths , objPath , 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-03-06 19:22:06 -05:00
assignDefaultMaterial ( 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
}
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 ) {
2017-04-10 17:57:56 -04:00
mtlPath = path . resolve ( objDirectory , mtlPath ) ;
2017-11-29 14:21:59 -05:00
var shallowPath = path . resolve ( 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 ) {
if ( materials . length === 0 ) {
return ;
}
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 ) {
return primitive . indices . length > 0 ;
} ) ;
// Valid meshes must have at least one primitive and contain positions
return ( mesh . primitives . length > 0 ) && ( mesh . positions . length > 0 ) ;
} ) ;
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
}