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 loadImage = require ( './loadImage' ) ;
var loadMtl = require ( './loadMtl' ) ;
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 Axis = Cesium . Axis ;
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-04-20 14:41:39 -04:00
var Matrix4 = Cesium . Matrix4 ;
2017-03-13 15:28:51 -04:00
var RuntimeError = Cesium . RuntimeError ;
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)
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
var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/ ; // f vertex vertex vertex ...
var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/ ; // f vertex/uv vertex/uv vertex/uv ...
var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/ ; // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ ; // f vertex//normal vertex//normal vertex//normal ...
2017-04-20 14:41:39 -04:00
var scratchCartesian = new Cartesian3 ( ) ;
2017-03-13 15:28:51 -04:00
/ * *
* Parse an obj file .
*
* @ param { String } objPath Path to the obj file .
2017-04-10 17:57:56 -04:00
* @ param { Object } options An object with the following properties :
* @ param { Boolean } options . checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel .
* @ param { Boolean } options . secure Prevent the converter from reading image or mtl files outside of the input obj directory .
2017-04-20 14:41:39 -04:00
* @ param { String } options . inputUpAxis Up axis of the obj .
* @ param { String } options . outputUpAxis Up axis of the converted glTF .
2017-04-10 17:57:56 -04:00
* @ param { Boolean } options . logger A callback function for handling logged messages . Defaults to console . log .
2017-03-13 15:28:51 -04:00
* @ returns { Promise } A promise resolving to the obj data .
* @ exception { RuntimeError } The file does not have any geometry information in it .
*
* @ private
* /
2017-03-17 16:05:51 -04:00
function loadObj ( objPath , options ) {
2017-04-20 14:41:39 -04:00
var axisTransform = getAxisTransform ( options . inputUpAxis , options . outputUpAxis ) ;
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 ;
// 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 = [ ] ;
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 ( ) ;
mesh . primitives . push ( primitive ) ;
}
function useMaterial ( name ) {
// Look to see if this material has already been used by a primitive in the mesh
var material = getName ( name ) ;
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 ( ) ;
primitive . material = getName ( name ) ;
}
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
if ( defined ( p ) ) {
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
if ( defined ( n ) ) {
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
if ( defined ( u ) ) {
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-03-13 15:28:51 -04:00
function addFace ( v1 , p1 , u1 , n1 , v2 , p2 , u2 , n2 , v3 , p3 , u3 , n3 , v4 , p4 , u4 , n4 ) {
var index1 = addVertex ( v1 , p1 , u1 , n1 ) ;
var index2 = addVertex ( v2 , p2 , u2 , n2 ) ;
var index3 = addVertex ( v3 , p3 , u3 , n3 ) ;
primitive . indices . push ( index1 ) ;
primitive . indices . push ( index2 ) ;
primitive . indices . push ( index3 ) ;
// Triangulate if the face is a quad
if ( defined ( v4 ) ) {
var index4 = addVertex ( v4 , p4 , u4 , n4 ) ;
primitive . indices . push ( index1 ) ;
primitive . indices . push ( index3 ) ;
primitive . indices . push ( index4 ) ;
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 ) ) {
var paths = line . substring ( 7 ) . trim ( ) . split ( ' ' ) ;
mtlPaths = mtlPaths . concat ( paths ) ;
} else if ( ( result = vertexPattern . exec ( line ) ) !== null ) {
2017-04-20 14:41:39 -04:00
var 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 ) ;
}
positions . push ( position . x ) ;
positions . push ( position . y ) ;
positions . push ( position . z ) ;
2017-03-13 15:28:51 -04:00
} else if ( ( result = normalPattern . exec ( line ) ) !== null ) {
2017-04-20 14:41:39 -04:00
var normal = scratchCartesian ;
normal . x = parseFloat ( result [ 1 ] ) ;
normal . y = parseFloat ( result [ 2 ] ) ;
normal . z = parseFloat ( result [ 3 ] ) ;
if ( defined ( axisTransform ) ) {
Matrix4 . multiplyByPointAsVector ( axisTransform , 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
} else if ( ( result = facePattern1 . exec ( line ) ) !== null ) {
addFace (
result [ 1 ] , result [ 1 ] , undefined , undefined ,
result [ 2 ] , result [ 2 ] , undefined , undefined ,
result [ 3 ] , result [ 3 ] , undefined , undefined ,
result [ 4 ] , result [ 4 ] , undefined , undefined
) ;
} else if ( ( result = facePattern2 . exec ( line ) ) !== null ) {
addFace (
result [ 1 ] , result [ 2 ] , result [ 3 ] , undefined ,
result [ 4 ] , result [ 5 ] , result [ 6 ] , undefined ,
result [ 7 ] , result [ 8 ] , result [ 9 ] , undefined ,
result [ 10 ] , result [ 11 ] , result [ 12 ] , undefined
) ;
} else if ( ( result = facePattern3 . exec ( line ) ) !== null ) {
addFace (
result [ 1 ] , result [ 2 ] , result [ 3 ] , result [ 4 ] ,
result [ 5 ] , result [ 6 ] , result [ 7 ] , result [ 8 ] ,
result [ 9 ] , result [ 10 ] , result [ 11 ] , result [ 12 ] ,
result [ 13 ] , result [ 14 ] , result [ 15 ] , result [ 16 ]
) ;
} else if ( ( result = facePattern4 . exec ( line ) ) !== null ) {
addFace (
result [ 1 ] , result [ 2 ] , undefined , result [ 3 ] ,
result [ 4 ] , result [ 5 ] , undefined , result [ 6 ] ,
result [ 7 ] , result [ 8 ] , undefined , result [ 9 ] ,
result [ 10 ] , result [ 11 ] , undefined , result [ 12 ]
) ;
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
positions = undefined ;
normals = undefined ;
uvs = undefined ;
// Load materials and images
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-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-04-10 17:57:56 -04:00
return Promise . reject ( new RuntimeError ( objPath + ' does not have any geometry data' ) ) ;
2017-03-13 15:28:51 -04:00
}
2017-04-04 16:45:21 -04:00
return loadMaterials ( mtlPaths , objPath , options )
2017-03-13 15:28:51 -04:00
. then ( function ( materials ) {
var imagePaths = getImagePaths ( materials ) ;
2017-04-04 16:45:21 -04:00
return loadImages ( imagePaths , objPath , options )
2017-03-13 15:28:51 -04:00
. then ( function ( images ) {
return {
nodes : nodes ,
materials : materials ,
images : images
} ;
} ) ;
2016-06-22 10:07:08 -04:00
} ) ;
2017-03-13 15:28:51 -04:00
}
2017-04-04 16:45:21 -04:00
function outsideDirectory ( filePath , objPath ) {
return ( path . relative ( path . dirname ( objPath ) , filePath ) . indexOf ( '..' ) === 0 ) ;
}
function loadMaterials ( mtlPaths , objPath , options ) {
2017-04-10 17:57:56 -04:00
var secure = options . secure ;
var logger = options . logger ;
var objDirectory = path . dirname ( objPath ) ;
2017-03-13 15:28:51 -04:00
var materials = { } ;
return Promise . map ( mtlPaths , function ( mtlPath ) {
2017-04-10 17:57:56 -04:00
mtlPath = path . resolve ( objDirectory , mtlPath ) ;
if ( secure && outsideDirectory ( mtlPath , objPath ) ) {
logger ( 'Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.' ) ;
2017-04-04 16:45:21 -04:00
return ;
}
2017-03-13 15:28:51 -04:00
return loadMtl ( mtlPath )
. then ( function ( materialsInMtl ) {
2017-04-10 17:57:56 -04:00
materials = Object . assign ( materials , materialsInMtl ) ;
2017-04-04 16:45:21 -04:00
} )
. catch ( function ( ) {
2017-04-10 17:57:56 -04:00
logger ( 'Could not read mtl file at ' + mtlPath + '. Using default material instead.' ) ;
2017-03-13 15:28:51 -04:00
} ) ;
2017-04-10 17:57:56 -04:00
} , { concurrency : 10 } )
. thenReturn ( materials ) ;
2017-03-13 15:28:51 -04:00
}
2017-04-04 16:45:21 -04:00
function loadImages ( imagePaths , objPath , options ) {
2017-04-10 17:57:56 -04:00
var secure = options . secure ;
var logger = options . logger ;
2017-03-13 15:28:51 -04:00
var images = { } ;
return Promise . map ( imagePaths , function ( imagePath ) {
2017-04-10 17:57:56 -04:00
if ( secure && outsideDirectory ( imagePath , objPath ) ) {
logger ( 'Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.' ) ;
2017-04-04 16:45:21 -04:00
return ;
}
2017-03-17 16:05:51 -04:00
return loadImage ( imagePath , options )
2017-03-13 15:28:51 -04:00
. then ( function ( image ) {
2017-04-10 17:57:56 -04:00
images [ imagePath ] = image ;
2017-04-04 16:45:21 -04:00
} )
. catch ( function ( ) {
2017-04-10 17:57:56 -04:00
logger ( 'Could not read image file at ' + imagePath + '. Material will ignore this image.' ) ;
2017-03-13 15:28:51 -04:00
} ) ;
2017-04-10 17:57:56 -04:00
} , { concurrency : 10 } )
. thenReturn ( images ) ;
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function getImagePaths ( materials ) {
2017-03-17 11:40:54 -04:00
var imagePaths = { } ;
2016-06-09 13:33:08 -04:00
for ( var name in materials ) {
if ( materials . hasOwnProperty ( name ) ) {
var material = materials [ name ] ;
2017-04-10 17:57:56 -04:00
if ( defined ( material . ambientTexture ) ) {
imagePaths [ material . ambientTexture ] = true ;
2015-10-16 17:32:23 -04:00
}
2017-04-10 17:57:56 -04:00
if ( defined ( material . diffuseTexture ) ) {
imagePaths [ material . diffuseTexture ] = true ;
2015-10-19 17:03:50 -04:00
}
2017-04-10 17:57:56 -04:00
if ( defined ( material . emissionTexture ) ) {
imagePaths [ material . emissionTexture ] = true ;
2016-06-09 13:33:08 -04:00
}
2017-04-10 17:57:56 -04:00
if ( defined ( material . specularTexture ) ) {
imagePaths [ material . specularTexture ] = true ;
2016-06-09 13:33:08 -04:00
}
}
}
2017-03-17 11:40:54 -04:00
return Object . keys ( imagePaths ) ;
2017-03-13 15:28:51 -04:00
}
2015-10-19 17:03:50 -04:00
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
}
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 ;
}
}