2021-08-02 11:31:59 -04:00
"use strict" ;
const Cesium = require ( "cesium" ) ;
const path = require ( "path" ) ;
2019-02-05 20:59:09 -05:00
2021-08-02 11:31:59 -04:00
const loadObj = require ( "../../lib/loadObj" ) ;
const obj2gltf = require ( "../../lib/obj2gltf" ) ;
2019-02-05 20:59:09 -05:00
const Cartesian3 = Cesium . Cartesian3 ;
const CesiumMath = Cesium . Math ;
const clone = Cesium . clone ;
const RuntimeError = Cesium . RuntimeError ;
2021-08-02 11:31:59 -04:00
const objPath = "specs/data/box/box.obj" ;
const objRotatedUrl = "specs/data/box-rotated/box-rotated.obj" ;
const objNormalsPath = "specs/data/box-normals/box-normals.obj" ;
const objUvsPath = "specs/data/box-uvs/box-uvs.obj" ;
const objPositionsOnlyPath =
"specs/data/box-positions-only/box-positions-only.obj" ;
const objNegativeIndicesPath =
"specs/data/box-negative-indices/box-negative-indices.obj" ;
const objTrianglesPath = "specs/data/box-triangles/box-triangles.obj" ;
const objObjectsPath = "specs/data/box-objects/box-objects.obj" ;
const objGroupsPath = "specs/data/box-groups/box-groups.obj" ;
const objObjectsGroupsPath =
"specs/data/box-objects-groups/box-objects-groups.obj" ;
const objObjectsGroupsMaterialsPath =
"specs/data/box-objects-groups-materials/box-objects-groups-materials.obj" ;
const objObjectsGroupsMaterialsPath2 =
"specs/data/box-objects-groups-materials-2/box-objects-groups-materials-2.obj" ;
const objUsemtlPath = "specs/data/box-usemtl/box-usemtl.obj" ;
const objNoMaterialsPath = "specs/data/box-no-materials/box-no-materials.obj" ;
const objMultipleMaterialsPath =
"specs/data/box-multiple-materials/box-multiple-materials.obj" ;
const objUncleanedPath = "specs/data/box-uncleaned/box-uncleaned.obj" ;
const objMtllibPath = "specs/data/box-mtllib/box-mtllib.obj" ;
const objMtllibSpacesPath = "specs/data/box-mtllib-spaces/box mtllib.obj" ;
const objMissingMtllibPath =
"specs/data/box-missing-mtllib/box-missing-mtllib.obj" ;
const objMissingUsemtlPath =
"specs/data/box-missing-usemtl/box-missing-usemtl.obj" ;
const objUnnamedMaterialPath =
"specs/data/box-unnamed-material/box-unnamed-material.obj" ;
const objExternalResourcesPath =
"specs/data/box-external-resources/box-external-resources.obj" ;
const objResourcesInRootPath =
"specs/data/box-resources-in-root/box-resources-in-root.obj" ;
const objExternalResourcesInRootPath =
"specs/data/box-external-resources-in-root/box-external-resources-in-root.obj" ;
const objTexturedPath = "specs/data/box-textured/box-textured.obj" ;
const objMissingTexturePath =
"specs/data/box-missing-texture/box-missing-texture.obj" ;
const objSubdirectoriesPath = "specs/data/box-subdirectories/box-textured.obj" ;
const objWindowsPaths = "specs/data/box-windows-paths/box-windows-paths.obj" ;
const objInvalidContentsPath = "specs/data/box/box.mtl" ;
const objConcavePath = "specs/data/concave/concave.obj" ;
const objUnnormalizedPath = "specs/data/box-unnormalized/box-unnormalized.obj" ;
const objMixedAttributesPath =
"specs/data/box-mixed-attributes/box-mixed-attributes.obj" ;
const objMissingAttributesPath =
"specs/data/box-missing-attributes/box-missing-attributes.obj" ;
const objIncompletePositionsPath =
"specs/data/box-incomplete-attributes/box-incomplete-positions.obj" ;
const objIncompleteNormalsPath =
"specs/data/box-incomplete-attributes/box-incomplete-normals.obj" ;
const objIncompleteUvsPath =
"specs/data/box-incomplete-attributes/box-incomplete-uvs.obj" ;
const objIncorrectWindingOrderPath =
"specs/data/box-incorrect-winding-order/box-incorrect-winding-order.obj" ;
2021-08-17 20:08:14 +01:00
const objWithTabs = "specs/data/box-with-tabs/box-with-tabs.obj" ;
2021-08-02 11:31:59 -04:00
const objInvalidPath = "invalid.obj" ;
2017-03-13 15:28:51 -04:00
function getMeshes ( data ) {
2021-08-02 11:31:59 -04:00
let meshes = [ ] ;
const nodes = data . nodes ;
const nodesLength = nodes . length ;
for ( let i = 0 ; i < nodesLength ; ++ i ) {
meshes = meshes . concat ( nodes [ i ] . meshes ) ;
}
return meshes ;
2017-03-13 15:28:51 -04:00
}
function getPrimitives ( data ) {
2021-08-02 11:31:59 -04:00
let primitives = [ ] ;
const nodes = data . nodes ;
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 ) {
primitives = primitives . concat ( meshes [ j ] . primitives ) ;
2017-03-13 15:28:51 -04:00
}
2021-08-02 11:31:59 -04:00
}
return primitives ;
2017-03-13 15:28:51 -04:00
}
2019-02-05 20:59:09 -05:00
let options ;
2017-04-10 17:57:56 -04:00
2021-08-02 11:31:59 -04:00
describe ( "loadObj" , ( ) => {
beforeEach ( ( ) => {
options = clone ( obj2gltf . defaults ) ;
options . overridingTextures = { } ;
options . logger = ( ) => { } ;
} ) ;
2019-02-05 20:59:09 -05:00
2021-08-02 11:31:59 -04:00
it ( "loads obj with positions, normals, and uvs" , async ( ) => {
const data = await loadObj ( objPath , options ) ;
const materials = data . materials ;
const nodes = data . nodes ;
const name = data . name ;
const meshes = getMeshes ( data ) ;
const primitives = getPrimitives ( data ) ;
expect ( name ) . toBe ( "box" ) ;
expect ( materials . length ) . toBe ( 1 ) ;
expect ( nodes . length ) . toBe ( 1 ) ;
expect ( meshes . length ) . toBe ( 1 ) ;
expect ( primitives . length ) . toBe ( 1 ) ;
const node = nodes [ 0 ] ;
const mesh = meshes [ 0 ] ;
const primitive = primitives [ 0 ] ;
expect ( node . name ) . toBe ( "Cube" ) ;
expect ( mesh . name ) . toBe ( "Cube-Mesh" ) ;
expect ( primitive . positions . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . normals . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . uvs . length / 2 ) . toBe ( 24 ) ;
expect ( primitive . indices . length ) . toBe ( 36 ) ;
expect ( primitive . material ) . toBe ( "Material" ) ;
} ) ;
it ( "loads obj with normals" , async ( ) => {
const data = await loadObj ( objNormalsPath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . normals . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . uvs . length / 2 ) . toBe ( 0 ) ;
} ) ;
it ( "normalizes normals" , async ( ) => {
const data = await loadObj ( objUnnormalizedPath , options ) ;
const scratchNormal = new Cesium . Cartesian3 ( ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
const normals = primitive . normals ;
const normalsLength = normals . length / 3 ;
for ( let i = 0 ; i < normalsLength ; ++ i ) {
const normalX = normals . get ( i * 3 ) ;
const normalY = normals . get ( i * 3 + 1 ) ;
const normalZ = normals . get ( i * 3 + 2 ) ;
const normal = Cartesian3 . fromElements (
normalX ,
normalY ,
normalZ ,
scratchNormal
) ;
expect (
CesiumMath . equalsEpsilon (
Cartesian3 . magnitude ( normal ) ,
1.0 ,
CesiumMath . EPSILON5
)
) . toBe ( true ) ;
2017-12-29 11:55:21 -05:00
}
2021-08-02 11:31:59 -04:00
} ) ;
it ( "loads obj with uvs" , async ( ) => {
const data = await loadObj ( objUvsPath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length / 3 ) . toBe ( 20 ) ;
expect ( primitive . normals . length / 3 ) . toBe ( 0 ) ;
expect ( primitive . uvs . length / 2 ) . toBe ( 20 ) ;
} ) ;
it ( "loads obj with negative indices" , async ( ) => {
const results = [
await loadObj ( objPositionsOnlyPath , options ) ,
await loadObj ( objNegativeIndicesPath , options ) ,
] ;
const positionsReference = getPrimitives (
results [ 0 ]
) [ 0 ] . positions . toFloatBuffer ( ) ;
const positions = getPrimitives ( results [ 1 ] ) [ 0 ] . positions . toFloatBuffer ( ) ;
expect ( positions ) . toEqual ( positionsReference ) ;
} ) ;
it ( "loads obj with triangle faces" , async ( ) => {
const data = await loadObj ( objTrianglesPath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . indices . length ) . toBe ( 36 ) ;
} ) ;
it ( "loads obj with objects" , async ( ) => {
const data = await loadObj ( objObjectsPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 3 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "CubeBlue" ) ;
expect ( nodes [ 1 ] . name ) . toBe ( "CubeGreen" ) ;
expect ( nodes [ 2 ] . name ) . toBe ( "CubeRed" ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 3 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Blue" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Red" ) ;
} ) ;
it ( "loads obj with groups" , async ( ) => {
const data = await loadObj ( objGroupsPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 3 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "CubeBlue" ) ;
expect ( nodes [ 1 ] . name ) . toBe ( "CubeGreen" ) ;
expect ( nodes [ 2 ] . name ) . toBe ( "CubeRed" ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 3 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Blue" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Red" ) ;
} ) ;
it ( "loads obj with objects and groups" , async ( ) => {
const data = await loadObj ( objObjectsGroupsPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 3 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "CubeBlue" ) ;
expect ( nodes [ 1 ] . name ) . toBe ( "CubeGreen" ) ;
expect ( nodes [ 2 ] . name ) . toBe ( "CubeRed" ) ;
const meshes = getMeshes ( data ) ;
expect ( meshes . length ) . toBe ( 3 ) ;
expect ( meshes [ 0 ] . name ) . toBe ( "CubeBlue_CubeBlue_Blue" ) ;
expect ( meshes [ 1 ] . name ) . toBe ( "CubeGreen_CubeGreen_Green" ) ;
expect ( meshes [ 2 ] . name ) . toBe ( "CubeRed_CubeRed_Red" ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 3 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Blue" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Red" ) ;
} ) ;
function loadsObjWithObjectsGroupsAndMaterials ( data ) {
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 1 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "Cube" ) ;
const meshes = getMeshes ( data ) ;
expect ( meshes . length ) . toBe ( 3 ) ;
expect ( meshes [ 0 ] . name ) . toBe ( "Blue" ) ;
expect ( meshes [ 1 ] . name ) . toBe ( "Green" ) ;
expect ( meshes [ 2 ] . name ) . toBe ( "Red" ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 6 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Blue" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 3 ] . material ) . toBe ( "Red" ) ;
expect ( primitives [ 4 ] . material ) . toBe ( "Red" ) ;
expect ( primitives [ 5 ] . material ) . toBe ( "Blue" ) ;
}
it ( "loads obj with objects, groups, and materials" , async ( ) => {
const data = await loadObj ( objObjectsGroupsMaterialsPath , options ) ;
loadsObjWithObjectsGroupsAndMaterials ( data ) ;
} ) ;
it ( "loads obj with objects, groups, and materials (2)" , async ( ) => {
// The usemtl lines are placed in an unordered fashion but
// should produce the same result as the previous test
const data = await loadObj ( objObjectsGroupsMaterialsPath2 , options ) ;
loadsObjWithObjectsGroupsAndMaterials ( data ) ;
} ) ;
it ( "loads obj with concave face containing 5 vertices" , async ( ) => {
const data = await loadObj ( objConcavePath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length / 3 ) . toBe ( 30 ) ;
expect ( primitive . indices . length ) . toBe ( 48 ) ;
} ) ;
it ( "loads obj with usemtl only" , async ( ) => {
const data = await loadObj ( objUsemtlPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 1 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "Node" ) ; // default name
const meshes = getMeshes ( data ) ;
expect ( meshes . length ) . toBe ( 1 ) ;
expect ( meshes [ 0 ] . name ) . toBe ( "Node-Mesh" ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 3 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Blue" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Red" ) ;
} ) ;
it ( "loads obj with no materials" , async ( ) => {
const data = await loadObj ( objNoMaterialsPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 1 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "Node" ) ; // default name
2017-12-29 11:55:21 -05:00
2021-08-02 11:31:59 -04:00
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 1 ) ;
} ) ;
2019-08-19 16:33:12 -07:00
2021-08-02 11:31:59 -04:00
it ( "loads obj with multiple materials" , async ( ) => {
// The usemtl markers are interleaved, but should condense to just three primitives
const data = await loadObj ( objMultipleMaterialsPath , options ) ;
const nodes = data . nodes ;
expect ( nodes . length ) . toBe ( 1 ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 3 ) ;
expect ( primitives [ 0 ] . indices . length ) . toBe ( 12 ) ;
expect ( primitives [ 1 ] . indices . length ) . toBe ( 12 ) ;
expect ( primitives [ 2 ] . indices . length ) . toBe ( 12 ) ;
expect ( primitives [ 0 ] . material ) . toBe ( "Red" ) ;
expect ( primitives [ 1 ] . material ) . toBe ( "Green" ) ;
expect ( primitives [ 2 ] . material ) . toBe ( "Blue" ) ;
for ( let i = 0 ; i < 3 ; ++ i ) {
const indices = primitives [ i ] . indices ;
for ( let j = 0 ; j < indices . length ; ++ j ) {
expect ( indices . get ( j ) ) . toBeLessThan ( 8 ) ;
}
2017-04-20 14:41:39 -04:00
}
2021-08-02 11:31:59 -04:00
} ) ;
2017-04-20 14:41:39 -04:00
2021-08-02 11:31:59 -04:00
it ( "loads obj uncleaned" , async ( ) => {
// Obj with extraneous o, g, and usemtl lines
// Also tests handling of o and g lines with the same names
const data = await loadObj ( objUncleanedPath , options ) ;
const nodes = data . nodes ;
const meshes = getMeshes ( data ) ;
const primitives = getPrimitives ( data ) ;
expect ( nodes . length ) . toBe ( 1 ) ;
expect ( meshes . length ) . toBe ( 1 ) ;
expect ( primitives . length ) . toBe ( 1 ) ;
expect ( nodes [ 0 ] . name ) . toBe ( "Cube" ) ;
expect ( meshes [ 0 ] . name ) . toBe ( "Cube_1" ) ;
} ) ;
it ( "loads obj with multiple mtllibs" , async ( ) => {
const data = await loadObj ( objMtllibPath , options ) ;
const materials = data . materials ;
expect ( materials . length ) . toBe ( 3 ) ;
// .mtl files are loaded in an arbitrary order, so sort for testing purposes
materials . sort ( ( a , b ) => {
return a . name . localeCompare ( b . name ) ;
} ) ;
expect ( materials [ 0 ] . name ) . toBe ( "Blue" ) ;
expect ( materials [ 0 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.0 , 0.0 , 0.64 , 1.0 ,
] ) ;
expect ( materials [ 1 ] . name ) . toBe ( "Green" ) ;
expect ( materials [ 1 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.0 , 0.64 , 0.0 , 1.0 ,
] ) ;
expect ( materials [ 2 ] . name ) . toBe ( "Red" ) ;
expect ( materials [ 2 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.64 , 0.0 , 0.0 , 1.0 ,
] ) ;
} ) ;
it ( "loads obj with mtllib paths with spaces" , async ( ) => {
const data = await loadObj ( objMtllibSpacesPath , options ) ;
const materials = data . materials ;
expect ( materials . length ) . toBe ( 3 ) ;
// .mtl files are loaded in an arbitrary order, so sort for testing purposes
materials . sort ( ( a , b ) => {
return a . name . localeCompare ( b . name ) ;
} ) ;
expect ( materials [ 0 ] . name ) . toBe ( "Blue" ) ;
expect ( materials [ 0 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.0 , 0.0 , 0.64 , 1.0 ,
] ) ;
expect ( materials [ 1 ] . name ) . toBe ( "Green" ) ;
expect ( materials [ 1 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.0 , 0.64 , 0.0 , 1.0 ,
] ) ;
expect ( materials [ 2 ] . name ) . toBe ( "Red" ) ;
expect ( materials [ 2 ] . pbrMetallicRoughness . baseColorFactor ) . toEqual ( [
0.64 , 0.0 , 0.0 , 1.0 ,
] ) ;
} ) ;
it ( "loads obj with missing mtllib" , async ( ) => {
const spy = jasmine . createSpy ( "logger" ) ;
options . logger = spy ;
const data = await loadObj ( objMissingMtllibPath , options ) ;
expect ( data . materials . length ) . toBe ( 0 ) ;
expect ( spy . calls . argsFor ( 0 ) [ 0 ] . indexOf ( "ENOENT" ) >= 0 ) . toBe ( true ) ;
expect ( spy . calls . argsFor ( 0 ) [ 0 ] . indexOf ( path . resolve ( "/box.mtl" ) ) >= 0 ) . toBe (
true
) ;
expect (
spy . calls
. argsFor ( 1 ) [ 0 ]
. indexOf (
"Attempting to read the material file from within the obj directory instead."
) >= 0
) . toBe ( true ) ;
expect ( spy . calls . argsFor ( 2 ) [ 0 ] . indexOf ( "ENOENT" ) >= 0 ) . toBe ( true ) ;
expect (
spy . calls . argsFor ( 3 ) [ 0 ] . indexOf ( "Could not read material file" ) >= 0
) . toBe ( true ) ;
} ) ;
it ( "loads obj with missing usemtl" , async ( ) => {
const data = await loadObj ( objMissingUsemtlPath , options ) ;
expect ( data . materials . length ) . toBe ( 1 ) ;
expect ( data . nodes [ 0 ] . meshes [ 0 ] . primitives [ 0 ] . material ) . toBe ( "Material" ) ;
} ) ;
it ( "loads obj with unnamed material" , async ( ) => {
const data = await loadObj ( objUnnamedMaterialPath , options ) ;
expect ( data . materials . length ) . toBe ( 1 ) ;
expect ( data . nodes [ 0 ] . meshes [ 0 ] . primitives [ 0 ] . material ) . toBe ( "" ) ;
} ) ;
it ( "loads .mtl outside of the obj directory" , async ( ) => {
const data = await loadObj ( objExternalResourcesPath , options ) ;
const materials = data . materials ;
expect ( materials . length ) . toBe ( 2 ) ;
// .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material
const materialTextured =
materials [ 0 ] . name === "MaterialTextured" ? materials [ 0 ] : materials [ 1 ] ;
const baseColorTexture =
materialTextured . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
expect ( baseColorTexture . name ) . toEqual ( "cesium" ) ;
} ) ;
it ( "does not load .mtl outside of the obj directory when secure is true" , async ( ) => {
const spy = jasmine . createSpy ( "logger" ) ;
options . logger = spy ;
options . secure = true ;
const data = await loadObj ( objExternalResourcesPath , options ) ;
expect ( data . materials . length ) . toBe ( 1 ) ; // obj references 2 materials, one of which is outside the input directory
expect (
spy . calls
. argsFor ( 0 ) [ 0 ]
. indexOf (
"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."
) >= 0
) . toBe ( true ) ;
expect ( spy . calls . argsFor ( 1 ) [ 0 ] . indexOf ( "ENOENT" ) >= 0 ) . toBe ( true ) ;
expect (
spy . calls . argsFor ( 2 ) [ 0 ] . indexOf ( "Could not read material file" ) >= 0
) . toBe ( true ) ;
} ) ;
it ( "loads .mtl from root directory when the .mtl path does not exist" , async ( ) => {
const data = await loadObj ( objResourcesInRootPath , options ) ;
const baseColorTexture =
data . materials [ 0 ] . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . name ) . toBe ( "cesium" ) ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
} ) ;
it ( "loads .mtl from root directory when the .mtl path is outside of the obj directory and secure is true" , async ( ) => {
options . secure = true ;
const data = await loadObj ( objExternalResourcesInRootPath , options ) ;
const materials = data . materials ;
expect ( materials . length ) . toBe ( 2 ) ;
// .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material
const materialTextured =
materials [ 0 ] . name === "MaterialTextured" ? materials [ 0 ] : materials [ 1 ] ;
const baseColorTexture =
materialTextured . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
expect ( baseColorTexture . name ) . toEqual ( "cesium" ) ;
} ) ;
it ( "loads obj with texture" , async ( ) => {
const data = await loadObj ( objTexturedPath , options ) ;
const baseColorTexture =
data . materials [ 0 ] . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . name ) . toBe ( "cesium" ) ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
} ) ;
it ( "loads obj with missing texture" , async ( ) => {
const spy = jasmine . createSpy ( "logger" ) ;
options . logger = spy ;
const data = await loadObj ( objMissingTexturePath , options ) ;
const baseColorTexture =
data . materials [ 0 ] . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture ) . toBeUndefined ( ) ;
expect ( spy . calls . argsFor ( 0 ) [ 0 ] . indexOf ( "ENOENT" ) >= 0 ) . toBe ( true ) ;
expect (
spy . calls . argsFor ( 0 ) [ 0 ] . indexOf ( path . resolve ( "/cesium.png" ) ) >= 0
) . toBe ( true ) ;
expect (
spy . calls
. argsFor ( 1 ) [ 0 ]
. indexOf (
"Attempting to read the texture file from within the obj directory instead."
) >= 0
) . toBe ( true ) ;
expect ( spy . calls . argsFor ( 2 ) [ 0 ] . indexOf ( "ENOENT" ) >= 0 ) . toBe ( true ) ;
expect (
spy . calls . argsFor ( 3 ) [ 0 ] . indexOf ( "Could not read texture file" ) >= 0
) . toBe ( true ) ;
} ) ;
it ( "loads obj with subdirectories" , async ( ) => {
const data = await loadObj ( objSubdirectoriesPath , options ) ;
const baseColorTexture =
data . materials [ 0 ] . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . name ) . toBe ( "cesium" ) ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
} ) ;
it ( "loads obj with windows paths" , async ( ) => {
const data = await loadObj ( objWindowsPaths , options ) ;
const baseColorTexture =
data . materials [ 0 ] . pbrMetallicRoughness . baseColorTexture ;
expect ( baseColorTexture . name ) . toBe ( "cesium" ) ;
expect ( baseColorTexture . source ) . toBeDefined ( ) ;
} ) ;
2021-08-17 20:08:14 +01:00
it ( "loads an obj where coordinates are separated by tabs" , async ( ) => {
/ * *
* We know Tinkercad to produce files with coordinates separated by tabs .
* /
const data = await loadObj ( objWithTabs , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . normals . length / 3 ) . toBe ( 24 ) ;
expect ( primitive . uvs . length / 2 ) . toBe ( 24 ) ;
} ) ;
2021-08-02 11:31:59 -04:00
it ( "separates faces that don't use the same attributes as other faces in the primitive" , async ( ) => {
const data = await loadObj ( objMixedAttributesPath , options ) ;
const primitives = getPrimitives ( data ) ;
expect ( primitives . length ) . toBe ( 4 ) ;
expect ( primitives [ 0 ] . indices . length ) . toBe ( 18 ) ; // 6 faces
expect ( primitives [ 1 ] . indices . length ) . toBe ( 6 ) ; // 2 faces
expect ( primitives [ 2 ] . indices . length ) . toBe ( 6 ) ; // 2 faces
expect ( primitives [ 3 ] . indices . length ) . toBe ( 6 ) ; // 2 faces
} ) ;
function getFirstPosition ( data ) {
const primitive = getPrimitives ( data ) [ 0 ] ;
return new Cartesian3 (
primitive . positions . get ( 0 ) ,
primitive . positions . get ( 1 ) ,
primitive . positions . get ( 2 )
) ;
}
function getFirstNormal ( data ) {
const primitive = getPrimitives ( data ) [ 0 ] ;
return new Cartesian3 (
primitive . normals . get ( 0 ) ,
primitive . normals . get ( 1 ) ,
primitive . normals . get ( 2 )
) ;
}
async function checkAxisConversion (
inputUpAxis ,
outputUpAxis ,
position ,
normal
) {
const sameAxis = inputUpAxis === outputUpAxis ;
options . inputUpAxis = inputUpAxis ;
options . outputUpAxis = outputUpAxis ;
const data = await loadObj ( objRotatedUrl , options ) ;
const rotatedPosition = getFirstPosition ( data ) ;
const rotatedNormal = getFirstNormal ( data ) ;
if ( sameAxis ) {
expect ( rotatedPosition ) . toEqual ( position ) ;
expect ( rotatedNormal ) . toEqual ( normal ) ;
} else {
expect ( rotatedPosition ) . not . toEqual ( position ) ;
expect ( rotatedNormal ) . not . toEqual ( normal ) ;
2017-04-20 14:41:39 -04:00
}
2021-08-02 11:31:59 -04:00
}
it ( "performs up axis conversion" , async ( ) => {
const data = await loadObj ( objRotatedUrl , options ) ;
const position = getFirstPosition ( data ) ;
const normal = getFirstNormal ( data ) ;
const axes = [ "X" , "Y" , "Z" ] ;
const axesLength = axes . length ;
for ( let i = 0 ; i < axesLength ; ++ i ) {
for ( let j = 0 ; j < axesLength ; ++ j ) {
await checkAxisConversion ( axes [ i ] , axes [ j ] , position , normal ) ;
}
2017-04-20 14:41:39 -04:00
}
2021-08-02 11:31:59 -04:00
} ) ;
it ( "ignores missing normals and uvs" , async ( ) => {
const data = await loadObj ( objMissingAttributesPath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
expect ( primitive . positions . length ) . toBeGreaterThan ( 0 ) ;
expect ( primitive . normals . length ) . toBe ( 0 ) ;
expect ( primitive . uvs . length ) . toBe ( 0 ) ;
} ) ;
async function loadAndGetIndices ( objPath , options ) {
const data = await loadObj ( objPath , options ) ;
const primitive = getPrimitives ( data ) [ 0 ] ;
const indices = primitive . indices ;
return new Uint16Array ( indices . toUint16Buffer ( ) . buffer ) ;
}
it ( "applies triangle winding order sanitization" , async ( ) => {
options . triangleWindingOrderSanitization = false ;
const indicesIncorrect = await loadAndGetIndices (
objIncorrectWindingOrderPath ,
options
) ;
options . triangleWindingOrderSanitization = true ;
const indicesCorrect = await loadAndGetIndices (
objIncorrectWindingOrderPath ,
options
) ;
expect ( indicesIncorrect [ 0 ] ) . toBe ( 0 ) ;
expect ( indicesIncorrect [ 2 ] ) . toBe ( 2 ) ;
expect ( indicesIncorrect [ 1 ] ) . toBe ( 1 ) ;
expect ( indicesCorrect [ 0 ] ) . toBe ( 0 ) ;
expect ( indicesCorrect [ 2 ] ) . toBe ( 1 ) ;
expect ( indicesCorrect [ 1 ] ) . toBe ( 2 ) ;
} ) ;
it ( "throws when position index is out of bounds" , async ( ) => {
let thrownError ;
try {
await loadObj ( objIncompletePositionsPath , options ) ;
} catch ( e ) {
thrownError = e ;
2021-08-01 20:11:28 -04:00
}
2021-08-02 11:31:59 -04:00
expect ( thrownError ) . toEqual (
new RuntimeError ( "Position index 1 is out of bounds" )
) ;
} ) ;
it ( "throws when normal index is out of bounds" , async ( ) => {
let thrownError ;
try {
await loadObj ( objIncompleteNormalsPath , options ) ;
} catch ( e ) {
thrownError = e ;
}
expect ( thrownError ) . toEqual (
new RuntimeError ( "Normal index 1 is out of bounds" )
) ;
} ) ;
it ( "throws when uv index is out of bounds" , async ( ) => {
let thrownError ;
try {
await loadObj ( objIncompleteUvsPath , options ) ;
} catch ( e ) {
thrownError = e ;
}
expect ( thrownError ) . toEqual (
new RuntimeError ( "UV index 1 is out of bounds" )
) ;
} ) ;
it ( "throws when file has invalid contents" , async ( ) => {
let thrownError ;
try {
await loadObj ( objInvalidContentsPath , options ) ;
} catch ( e ) {
thrownError = e ;
}
expect ( thrownError ) . toEqual (
new RuntimeError (
2022-05-25 10:18:31 -04:00
` ${ objInvalidContentsPath } does not have any geometry data `
2021-08-02 11:31:59 -04:00
)
) ;
} ) ;
it ( "throw when reading invalid file" , async ( ) => {
let thrownError ;
try {
await loadObj ( objInvalidPath , options ) ;
} catch ( e ) {
thrownError = e ;
}
expect (
thrownError . message . startsWith ( "ENOENT: no such file or directory" )
) . toBe ( true ) ;
} ) ;
2017-03-13 15:28:51 -04:00
} ) ;