2021-08-02 11:31:59 -04:00
"use strict" ;
const Cesium = require ( "cesium" ) ;
const path = require ( "path" ) ;
const Promise = require ( "bluebird" ) ;
2019-02-05 20:59:09 -05:00
2021-08-02 11:31:59 -04:00
const ArrayStorage = require ( "./ArrayStorage" ) ;
const loadMtl = require ( "./loadMtl" ) ;
const outsideDirectory = require ( "./outsideDirectory" ) ;
const readLines = require ( "./readLines" ) ;
2019-02-05 20:59:09 -05:00
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 ( ) {
2021-08-02 11:31:59 -04:00
this . name = undefined ;
this . meshes = [ ] ;
2015-10-16 17:32:23 -04:00
}
2017-03-13 15:28:51 -04:00
function Mesh ( ) {
2021-08-02 11:31:59 -04:00
this . name = undefined ;
this . primitives = [ ] ;
2017-03-13 15:28:51 -04:00
}
function Primitive ( ) {
2021-08-02 11:31:59 -04:00
this . material = undefined ;
this . indices = new ArrayStorage ( ComponentDatatype . UNSIGNED _INT ) ;
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)
2021-08-02 11:31:59 -04: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 ) {
2021-08-02 11:31:59 -04:00
const axisTransform = getAxisTransform (
options . inputUpAxis ,
options . outputUpAxis
) ;
// Global store of vertex attributes listed in the obj file
let globalPositions = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
let globalNormals = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
let globalUvs = new ArrayStorage ( ComponentDatatype . FLOAT ) ;
// The current node, mesh, and primitive
let node ;
let mesh ;
let primitive ;
let activeMaterial ;
// All nodes seen in the obj
const nodes = [ ] ;
// Used to build the indices. The vertex cache is unique to each primitive.
let vertexCache = { } ;
const vertexCacheLimit = 1000000 ;
let vertexCacheCount = 0 ;
let vertexCount = 0 ;
// All mtl paths seen in the obj
let mtlPaths = [ ] ;
// Buffers for face data that spans multiple lines
let lineBuffer = "" ;
// Used for parsing face data
const faceVertices = [ ] ;
const facePositions = [ ] ;
const faceUvs = [ ] ;
const faceNormals = [ ] ;
function clearVertexCache ( ) {
vertexCache = { } ;
vertexCacheCount = 0 ;
}
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 ( ) ;
primitive . material = activeMaterial ;
mesh . primitives . push ( primitive ) ;
// Clear the vertex cache for each new primitive
clearVertexCache ( ) ;
vertexCount = 0 ;
}
function reusePrimitive ( callback ) {
const primitives = mesh . primitives ;
const primitivesLength = primitives . length ;
for ( let i = 0 ; i < primitivesLength ; ++ i ) {
if ( primitives [ i ] . material === activeMaterial ) {
if ( ! defined ( callback ) || callback ( primitives [ i ] ) ) {
primitive = primitives [ i ] ;
clearVertexCache ( ) ;
vertexCount = primitive . positions . length / 3 ;
return ;
2016-06-22 16:16:29 -04:00
}
2021-08-02 11:31:59 -04:00
}
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
addPrimitive ( ) ;
}
function useMaterial ( name ) {
activeMaterial = getName ( name ) ;
reusePrimitive ( ) ;
}
function faceAndPrimitiveMatch ( uvs , normals , primitive ) {
const faceHasUvs = defined ( uvs [ 0 ] ) ;
const faceHasNormals = defined ( normals [ 0 ] ) ;
const primitiveHasUvs = primitive . uvs . length > 0 ;
const primitiveHasNormals = primitive . normals . length > 0 ;
return (
primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals
) ;
}
function checkPrimitive ( uvs , normals ) {
const firstFace = primitive . indices . length === 0 ;
if ( ! firstFace && ! faceAndPrimitiveMatch ( uvs , normals , primitive ) ) {
reusePrimitive ( function ( primitive ) {
return faceAndPrimitiveMatch ( uvs , normals , primitive ) ;
} ) ;
2018-10-17 21:42:28 -04:00
}
2021-08-02 11:31:59 -04:00
}
2018-10-17 21:42:28 -04:00
2021-08-02 11:31:59 -04:00
function getIndexFromStart ( index , attributeData , components ) {
const i = parseInt ( index ) ;
if ( i < 0 ) {
// Negative vertex indexes reference the vertices immediately above it
return attributeData . length / components + i ;
2018-10-17 21:42:28 -04:00
}
2021-08-02 11:31:59 -04:00
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
) ;
}
2018-10-17 21:42:28 -04:00
}
2021-08-02 11:31:59 -04:00
}
2018-10-17 21:42:28 -04:00
2021-08-02 11:31:59 -04:00
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 ] , "" ) ;
2019-04-07 17:17:59 -04:00
}
2021-08-02 11:31:59 -04:00
}
function createVertex ( p , u , n ) {
// Positions
if ( defined ( p ) && globalPositions . length > 0 ) {
if ( p * 3 >= globalPositions . length ) {
throw new RuntimeError ( ` Position index ${ p } is out of bounds ` ) ;
}
const px = globalPositions . get ( p * 3 ) ;
const py = globalPositions . get ( p * 3 + 1 ) ;
const pz = globalPositions . get ( p * 3 + 2 ) ;
primitive . positions . push ( px ) ;
primitive . positions . push ( py ) ;
primitive . positions . push ( pz ) ;
2019-04-07 17:17:59 -04:00
}
2021-08-02 11:31:59 -04:00
// Normals
if ( defined ( n ) && globalNormals . length > 0 ) {
if ( n * 3 >= globalNormals . length ) {
throw new RuntimeError ( ` Normal index ${ n } is out of bounds ` ) ;
}
const nx = globalNormals . get ( n * 3 ) ;
const ny = globalNormals . get ( n * 3 + 1 ) ;
const nz = globalNormals . get ( n * 3 + 2 ) ;
primitive . normals . push ( nx ) ;
primitive . normals . push ( ny ) ;
primitive . normals . push ( nz ) ;
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2021-08-02 11:31:59 -04:00
// UVs
if ( defined ( u ) && globalUvs . length > 0 ) {
if ( u * 2 >= globalUvs . length ) {
throw new RuntimeError ( ` UV index ${ u } is out of bounds ` ) ;
}
const ux = globalUvs . get ( u * 2 ) ;
const uy = globalUvs . get ( u * 2 + 1 ) ;
primitive . uvs . push ( ux ) ;
primitive . uvs . push ( uy ) ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
}
function addVertex ( v , p , u , n ) {
let 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 ) {
clearVertexCache ( ) ;
}
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
return index ;
}
function getPosition ( index , result ) {
const px = globalPositions . get ( index * 3 ) ;
const py = globalPositions . get ( index * 3 + 1 ) ;
const pz = globalPositions . get ( index * 3 + 2 ) ;
return Cartesian3 . fromElements ( px , py , pz , result ) ;
}
function getNormal ( index , result ) {
const nx = globalNormals . get ( index * 3 ) ;
const ny = globalNormals . get ( index * 3 + 1 ) ;
const nz = globalNormals . get ( index * 3 + 2 ) ;
return Cartesian3 . fromElements ( nx , ny , nz , result ) ;
}
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 = [ ] ;
function checkWindingCorrect (
positionIndex1 ,
positionIndex2 ,
positionIndex3 ,
normalIndex
) {
if ( ! defined ( normalIndex ) ) {
// If no face normal, we have to assume the winding is correct.
return true ;
2017-06-15 17:57:05 -04:00
}
2021-08-02 11:31:59 -04:00
const normal = getNormal ( normalIndex , scratchNormal ) ;
const A = getPosition ( positionIndex1 , scratch1 ) ;
const B = getPosition ( positionIndex2 , scratch2 ) ;
const C = getPosition ( positionIndex3 , scratch3 ) ;
const BA = Cartesian3 . subtract ( B , A , scratch4 ) ;
const CA = Cartesian3 . subtract ( C , A , scratch5 ) ;
const cross = Cartesian3 . cross ( BA , CA , scratch3 ) ;
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 {
primitive . indices . push ( index1 ) ;
primitive . indices . push ( index3 ) ;
primitive . indices . push ( index2 ) ;
2021-08-01 19:47:25 -04:00
}
2021-08-02 11:31:59 -04:00
}
function addFace (
vertices ,
positions ,
uvs ,
normals ,
triangleWindingOrderSanitization
) {
correctAttributeIndices ( positions , globalPositions , 3 ) ;
correctAttributeIndices ( normals , globalNormals , 3 ) ;
correctAttributeIndices ( uvs , globalUvs , 2 ) ;
correctVertices ( vertices , positions , uvs , normals ) ;
checkPrimitive ( uvs , faceNormals ) ;
if ( vertices . length === 3 ) {
const isWindingCorrect =
! triangleWindingOrderSanitization ||
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 ] ) ;
addTriangle ( index1 , index2 , index3 , isWindingCorrect ) ;
} else {
// Triangulate if the face is not a triangle
const points = scratchPoints ;
const vertexIndices = scratchVertexIndices ;
points . length = 0 ;
vertexIndices . length = 0 ;
for ( let i = 0 ; i < vertices . length ; ++ i ) {
const index = addVertex ( vertices [ i ] , positions [ i ] , uvs [ i ] , normals [ i ] ) ;
vertexIndices . push ( index ) ;
if ( i === scratchPositions . length ) {
scratchPositions . push ( new Cartesian3 ( ) ) ;
2021-08-01 19:47:25 -04:00
}
2021-08-02 11:31:59 -04:00
points . push ( getPosition ( positions [ i ] , scratchPositions [ i ] ) ) ;
}
const validGeometry =
CoplanarPolygonGeometryLibrary . computeProjectTo2DArguments (
points ,
scratchCenter ,
scratchAxis1 ,
scratchAxis2
) ;
if ( ! validGeometry ) {
return ;
}
const projectPoints =
CoplanarPolygonGeometryLibrary . createProjectPointsTo2DFunction (
scratchCenter ,
scratchAxis1 ,
scratchAxis2
) ;
const points2D = projectPoints ( points ) ;
const indices = PolygonPipeline . triangulate ( points2D ) ;
const isWindingCorrect =
PolygonPipeline . computeWindingOrder2D ( points2D ) !==
WindingOrder . CLOCKWISE ;
for ( let i = 0 ; i < indices . length - 2 ; i += 3 ) {
addTriangle (
vertexIndices [ indices [ i ] ] ,
vertexIndices [ indices [ i + 1 ] ] ,
vertexIndices [ indices [ i + 2 ] ] ,
isWindingCorrect
) ;
}
2021-08-01 19:47:25 -04:00
}
2021-08-02 11:31:59 -04:00
}
function parseLine ( line ) {
line = line . trim ( ) ;
let result ;
if ( line . length === 0 || line . charAt ( 0 ) === "#" ) {
// Don't process empty lines or comments
} else if ( /^o\s/i . test ( line ) ) {
const objectName = line . substring ( 2 ) . trim ( ) ;
addNode ( objectName ) ;
} else if ( /^g\s/i . test ( line ) ) {
const groupName = line . substring ( 2 ) . trim ( ) ;
addMesh ( groupName ) ;
} else if ( /^usemtl/i . test ( line ) ) {
const materialName = line . substring ( 7 ) . trim ( ) ;
useMaterial ( materialName ) ;
} else if ( /^mtllib/i . test ( line ) ) {
const mtllibLine = line . substring ( 7 ) . trim ( ) ;
mtlPaths = mtlPaths . concat ( getMtlPaths ( mtllibLine ) ) ;
} else if ( ( result = vertexPattern . exec ( line ) ) !== null ) {
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 ) ;
} else if ( ( result = normalPattern . exec ( line ) ) !== null ) {
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 ) ;
}
if ( defined ( axisTransform ) ) {
Matrix4 . multiplyByPointAsVector ( axisTransform , normal , normal ) ;
}
globalNormals . push ( normal . x ) ;
globalNormals . push ( normal . y ) ;
globalNormals . push ( normal . z ) ;
} else if ( ( result = uvPattern . exec ( line ) ) !== null ) {
globalUvs . push ( parseFloat ( result [ 1 ] ) ) ;
globalUvs . push ( 1.0 - parseFloat ( result [ 2 ] ) ) ; // Flip y so 0.0 is the bottom of the image
} else {
// face line or invalid line
// 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 ) === "\\" ) {
lineBuffer += line . substring ( 0 , line . length - 1 ) ;
return ;
}
lineBuffer += line ;
if ( lineBuffer . substring ( 0 , 2 ) === "f " ) {
while ( ( result = facePattern . exec ( lineBuffer ) ) !== null ) {
faceVertices . push ( result [ 0 ] ) ;
facePositions . push ( result [ 1 ] ) ;
faceUvs . push ( result [ 2 ] ) ;
faceNormals . push ( result [ 3 ] ) ;
2017-06-16 13:51:29 -04:00
}
2021-08-02 11:31:59 -04:00
if ( faceVertices . length > 2 ) {
addFace (
faceVertices ,
facePositions ,
faceUvs ,
faceNormals ,
options . triangleWindingOrderSanitization
) ;
2016-07-22 16:17:27 -04:00
}
2016-06-09 13:33:08 -04:00
2021-08-02 11:31:59 -04:00
faceVertices . length = 0 ;
facePositions . length = 0 ;
faceNormals . length = 0 ;
faceUvs . length = 0 ;
}
lineBuffer = "" ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -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
globalPositions = undefined ;
globalNormals = undefined ;
globalUvs = undefined ;
// Load materials and textures
return finishLoading (
nodes ,
mtlPaths ,
objPath ,
defined ( activeMaterial ) ,
options
) ;
} ) ;
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 ) {
2021-08-02 11:31:59 -04:00
// Handle paths with spaces. E.g. mtllib my material file.mtl
const mtlPaths = [ ] ;
const splits = mtllibLine . split ( " " ) ;
const length = splits . length ;
let startIndex = 0 ;
for ( let i = 0 ; i < length ; ++ i ) {
if ( path . extname ( splits [ i ] ) !== ".mtl" ) {
continue ;
2017-11-17 11:53:04 -05:00
}
2021-08-02 11:31:59 -04:00
const mtlPath = splits . slice ( startIndex , i + 1 ) . join ( " " ) ;
mtlPaths . push ( mtlPath ) ;
startIndex = i + 1 ;
}
return mtlPaths ;
2017-11-17 11:53:04 -05:00
}
2018-08-30 23:04:45 -04:00
function finishLoading ( nodes , mtlPaths , objPath , usesMaterials , options ) {
2021-08-02 11:31:59 -04:00
nodes = cleanNodes ( nodes ) ;
if ( nodes . length === 0 ) {
throw new RuntimeError ( objPath + " does not have any geometry data" ) ;
}
const name = path . basename ( objPath , path . extname ( objPath ) ) ;
return loadMtls ( mtlPaths , objPath , options ) . then ( function ( materials ) {
if ( materials . length > 0 && ! usesMaterials ) {
assignDefaultMaterial ( nodes , materials , usesMaterials ) ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
assignUnnamedMaterial ( nodes , materials ) ;
return {
nodes : nodes ,
materials : materials ,
name : name ,
} ;
} ) ;
2017-03-13 15:28:51 -04:00
}
2018-08-30 10:42:10 -04:00
function normalizeMtlPath ( mtlPath , objDirectory ) {
2021-08-02 11:31:59 -04:00
mtlPath = mtlPath . replace ( /\\/g , "/" ) ;
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 ) {
2021-08-02 11:31:59 -04:00
const objDirectory = path . dirname ( objPath ) ;
let materials = [ ] ;
// Remove duplicates
mtlPaths = mtlPaths . filter ( function ( value , index , self ) {
return self . indexOf ( value ) === index ;
} ) ;
return Promise . map (
mtlPaths ,
function ( mtlPath ) {
mtlPath = normalizeMtlPath ( mtlPath , objDirectory ) ;
const shallowPath = path . join ( objDirectory , path . basename ( mtlPath ) ) ;
if ( options . secure && outsideDirectory ( mtlPath , objDirectory ) ) {
// Try looking for the .mtl in the same directory as the obj
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."
) ;
return loadMtl ( shallowPath , options )
. then ( function ( materialsInMtl ) {
materials = materials . concat ( materialsInMtl ) ;
} )
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger (
"Could not read material file at " +
shallowPath +
". Using default material instead."
) ;
} ) ;
}
return loadMtl ( mtlPath , options )
. catch ( function ( error ) {
// Try looking for the .mtl in the same directory as the obj
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."
) ;
return loadMtl ( shallowPath , options ) ;
} )
. then ( function ( materialsInMtl ) {
materials = materials . concat ( materialsInMtl ) ;
} )
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger (
"Could not read material file at " +
shallowPath +
". Using default material instead."
) ;
2017-05-03 17:59:24 -04:00
} ) ;
2021-08-02 11:31:59 -04:00
} ,
{ concurrency : 10 }
) . then ( function ( ) {
return materials ;
} ) ;
2017-03-13 15:28:51 -04:00
}
2018-03-06 19:22:06 -05:00
function assignDefaultMaterial ( nodes , materials ) {
2021-08-02 11:31:59 -04: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 ] ;
primitive . material = defaultValue ( primitive . material , defaultMaterial ) ;
}
2018-03-06 19:22:06 -05:00
}
2021-08-02 11:31:59 -04:00
}
2018-03-06 19:22:06 -05:00
}
2019-01-03 09:29:02 -05:00
function assignUnnamedMaterial ( nodes , materials ) {
2021-08-02 11:31:59 -04:00
// If there is a material that doesn't have a name, assign that
// material to any primitives whose material is undefined.
const unnamedMaterial = materials . find ( function ( material ) {
return material . name . length === 0 ;
} ) ;
if ( ! defined ( unnamedMaterial ) ) {
return ;
}
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 ] ;
if ( ! defined ( primitive . material ) ) {
primitive . material = unnamedMaterial . name ;
2019-01-03 09:29:02 -05:00
}
2021-08-02 11:31:59 -04:00
}
2019-01-03 09:29:02 -05:00
}
2021-08-02 11:31:59 -04:00
}
2019-01-03 09:29:02 -05:00
}
2017-03-13 15:28:51 -04:00
function removeEmptyMeshes ( meshes ) {
2021-08-02 11:31:59 -04:00
return meshes . filter ( function ( mesh ) {
// Remove empty primitives
mesh . primitives = mesh . primitives . filter ( function ( primitive ) {
return primitive . indices . length > 0 && primitive . positions . length > 0 ;
2017-04-10 17:57:56 -04:00
} ) ;
2021-08-02 11:31:59 -04:00
// Valid meshes must have at least one primitive
return mesh . primitives . 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 ) {
2021-08-02 11:31:59 -04:00
const meshesLength = meshes . length ;
for ( let i = 0 ; i < meshesLength ; ++ i ) {
if ( defined ( meshes [ i ] . name ) ) {
return true ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
}
return false ;
2016-06-22 10:07:08 -04:00
}
2017-03-13 15:28:51 -04:00
function removeEmptyNodes ( nodes ) {
2021-08-02 11:31:59 -04:00
const final = [ ] ;
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const node = nodes [ i ] ;
const meshes = removeEmptyMeshes ( node . meshes ) ;
if ( meshes . length === 0 ) {
continue ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
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
const meshesLength = meshes . length ;
for ( let j = 0 ; j < meshesLength ; ++ j ) {
const mesh = meshes [ j ] ;
const convertedNode = new Node ( ) ;
convertedNode . name = mesh . name ;
convertedNode . meshes = [ mesh ] ;
final . push ( convertedNode ) ;
}
} else {
final . push ( node ) ;
}
}
return final ;
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 setDefaultNames ( items , defaultName , usedNames ) {
2021-08-02 11:31:59 -04: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 ] ;
if ( defined ( occurrences ) ) {
usedNames [ name ] ++ ;
name = name + "_" + occurrences ;
} else {
usedNames [ name ] = 1 ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
item . name = name ;
}
2017-03-13 15:28:51 -04:00
}
2016-07-22 14:09:13 -04:00
2017-03-13 15:28:51 -04:00
function setDefaults ( nodes ) {
2021-08-02 11:31:59 -04:00
const usedNames = { } ;
setDefaultNames ( nodes , "Node" , usedNames ) ;
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
const node = nodes [ i ] ;
setDefaultNames ( node . meshes , node . name + "-Mesh" , usedNames ) ;
}
2017-03-13 15:28:51 -04:00
}
function cleanNodes ( nodes ) {
2021-08-02 11:31:59 -04:00
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 ) {
2021-08-02 11:31:59 -04:00
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 ;
}
2017-04-20 14:41:39 -04:00
}