obj2gltf/specs/lib/loadObjSpec.js

414 lines
17 KiB
JavaScript
Raw Normal View History

2017-03-13 15:28:51 -04:00
'use strict';
var Cesium = require('cesium');
var path = require('path');
var Promise = require('bluebird');
2017-04-12 16:55:03 -04:00
var loadObj = require('../../lib/loadObj');
var obj2gltf = require('../../lib/obj2gltf');
2017-03-13 15:28:51 -04:00
2017-04-20 14:41:39 -04:00
var Cartesian3 = Cesium.Cartesian3;
2017-04-10 17:57:56 -04:00
var clone = Cesium.clone;
2017-03-13 15:28:51 -04:00
var RuntimeError = Cesium.RuntimeError;
var objUrl = 'specs/data/box/box.obj';
2017-04-20 14:41:39 -04:00
var objRotatedUrl = 'specs/data/box-rotated/box-rotated.obj';
2017-03-13 15:28:51 -04:00
var objNormalsUrl = 'specs/data/box-normals/box-normals.obj';
var objUvsUrl = 'specs/data/box-uvs/box-uvs.obj';
var objPositionsOnlyUrl = 'specs/data/box-positions-only/box-positions-only.obj';
var objNegativeIndicesUrl = 'specs/data/box-negative-indices/box-negative-indices.obj';
var objTrianglesUrl = 'specs/data/box-triangles/box-triangles.obj';
var objObjectsUrl = 'specs/data/box-objects/box-objects.obj';
var objGroupsUrl = 'specs/data/box-groups/box-groups.obj';
var objObjectsGroupsUrl = 'specs/data/box-objects-groups/box-objects-groups.obj';
var objUsemtlUrl = 'specs/data/box-usemtl/box-usemtl.obj';
var objNoMaterialsUrl = 'specs/data/box-no-materials/box-no-materials.obj';
var objMultipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.obj';
var objUncleanedUrl = 'specs/data/box-uncleaned/box-uncleaned.obj';
var objMtllibUrl = 'specs/data/box-mtllib/box-mtllib.obj';
var objMissingMtllibUrl = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj';
2017-04-04 16:45:21 -04:00
var objExternalResourcesUrl = 'specs/data/box-external-resources/box-external-resources.obj';
2017-03-13 15:28:51 -04:00
var objTexturedUrl = 'specs/data/box-textured/box-textured.obj';
var objMissingTextureUrl = 'specs/data/box-missing-texture/box-missing-texture.obj';
var objSubdirectoriesUrl = 'specs/data/box-subdirectories/box-textured.obj';
var objComplexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.obj';
var objInvalidContentsUrl = 'specs/data/box/box.mtl';
var objInvalidUrl = 'invalid.obj';
function getMeshes(data) {
var meshes = [];
var nodes = data.nodes;
var nodesLength = nodes.length;
for (var i = 0; i < nodesLength; ++i) {
meshes = meshes.concat(nodes[i].meshes);
}
return meshes;
}
function getPrimitives(data) {
var primitives = [];
var nodes = data.nodes;
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) {
primitives = primitives.concat(meshes[j].primitives);
}
}
return primitives;
}
function getImagePath(objPath, relativePath) {
2017-04-10 17:57:56 -04:00
return path.resolve(path.dirname(objPath), relativePath);
2017-03-13 15:28:51 -04:00
}
2017-04-12 16:55:03 -04:00
var defaultOptions = obj2gltf.defaults;
2017-04-10 17:57:56 -04:00
2017-04-19 15:48:07 -04:00
describe('loadObj', function() {
2017-04-25 13:02:14 -04:00
beforeEach(function() {
spyOn(console, 'log');
});
2017-03-13 15:28:51 -04:00
it('loads obj with positions, normals, and uvs', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var images = data.images;
var materials = data.materials;
var nodes = data.nodes;
var meshes = getMeshes(data);
var primitives = getPrimitives(data);
2017-05-04 17:58:13 -04:00
expect(images.length).toBe(0);
expect(materials.length).toBe(1);
2017-03-13 15:28:51 -04:00
expect(nodes.length).toBe(1);
expect(meshes.length).toBe(1);
expect(primitives.length).toBe(1);
var node = nodes[0];
var mesh = meshes[0];
var primitive = primitives[0];
expect(node.name).toBe('Cube');
expect(mesh.name).toBe('Cube-Mesh');
expect(mesh.positions.length / 3).toBe(24);
expect(mesh.normals.length / 3).toBe(24);
expect(mesh.uvs.length / 2).toBe(24);
expect(primitive.indices.length).toBe(36);
expect(primitive.material).toBe('Material');
}), done).toResolve();
});
it('loads obj with normals', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objNormalsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var mesh = getMeshes(data)[0];
expect(mesh.positions.length / 3).toBe(24);
expect(mesh.normals.length / 3).toBe(24);
expect(mesh.uvs.length / 2).toBe(0);
}), done).toResolve();
});
it('loads obj with uvs', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objUvsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var mesh = getMeshes(data)[0];
expect(mesh.positions.length / 3).toBe(20);
expect(mesh.normals.length / 3).toBe(0);
expect(mesh.uvs.length / 2).toBe(20);
}), done).toResolve();
});
it('loads obj with negative indices', function(done) {
expect(Promise.all([
2017-04-10 17:57:56 -04:00
loadObj(objPositionsOnlyUrl, defaultOptions),
loadObj(objNegativeIndicesUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
])
.then(function(results) {
var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer();
var positions = getMeshes(results[1])[0].positions.toFloatBuffer();
expect(positions).toEqual(positionsReference);
}), done).toResolve();
});
it('loads obj with triangle faces', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objTrianglesUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var mesh = getMeshes(data)[0];
var primitive = getPrimitives(data)[0];
expect(mesh.positions.length / 3).toBe(24);
expect(primitive.indices.length).toBe(36);
}), done).toResolve();
});
it('loads obj with objects', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objObjectsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var 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');
var 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');
}), done).toResolve();
});
it('loads obj with groups', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objGroupsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var 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');
var 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');
}), done).toResolve();
});
it('loads obj with objects and groups', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objObjectsGroupsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var 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');
var 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');
var 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');
}), done).toResolve();
});
it('loads obj with usemtl only', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objUsemtlUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var nodes = data.nodes;
expect(nodes.length).toBe(1);
expect(nodes[0].name).toBe('Node'); // default name
var meshes = getMeshes(data);
expect(meshes.length).toBe(1);
expect(meshes[0].name).toBe('Node-Mesh');
var 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');
}), done).toResolve();
});
it('loads obj with no materials', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objNoMaterialsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var nodes = data.nodes;
expect(nodes.length).toBe(1);
expect(nodes[0].name).toBe('Node'); // default name
var primitives = getPrimitives(data);
expect(primitives.length).toBe(1);
}), done).toResolve();
});
it('loads obj with multiple materials', function(done) {
// The usemtl markers are interleaved, but should condense to just three primitives
2017-04-10 17:57:56 -04:00
expect(loadObj(objMultipleMaterialsUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var nodes = data.nodes;
expect(nodes.length).toBe(1);
var 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');
}), done).toResolve();
});
it('loads obj uncleaned', function(done) {
// Obj with extraneous o, g, and usemtl lines
// Also tests handling of o and g lines with the same names
2017-04-10 17:57:56 -04:00
expect(loadObj(objUncleanedUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var nodes = data.nodes;
var meshes = getMeshes(data);
var 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');
}), done).toResolve();
});
it('loads obj with multiple mtllibs', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objMtllibUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var materials = data.materials;
2017-05-04 17:58:13 -04:00
expect(materials.length).toBe(3);
// .mtl files are loaded in an arbitrary order, so sort for testing purposes
materials.sort(function(a, b){
return a.name.localeCompare(b.name);
});
expect(materials[0].name).toBe('Blue');
expect(materials[0].diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]);
expect(materials[1].name).toBe('Green');
expect(materials[1].diffuseColor).toEqual([0.0, 0.64, 0.0, 1.0]);
expect(materials[2].name).toBe('Red');
expect(materials[2].diffuseColor).toEqual([0.64, 0.0, 0.0, 1.0]);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
it('loads obj with missing mtllib', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objMissingMtllibUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
2017-05-04 17:58:13 -04:00
expect(data.materials.length).toBe(0);
2017-04-04 16:45:21 -04:00
expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true);
}), done).toResolve();
});
it('loads resources outside of the obj directory', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objExternalResourcesUrl, defaultOptions)
2017-04-04 16:45:21 -04:00
.then(function(data) {
var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
2017-05-04 17:58:13 -04:00
expect(data.images[0].path).toBe(imagePath);
var materials = data.materials;
expect(materials.length).toBe(2);
// .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material
var materialTextured = materials[0].name === 'MaterialTextured' ? materials[0] : materials[1];
expect(materialTextured.diffuseTexture).toEqual(imagePath);
2017-04-04 16:45:21 -04:00
}), done).toResolve();
});
it('does not load resources outside of the obj directory when secure is true', function(done) {
2017-04-10 17:57:56 -04:00
var options = clone(defaultOptions);
options.secure = true;
2017-04-04 16:45:21 -04:00
expect(loadObj(objExternalResourcesUrl, options)
.then(function(data) {
2017-05-04 17:58:13 -04:00
expect(data.images.length).toBe(0); // obj references an image file that is outside the input directory
expect(data.materials.length).toBe(1); // obj references 2 materials, one of which is outside the input directory
2017-04-04 16:45:21 -04:00
expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true);
expect(console.log.calls.argsFor(1)[0].indexOf('Could not read image file') >= 0).toBe(true);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
it('loads obj with texture', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objTexturedUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
2017-05-04 17:58:13 -04:00
expect(data.images[0].path).toBe(imagePath);
expect(data.materials[0].diffuseTexture).toEqual(imagePath);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
it('loads obj with missing texture', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objMissingTextureUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png');
2017-05-04 17:58:13 -04:00
expect(data.images.length).toBe(0);
expect(data.materials[0].diffuseTexture).toEqual(imagePath);
2017-04-04 16:45:21 -04:00
expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
it('loads obj with subdirectories', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objSubdirectoriesUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png'));
2017-05-04 17:58:13 -04:00
expect(data.images[0].path).toBe(imagePath);
expect(data.materials[0].diffuseTexture).toEqual(imagePath);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
it('loads obj with complex material', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objComplexMaterialUrl, defaultOptions)
2017-03-13 15:28:51 -04:00
.then(function(data) {
var images = data.images;
2017-05-04 17:58:13 -04:00
expect(images.length).toBe(6);
2017-03-13 15:28:51 -04:00
}), done).toResolve();
});
2017-04-20 14:41:39 -04:00
function getFirstPosition(data) {
var positions = data.nodes[0].meshes[0].positions;
return new Cartesian3(positions.get(0), positions.get(1), positions.get(2));
}
function getFirstNormal(data) {
var normals = data.nodes[0].meshes[0].normals;
return new Cartesian3(normals.get(0), normals.get(1), normals.get(2));
}
function checkAxisConversion(inputUpAxis, outputUpAxis, position, normal) {
var sameAxis = (inputUpAxis === outputUpAxis);
var options = clone(defaultOptions);
options.inputUpAxis = inputUpAxis;
options.outputUpAxis = outputUpAxis;
return loadObj(objRotatedUrl, options)
.then(function(data) {
var rotatedPosition = getFirstPosition(data);
var rotatedNormal = getFirstNormal(data);
if (sameAxis) {
expect(rotatedPosition).toEqual(position);
expect(rotatedNormal).toEqual(normal);
} else {
expect(rotatedPosition).not.toEqual(position);
expect(rotatedNormal).not.toEqual(normal);
}
});
}
it('performs up axis conversion', function(done) {
expect(loadObj(objRotatedUrl, defaultOptions)
.then(function(data) {
var position = getFirstPosition(data);
var normal = getFirstNormal(data);
var axes = ['X', 'Y', 'Z'];
var axesLength = axes.length;
var promises = [];
for (var i = 0; i < axesLength; ++i) {
for (var j = 0; j < axesLength; ++j) {
promises.push(checkAxisConversion(axes[i], axes[j], position, normal));
}
}
return Promise.all(promises);
}), done).toResolve();
});
it('throws when file has invalid contents', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objInvalidContentsUrl, defaultOptions), done).toRejectWith(RuntimeError);
2017-03-13 15:28:51 -04:00
});
it('throw when reading invalid file', function(done) {
2017-04-10 17:57:56 -04:00
expect(loadObj(objInvalidUrl, defaultOptions), done).toRejectWith(Error);
2017-03-13 15:28:51 -04:00
});
});