mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-27 10:30:16 -05:00
Merge branch 'master' into unlit
This commit is contained in:
commit
b5da7be229
@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "cesium/node"
|
||||
"extends": "cesium/node",
|
||||
"rules": {
|
||||
"no-var": "off"
|
||||
}
|
||||
}
|
||||
|
17
CHANGES.md
17
CHANGES.md
@ -1,12 +1,25 @@
|
||||
Change Log
|
||||
==========
|
||||
|
||||
### 2.3.0 ???
|
||||
### 2.3.2 2018-11-02
|
||||
|
||||
* Improved handling of primitives with different attributes using the same material. Materials are now duplicated. [#162](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/162)
|
||||
* Fixed a bug where primitives without texture coordinates could use materials containing textures. Those textures are now removed. [#162](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/162)
|
||||
* Improved parsing of faces with mismatching attributes. [#161](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/161)
|
||||
|
||||
### 2.3.1 2018-10-16
|
||||
|
||||
* Improved parsing models with concave or n-sided faces. [#157](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/157)
|
||||
* Fixed handling of objs with interleaved materials. [#155](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/155)
|
||||
|
||||
### 2.3.0 2018-09-19
|
||||
|
||||
* Fixed handling of objs with mismatching attribute layouts. [#153](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/153)
|
||||
* Fixed normalization of Windows paths when running the converter on Linux. [#150](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/150)
|
||||
* Added ability to use the first material in the mtl file when the obj is missing `usemtl`. [#133](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/133)
|
||||
* Fixed handling of unnormalized input normals. [#136](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/136)
|
||||
|
||||
### 2.2.0 2017-01-29
|
||||
### 2.2.0 2018-01-29
|
||||
|
||||
* Fixed handling of materials where the diffuse and ambient texture are the same. [#127](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/127)
|
||||
* Added ability to load alpha textures. [#124](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/124)
|
||||
|
@ -1,5 +1,7 @@
|
||||
# OBJ2GLTF
|
||||
|
||||
[![Greenkeeper badge](https://badges.greenkeeper.io/AnalyticalGraphicsInc/obj2gltf.svg)](https://greenkeeper.io/)
|
||||
|
||||
Convert OBJ assets to [glTF](https://www.khronos.org/gltf) 2.0.
|
||||
|
||||
## Getting Started
|
||||
|
30
gulpfile.js
30
gulpfile.js
@ -22,7 +22,14 @@ process.env.PATH += environmentSeparator + nodeBinaries;
|
||||
|
||||
var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**'];
|
||||
|
||||
gulp.task('test', function (done) {
|
||||
module.exports = {
|
||||
test: test,
|
||||
'test-watch': testWatch,
|
||||
coverage: coverage,
|
||||
cloc: cloc
|
||||
};
|
||||
|
||||
function test(done) {
|
||||
var jasmine = new Jasmine();
|
||||
jasmine.loadConfigFile('specs/jasmine.json');
|
||||
jasmine.addReporter(new JasmineSpecReporter({
|
||||
@ -32,10 +39,10 @@ gulp.task('test', function (done) {
|
||||
jasmine.onComplete(function (passed) {
|
||||
done(argv.failTaskOnError && !passed ? 1 : 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('test-watch', function () {
|
||||
gulp.watch(specFiles).on('change', function () {
|
||||
function testWatch() {
|
||||
return gulp.watch(specFiles).on('change', function () {
|
||||
// We can't simply depend on the test task because Jasmine
|
||||
// does not like being run multiple times in the same process.
|
||||
try {
|
||||
@ -46,9 +53,9 @@ gulp.task('test-watch', function () {
|
||||
console.log('Tests failed to execute.');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('coverage', function () {
|
||||
async function coverage() {
|
||||
fsExtra.removeSync('coverage/server');
|
||||
child_process.execSync('nyc' +
|
||||
' --all' +
|
||||
@ -57,13 +64,12 @@ gulp.task('coverage', function () {
|
||||
' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' +
|
||||
' node_modules/jasmine/bin/jasmine.js' +
|
||||
' JASMINE_CONFIG_PATH=specs/jasmine.json', {
|
||||
stdio: [process.stdin, process.stdout, process.stderr]
|
||||
});
|
||||
stdio: [process.stdin, process.stdout, process.stderr]
|
||||
});
|
||||
open('coverage/lcov-report/index.html');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
gulp.task('cloc', function() {
|
||||
function cloc() {
|
||||
var cmdLine;
|
||||
var clocPath = path.join('node_modules', 'cloc', 'lib', 'cloc');
|
||||
|
||||
@ -99,4 +105,4 @@ gulp.task('cloc', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ var getBufferPadded = require('./getBufferPadded');
|
||||
var getDefaultMaterial = require('./loadMtl').getDefaultMaterial;
|
||||
var Texture = require('./Texture');
|
||||
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
var defined = Cesium.defined;
|
||||
var WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
@ -23,6 +24,9 @@ function createGltf(objData, options) {
|
||||
var materials = objData.materials;
|
||||
var name = objData.name;
|
||||
|
||||
// Split materials used by primitives with different types of attributes
|
||||
materials = splitIncompatibleMaterials(nodes, materials, options);
|
||||
|
||||
var gltf = {
|
||||
accessors : [],
|
||||
asset : {},
|
||||
@ -133,10 +137,8 @@ function addBufferView(gltf, buffers, accessors, byteStride, target) {
|
||||
}
|
||||
|
||||
function addBuffers(gltf, bufferState, name) {
|
||||
// Positions and normals share the same byte stride so they can share the same bufferView
|
||||
var positionsAndNormalsAccessors = bufferState.positionAccessors.concat(bufferState.normalAccessors);
|
||||
var positionsAndNormalsBuffers = bufferState.positionBuffers.concat(bufferState.normalBuffers);
|
||||
addBufferView(gltf, positionsAndNormalsBuffers, positionsAndNormalsAccessors, 12, WebGLConstants.ARRAY_BUFFER);
|
||||
addBufferView(gltf, bufferState.positionBuffers, bufferState.positionAccessors, 12, WebGLConstants.ARRAY_BUFFER);
|
||||
addBufferView(gltf, bufferState.normalBuffers, bufferState.normalAccessors, 12, WebGLConstants.ARRAY_BUFFER);
|
||||
addBufferView(gltf, bufferState.uvBuffers, bufferState.uvAccessors, 8, WebGLConstants.ARRAY_BUFFER);
|
||||
addBufferView(gltf, bufferState.indexBuffers, bufferState.indexAccessors, undefined, WebGLConstants.ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
@ -198,6 +200,31 @@ function getTexture(gltf, texture) {
|
||||
};
|
||||
}
|
||||
|
||||
function cloneMaterial(material, removeTextures) {
|
||||
if (typeof material !== 'object') {
|
||||
return material;
|
||||
} else if (material instanceof Texture) {
|
||||
if (removeTextures) {
|
||||
return undefined;
|
||||
}
|
||||
return material;
|
||||
} else if (Array.isArray(material)) {
|
||||
var length = material.length;
|
||||
var clonedArray = new Array(length);
|
||||
for (var i = 0; i < length; ++i) {
|
||||
clonedArray[i] = cloneMaterial(material[i], removeTextures);
|
||||
}
|
||||
return clonedArray;
|
||||
}
|
||||
var clonedObject = {};
|
||||
for (var name in material) {
|
||||
if (material.hasOwnProperty(name)) {
|
||||
clonedObject[name] = cloneMaterial(material[name], removeTextures);
|
||||
}
|
||||
}
|
||||
return clonedObject;
|
||||
}
|
||||
|
||||
function resolveTextures(gltf, material) {
|
||||
for (var name in material) {
|
||||
if (material.hasOwnProperty(name)) {
|
||||
@ -211,7 +238,7 @@ function resolveTextures(gltf, material) {
|
||||
}
|
||||
}
|
||||
|
||||
function addMaterial(gltf, material, options) {
|
||||
function addGltfMaterial(gltf, material, options) {
|
||||
resolveTextures(gltf, material);
|
||||
var materialIndex = gltf.materials.length;
|
||||
if (options.unlit) {
|
||||
@ -224,43 +251,94 @@ function addMaterial(gltf, material, options) {
|
||||
return materialIndex;
|
||||
}
|
||||
|
||||
function getMaterial(gltf, materials, materialName, options) {
|
||||
if (!defined(materialName)) {
|
||||
// Create a default material if the primitive does not specify one
|
||||
materialName = 'default';
|
||||
}
|
||||
|
||||
var i;
|
||||
var material;
|
||||
function getMaterialByName(materials, materialName) {
|
||||
var materialsLength = materials.length;
|
||||
for (i = 0; i < materialsLength; ++i) {
|
||||
for (var i = 0; i < materialsLength; ++i) {
|
||||
if (materials[i].name === materialName) {
|
||||
material = materials[i];
|
||||
break;
|
||||
return materials[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined(material)) {
|
||||
material = getDefaultMaterial(options);
|
||||
material.name = materialName;
|
||||
}
|
||||
|
||||
var materialIndex;
|
||||
materialsLength = gltf.materials.length;
|
||||
for (i = 0; i < materialsLength; ++i) {
|
||||
if (gltf.materials[i].name === materialName) {
|
||||
materialIndex = i;
|
||||
break;
|
||||
function getMaterialIndex(materials, materialName) {
|
||||
var materialsLength = materials.length;
|
||||
for (var i = 0; i < materialsLength; ++i) {
|
||||
if (materials[i].name === materialName) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOrCreateGltfMaterial(gltf, materials, materialName, options) {
|
||||
var material = getMaterialByName(materials, materialName);
|
||||
var materialIndex = getMaterialIndex(gltf.materials, materialName);
|
||||
|
||||
if (!defined(materialIndex)) {
|
||||
materialIndex = addMaterial(gltf, material, options);
|
||||
materialIndex = addGltfMaterial(gltf, material, options);
|
||||
}
|
||||
|
||||
return materialIndex;
|
||||
}
|
||||
|
||||
function primitiveInfoMatch(a, b) {
|
||||
return a.hasUvs === b.hasUvs &&
|
||||
a.hasNormals === b.hasNormals;
|
||||
}
|
||||
|
||||
function getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial) {
|
||||
var splitMaterialName = originalMaterialName;
|
||||
var suffix = 2;
|
||||
while (defined(primitiveInfoByMaterial[splitMaterialName])) {
|
||||
if (primitiveInfoMatch(primitiveInfo, primitiveInfoByMaterial[splitMaterialName])) {
|
||||
break;
|
||||
}
|
||||
splitMaterialName = originalMaterialName + '-' + suffix++;
|
||||
}
|
||||
return splitMaterialName;
|
||||
}
|
||||
|
||||
function splitIncompatibleMaterials(nodes, materials, options) {
|
||||
var splitMaterials = [];
|
||||
var primitiveInfoByMaterial = {};
|
||||
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];
|
||||
var hasUvs = primitive.uvs.length > 0;
|
||||
var hasNormals = primitive.normals.length > 0;
|
||||
var primitiveInfo = {
|
||||
hasUvs : hasUvs,
|
||||
hasNormals : hasNormals
|
||||
};
|
||||
var originalMaterialName = defaultValue(primitive.material, 'default');
|
||||
var splitMaterialName = getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial);
|
||||
primitive.material = splitMaterialName;
|
||||
primitiveInfoByMaterial[splitMaterialName] = primitiveInfo;
|
||||
|
||||
var splitMaterial = getMaterialByName(splitMaterials, splitMaterialName);
|
||||
if (defined(splitMaterial)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalMaterial = getMaterialByName(materials, originalMaterialName);
|
||||
if (defined(originalMaterial)) {
|
||||
splitMaterial = cloneMaterial(originalMaterial, !hasUvs);
|
||||
} else {
|
||||
splitMaterial = getDefaultMaterial(options);
|
||||
}
|
||||
splitMaterial.name = splitMaterialName;
|
||||
splitMaterials.push(splitMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
return splitMaterials;
|
||||
}
|
||||
|
||||
function addVertexAttribute(gltf, array, components, name) {
|
||||
var count = array.length / components;
|
||||
var minMax = array.getMinMax(components);
|
||||
@ -305,68 +383,73 @@ function requiresUint32Indices(nodes) {
|
||||
var meshes = nodes[i].meshes;
|
||||
var meshesLength = meshes.length;
|
||||
for (var j = 0; j < meshesLength; ++j) {
|
||||
// Reserve the 65535 index for primitive restart
|
||||
var vertexCount = meshes[j].positions.length / 3;
|
||||
if (vertexCount > 65534) {
|
||||
return true;
|
||||
var primitives = meshes[j].primitives;
|
||||
var primitivesLength = primitives.length;
|
||||
for (var k = 0; k < primitivesLength; ++k) {
|
||||
// Reserve the 65535 index for primitive restart
|
||||
var vertexCount = primitives[k].positions.length / 3;
|
||||
if (vertexCount > 65534) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addMesh(gltf, materials, bufferState, uint32Indices, mesh, options) {
|
||||
var hasPositions = mesh.positions.length > 0;
|
||||
var hasNormals = mesh.normals.length > 0;
|
||||
var hasUVs = mesh.uvs.length > 0;
|
||||
function addPrimitive(gltf, materials, bufferState, uint32Indices, mesh, primitive, index, options) {
|
||||
var hasPositions = primitive.positions.length > 0;
|
||||
var hasNormals = primitive.normals.length > 0;
|
||||
var hasUVs = primitive.uvs.length > 0;
|
||||
|
||||
// Vertex attributes are shared by all primitives in the mesh
|
||||
var accessorIndex;
|
||||
var attributes = {};
|
||||
if (hasPositions) {
|
||||
accessorIndex = addVertexAttribute(gltf, mesh.positions, 3, mesh.name + '_positions');
|
||||
accessorIndex = addVertexAttribute(gltf, primitive.positions, 3, mesh.name + '_' + index + '_positions');
|
||||
attributes.POSITION = accessorIndex;
|
||||
bufferState.positionBuffers.push(mesh.positions.toFloatBuffer());
|
||||
bufferState.positionBuffers.push(primitive.positions.toFloatBuffer());
|
||||
bufferState.positionAccessors.push(accessorIndex);
|
||||
}
|
||||
if (hasNormals) {
|
||||
accessorIndex = addVertexAttribute(gltf, mesh.normals, 3, mesh.name + '_normals');
|
||||
accessorIndex = addVertexAttribute(gltf, primitive.normals, 3, mesh.name + '_' + index + '_normals');
|
||||
attributes.NORMAL = accessorIndex;
|
||||
bufferState.normalBuffers.push(mesh.normals.toFloatBuffer());
|
||||
bufferState.normalBuffers.push(primitive.normals.toFloatBuffer());
|
||||
bufferState.normalAccessors.push(accessorIndex);
|
||||
}
|
||||
if (hasUVs) {
|
||||
accessorIndex = addVertexAttribute(gltf, mesh.uvs, 2, mesh.name + '_texcoords');
|
||||
accessorIndex = addVertexAttribute(gltf, primitive.uvs, 2, mesh.name + '_' + index + '_texcoords');
|
||||
attributes.TEXCOORD_0 = accessorIndex;
|
||||
bufferState.uvBuffers.push(mesh.uvs.toFloatBuffer());
|
||||
bufferState.uvBuffers.push(primitive.uvs.toFloatBuffer());
|
||||
bufferState.uvAccessors.push(accessorIndex);
|
||||
}
|
||||
|
||||
// Unload resources
|
||||
mesh.positions = undefined;
|
||||
mesh.normals = undefined;
|
||||
mesh.uvs = undefined;
|
||||
var indexAccessorIndex = addIndexArray(gltf, primitive.indices, uint32Indices, mesh.name + '_' + index + '_indices');
|
||||
var indexBuffer = uint32Indices ? primitive.indices.toUint32Buffer() : primitive.indices.toUint16Buffer();
|
||||
bufferState.indexBuffers.push(indexBuffer);
|
||||
bufferState.indexAccessors.push(indexAccessorIndex);
|
||||
|
||||
// Unload resources
|
||||
primitive.positions = undefined;
|
||||
primitive.normals = undefined;
|
||||
primitive.uvs = undefined;
|
||||
primitive.indices = undefined;
|
||||
|
||||
var materialIndex = getOrCreateGltfMaterial(gltf, materials, primitive.material, options);
|
||||
|
||||
return {
|
||||
attributes : attributes,
|
||||
indices : indexAccessorIndex,
|
||||
material : materialIndex,
|
||||
mode : WebGLConstants.TRIANGLES
|
||||
};
|
||||
}
|
||||
|
||||
function addMesh(gltf, materials, bufferState, uint32Indices, mesh, options) {
|
||||
var gltfPrimitives = [];
|
||||
var primitives = mesh.primitives;
|
||||
var primitivesLength = primitives.length;
|
||||
for (var i = 0; i < primitivesLength; ++i) {
|
||||
var primitive = primitives[i];
|
||||
var indexAccessorIndex = addIndexArray(gltf, primitive.indices, uint32Indices, mesh.name + '_' + i + '_indices');
|
||||
var indexBuffer = uint32Indices ? primitive.indices.toUint32Buffer() : primitive.indices.toUint16Buffer();
|
||||
bufferState.indexBuffers.push(indexBuffer);
|
||||
bufferState.indexAccessors.push(indexAccessorIndex);
|
||||
|
||||
primitive.indices = undefined; // Unload resources
|
||||
|
||||
var materialIndex = getMaterial(gltf, materials, primitive.material, options);
|
||||
|
||||
gltfPrimitives.push({
|
||||
attributes : attributes,
|
||||
indices : indexAccessorIndex,
|
||||
material : materialIndex,
|
||||
mode : WebGLConstants.TRIANGLES
|
||||
});
|
||||
gltfPrimitives.push(addPrimitive(gltf, materials, bufferState, uint32Indices, mesh, primitives[i], i, options));
|
||||
}
|
||||
|
||||
var gltfMesh = {
|
||||
|
@ -81,19 +81,15 @@ function loadMtl(mtlPath, options) {
|
||||
materials.push(material);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes texture options from texture name
|
||||
* NOTE: assumes no spaces in texture name
|
||||
*
|
||||
* @param {String} name
|
||||
* @returns {String} The clean texture name
|
||||
*/
|
||||
function cleanTextureName (name) {
|
||||
function normalizeTexturePath(texturePath, mtlDirectory) {
|
||||
// Removes texture options from texture name
|
||||
// Assumes no spaces in texture name
|
||||
var re = /-(bm|t|s|o|blendu|blendv|boost|mm|texres|clamp|imfchan|type)/;
|
||||
if (re.test(name)) {
|
||||
return name.split(/\s+/).pop();
|
||||
if (re.test(texturePath)) {
|
||||
texturePath = texturePath.split(/\s+/).pop();
|
||||
}
|
||||
return name;
|
||||
texturePath = texturePath.replace(/\\/g, '/');
|
||||
return path.normalize(path.join(mtlDirectory, texturePath));
|
||||
}
|
||||
|
||||
function parseLine(line) {
|
||||
@ -144,31 +140,31 @@ function loadMtl(mtlPath, options) {
|
||||
material.alpha = correctAlpha(1.0 - parseFloat(value));
|
||||
} else if (/^map_Ka /i.test(line)) {
|
||||
if (!defined(overridingAmbientTexture)) {
|
||||
material.ambientTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(7).trim()));
|
||||
material.ambientTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_Ke /i.test(line)) {
|
||||
if (!defined(overridingEmissiveTexture)) {
|
||||
material.emissiveTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(7).trim()));
|
||||
material.emissiveTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_Kd /i.test(line)) {
|
||||
if (!defined(overridingDiffuseTexture)) {
|
||||
material.diffuseTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(7).trim()));
|
||||
material.diffuseTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_Ks /i.test(line)) {
|
||||
if (!defined(overridingSpecularTexture)) {
|
||||
material.specularTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(7).trim()));
|
||||
material.specularTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_Ns /i.test(line)) {
|
||||
if (!defined(overridingSpecularShininessTexture)) {
|
||||
material.specularShininessTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(7).trim()));
|
||||
material.specularShininessTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_Bump /i.test(line)) {
|
||||
if (!defined(overridingNormalTexture)) {
|
||||
material.normalTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(9).trim()));
|
||||
material.normalTexture = normalizeTexturePath(line.substring(9).trim(), mtlDirectory);
|
||||
}
|
||||
} else if (/^map_d /i.test(line)) {
|
||||
if (!defined(overridingAlphaTexture)) {
|
||||
material.alphaTexture = path.resolve(mtlDirectory, cleanTextureName(line.substring(6).trim()));
|
||||
material.alphaTexture = normalizeTexturePath(line.substring(6).trim(), mtlDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,7 +240,7 @@ function loadMaterialTexture(material, name, textureOptions, mtlDirectory, textu
|
||||
|
||||
var texturePromise = texturePromiseMap[texturePath];
|
||||
if (!defined(texturePromise)) {
|
||||
var shallowPath = path.resolve(path.join(mtlDirectory, path.basename(texturePath)));
|
||||
var shallowPath = path.join(mtlDirectory, path.basename(texturePath));
|
||||
if (options.secure && outsideDirectory(texturePath, mtlDirectory)) {
|
||||
// Try looking for the texture in the same directory as the obj
|
||||
options.logger('Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead.');
|
||||
|
314
lib/loadObj.js
314
lib/loadObj.js
@ -8,17 +8,12 @@ var loadMtl = require('./loadMtl');
|
||||
var outsideDirectory = require('./outsideDirectory');
|
||||
var readLines = require('./readLines');
|
||||
|
||||
var Cartesian2 = Cesium.Cartesian2;
|
||||
var Cartesian3 = Cesium.Cartesian3;
|
||||
var ComponentDatatype = Cesium.ComponentDatatype;
|
||||
var CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary;
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
var defined = Cesium.defined;
|
||||
var IntersectionTests = Cesium.IntersectionTests;
|
||||
var Matrix3 = Cesium.Matrix3;
|
||||
var OrientedBoundingBox = Cesium.OrientedBoundingBox;
|
||||
var Plane = Cesium.Plane;
|
||||
var PolygonPipeline = Cesium.PolygonPipeline;
|
||||
var Ray = Cesium.Ray;
|
||||
var RuntimeError = Cesium.RuntimeError;
|
||||
var WindingOrder = Cesium.WindingOrder;
|
||||
|
||||
@ -36,14 +31,14 @@ function Node() {
|
||||
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);
|
||||
this.positions = new ArrayStorage(ComponentDatatype.FLOAT);
|
||||
this.normals = new ArrayStorage(ComponentDatatype.FLOAT);
|
||||
this.uvs = new ArrayStorage(ComponentDatatype.FLOAT);
|
||||
}
|
||||
|
||||
// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
|
||||
@ -76,7 +71,7 @@ function loadObj(objPath, options) {
|
||||
// All nodes seen in the obj
|
||||
var nodes = [];
|
||||
|
||||
// Used to build the indices. The vertex cache is unique to each mesh.
|
||||
// Used to build the indices. The vertex cache is unique to each primitive.
|
||||
var vertexCache = {};
|
||||
var vertexCacheLimit = 1000000;
|
||||
var vertexCacheCount = 0;
|
||||
@ -94,7 +89,10 @@ function loadObj(objPath, options) {
|
||||
var faceUvs = [];
|
||||
var faceNormals = [];
|
||||
|
||||
var vertexIndices = [];
|
||||
function clearVertexCache() {
|
||||
vertexCache = {};
|
||||
vertexCacheCount = 0;
|
||||
}
|
||||
|
||||
function getName(name) {
|
||||
return (name === '' ? undefined : name);
|
||||
@ -112,36 +110,56 @@ function loadObj(objPath, options) {
|
||||
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();
|
||||
primitive.material = activeMaterial;
|
||||
mesh.primitives.push(primitive);
|
||||
|
||||
// Clear the vertex cache for each new primitive
|
||||
clearVertexCache();
|
||||
vertexCount = 0;
|
||||
}
|
||||
|
||||
function useMaterial(name) {
|
||||
var material = getName(name);
|
||||
activeMaterial = material;
|
||||
|
||||
// Look to see if this material has already been used by a primitive in the mesh
|
||||
function reusePrimitive(callback) {
|
||||
var primitives = mesh.primitives;
|
||||
var primitivesLength = primitives.length;
|
||||
for (var i = 0; i < primitivesLength; ++i) {
|
||||
if (primitives[i].material === material) {
|
||||
primitive = primitives[i];
|
||||
return;
|
||||
if (primitives[i].material === activeMaterial) {
|
||||
if (!defined(callback) || callback(primitives[i])) {
|
||||
primitive = primitives[i];
|
||||
clearVertexCache();
|
||||
vertexCount = primitive.positions.length / 3;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add a new primitive with this material
|
||||
addPrimitive();
|
||||
}
|
||||
|
||||
function useMaterial(name) {
|
||||
activeMaterial = getName(name);
|
||||
reusePrimitive();
|
||||
}
|
||||
|
||||
function faceAndPrimitiveMatch(uvs, normals, primitive) {
|
||||
var faceHasUvs = uvs[0].length > 0;
|
||||
var faceHasNormals = normals[0].length > 0;
|
||||
var primitiveHasUvs = primitive.uvs.length > 0;
|
||||
var primitiveHasNormals = primitive.normals.length > 0;
|
||||
return primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals;
|
||||
}
|
||||
|
||||
function checkPrimitive(uvs, normals) {
|
||||
var firstFace = primitive.indices.length === 0;
|
||||
if (!firstFace && !faceAndPrimitiveMatch(uvs, normals, primitive)) {
|
||||
reusePrimitive(function(primitive) {
|
||||
return faceAndPrimitiveMatch(uvs, normals, primitive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getOffset(a, attributeData, components) {
|
||||
var i = parseInt(a);
|
||||
if (i < 0) {
|
||||
@ -158,9 +176,9 @@ function loadObj(objPath, options) {
|
||||
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);
|
||||
primitive.positions.push(px);
|
||||
primitive.positions.push(py);
|
||||
primitive.positions.push(pz);
|
||||
}
|
||||
|
||||
// Normals
|
||||
@ -169,9 +187,9 @@ function loadObj(objPath, options) {
|
||||
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);
|
||||
primitive.normals.push(nx);
|
||||
primitive.normals.push(ny);
|
||||
primitive.normals.push(nz);
|
||||
}
|
||||
|
||||
// UVs
|
||||
@ -179,8 +197,8 @@ function loadObj(objPath, options) {
|
||||
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);
|
||||
primitive.uvs.push(ux);
|
||||
primitive.uvs.push(uy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,114 +213,13 @@ function loadObj(objPath, options) {
|
||||
// may be some duplicate vertices.
|
||||
vertexCacheCount++;
|
||||
if (vertexCacheCount > vertexCacheLimit) {
|
||||
vertexCacheCount = 0;
|
||||
vertexCache = {};
|
||||
clearVertexCache();
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
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);
|
||||
|
||||
if (i === scratchPositions2D.length) {
|
||||
scratchPositions2D.push(new Cartesian2());
|
||||
}
|
||||
|
||||
positions2D[i] = new Cartesian2.fromElements(x, y, scratchPositions2D[i]);
|
||||
}
|
||||
|
||||
return positions2D;
|
||||
}
|
||||
|
||||
function get3DPoint(index, result) {
|
||||
function getPosition(index, result) {
|
||||
var pi = getOffset(index, positions, 3);
|
||||
var px = positions.get(pi + 0);
|
||||
var py = positions.get(pi + 1);
|
||||
@ -310,7 +227,7 @@ function loadObj(objPath, options) {
|
||||
return Cartesian3.fromElements(px, py, pz, result);
|
||||
}
|
||||
|
||||
function get3DNormal(index, result) {
|
||||
function getNormal(index, result) {
|
||||
var ni = getOffset(index, normals, 3);
|
||||
var nx = normals.get(ni + 0);
|
||||
var ny = normals.get(ni + 1);
|
||||
@ -318,36 +235,28 @@ function loadObj(objPath, options) {
|
||||
return Cartesian3.fromElements(nx, ny, nz, result);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var turnDirection = getTurnDirection(positions2D[0], positions2D[1], positions2D[2]);
|
||||
for (var i=1; i < positions2D.length-2; ++i) {
|
||||
var currentTurnDirection = getTurnDirection(positions2D[i], positions2D[i+1], positions2D[i+2]);
|
||||
if (turnDirection * currentTurnDirection < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var scratch3 = new Cartesian3();
|
||||
var scratch4 = new Cartesian3();
|
||||
var scratch5 = new Cartesian3();
|
||||
// Checks if winding order matches the given normal.
|
||||
function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normal) {
|
||||
var A = get3DPoint(positionIndex1, scratch1);
|
||||
var B = get3DPoint(positionIndex2, scratch2);
|
||||
var C = get3DPoint(positionIndex3, scratch3);
|
||||
var scratchCenter = new Cartesian3();
|
||||
var scratchAxis1 = new Cartesian3();
|
||||
var scratchAxis2 = new Cartesian3();
|
||||
var scratchNormal = new Cartesian3();
|
||||
var scratchPositions = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
|
||||
var scratchVertexIndices = [];
|
||||
var scratchPoints = [];
|
||||
|
||||
function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normalIndex) {
|
||||
if (normalIndex.length === 0) {
|
||||
// If no face normal, we have to assume the winding is correct.
|
||||
return true;
|
||||
}
|
||||
var normal = getNormal(normalIndex, scratchNormal);
|
||||
var A = getPosition(positionIndex1, scratch1);
|
||||
var B = getPosition(positionIndex2, scratch2);
|
||||
var C = getPosition(positionIndex3, scratch3);
|
||||
|
||||
var BA = Cartesian3.subtract(B, A, scratch4);
|
||||
var CA = Cartesian3.subtract(C, A, scratch5);
|
||||
@ -368,58 +277,45 @@ function loadObj(objPath, options) {
|
||||
}
|
||||
}
|
||||
|
||||
var scratchPositions3D = [new Cartesian3(), new Cartesian3(), new Cartesian3()];
|
||||
function addFace(vertices, positions, uvs, normals) {
|
||||
var isWindingCorrect = true;
|
||||
var faceNormal;
|
||||
var i;
|
||||
var isWindingCorrect;
|
||||
|
||||
// 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.
|
||||
if (normals[0].length > 0) {
|
||||
faceNormal = get3DNormal(normals[0], scratchNormal);
|
||||
isWindingCorrect = checkWindingCorrect(positions[0], positions[1], positions[2], faceNormal);
|
||||
}
|
||||
checkPrimitive(uvs, normals);
|
||||
|
||||
if (vertices.length === 3) {
|
||||
isWindingCorrect = checkWindingCorrect(positions[0], positions[1], positions[2], normals[0]);
|
||||
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]);
|
||||
addTriangle(index1, index2, index3, isWindingCorrect);
|
||||
} else { // Triangulate if the face is not a triangle
|
||||
var positions3D = [];
|
||||
var points = scratchPoints;
|
||||
var vertexIndices = scratchVertexIndices;
|
||||
|
||||
points.length = 0;
|
||||
vertexIndices.length = 0;
|
||||
|
||||
var i;
|
||||
for (i = 0; i < vertices.length; ++i) {
|
||||
var index = addVertex(vertices[i], positions[i], uvs[i], normals[i]);
|
||||
vertexIndices.push(index);
|
||||
|
||||
// Collect the vertex positions as 3D points
|
||||
if (i === scratchPositions3D.length) {
|
||||
scratchPositions3D.push(new Cartesian3());
|
||||
if (i === scratchPositions.length) {
|
||||
scratchPositions.push(new Cartesian3());
|
||||
}
|
||||
positions3D.push(get3DPoint(positions[i], scratchPositions3D[i]));
|
||||
points.push(getPosition(positions[i], scratchPositions[i]));
|
||||
}
|
||||
|
||||
var positions2D = projectTo2D(positions3D);
|
||||
var validGeometry = CoplanarPolygonGeometryLibrary.computeProjectTo2DArguments(points, scratchCenter, scratchAxis1, scratchAxis2);
|
||||
if (!validGeometry) {
|
||||
return;
|
||||
}
|
||||
var projectPoints = CoplanarPolygonGeometryLibrary.createProjectPointsTo2DFunction(scratchCenter, scratchAxis1, scratchAxis2);
|
||||
var points2D = projectPoints(points);
|
||||
var indices = PolygonPipeline.triangulate(points2D);
|
||||
isWindingCorrect = PolygonPipeline.computeWindingOrder2D(points2D) !== WindingOrder.CLOCKWISE;
|
||||
|
||||
if (isConvex(positions2D)) {
|
||||
for (i=1; i < vertices.length-1; ++i) {
|
||||
addTriangle(vertexIndices[0], vertexIndices[i], vertexIndices[i+1], isWindingCorrect);
|
||||
}
|
||||
} else {
|
||||
// Since the projection doesn't preserve winding order, reverse the order of
|
||||
// the vertices before triangulating to enforce counter clockwise.
|
||||
var projectedWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
|
||||
if (projectedWindingOrder === WindingOrder.CLOCKWISE) {
|
||||
positions2D.reverse();
|
||||
}
|
||||
|
||||
// Use an ear-clipping algorithm to triangulate
|
||||
var positionIndices = PolygonPipeline.triangulate(positions2D);
|
||||
for (i = 0; i < positionIndices.length-2; i += 3) {
|
||||
addTriangle(vertexIndices[positionIndices[i]], vertexIndices[positionIndices[i+1]], vertexIndices[positionIndices[i+2]], isWindingCorrect);
|
||||
}
|
||||
for (i = 0; i < indices.length - 2; i += 3) {
|
||||
addTriangle(vertexIndices[indices[i]], vertexIndices[indices[i+1]], vertexIndices[indices[i+2]], isWindingCorrect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -502,7 +398,7 @@ function loadObj(objPath, options) {
|
||||
uvs = undefined;
|
||||
|
||||
// Load materials and textures
|
||||
return finishLoading(nodes, mtlPaths, objPath, options);
|
||||
return finishLoading(nodes, mtlPaths, objPath, defined(activeMaterial), options);
|
||||
});
|
||||
}
|
||||
|
||||
@ -523,7 +419,7 @@ function getMtlPaths(mtllibLine) {
|
||||
return mtlPaths;
|
||||
}
|
||||
|
||||
function finishLoading(nodes, mtlPaths, objPath, options) {
|
||||
function finishLoading(nodes, mtlPaths, objPath, usesMaterials, options) {
|
||||
nodes = cleanNodes(nodes);
|
||||
if (nodes.length === 0) {
|
||||
throw new RuntimeError(objPath + ' does not have any geometry data');
|
||||
@ -531,7 +427,9 @@ function finishLoading(nodes, mtlPaths, objPath, options) {
|
||||
var name = path.basename(objPath, path.extname(objPath));
|
||||
return loadMtls(mtlPaths, objPath, options)
|
||||
.then(function(materials) {
|
||||
assignDefaultMaterial(nodes, materials);
|
||||
if (materials.length > 0 && !usesMaterials) {
|
||||
assignDefaultMaterial(nodes, materials, usesMaterials);
|
||||
}
|
||||
return {
|
||||
nodes : nodes,
|
||||
materials : materials,
|
||||
@ -540,6 +438,11 @@ function finishLoading(nodes, mtlPaths, objPath, options) {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeMtlPath(mtlPath, objDirectory) {
|
||||
mtlPath = mtlPath.replace(/\\/g, '/');
|
||||
return path.normalize(path.join(objDirectory, mtlPath));
|
||||
}
|
||||
|
||||
function loadMtls(mtlPaths, objPath, options) {
|
||||
var objDirectory = path.dirname(objPath);
|
||||
var materials = [];
|
||||
@ -550,8 +453,8 @@ function loadMtls(mtlPaths, objPath, options) {
|
||||
});
|
||||
|
||||
return Promise.map(mtlPaths, function(mtlPath) {
|
||||
mtlPath = path.resolve(objDirectory, mtlPath);
|
||||
var shallowPath = path.resolve(path.join(objDirectory, path.basename(mtlPath)));
|
||||
mtlPath = normalizeMtlPath(mtlPath, objDirectory);
|
||||
var 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.');
|
||||
@ -586,9 +489,6 @@ function loadMtls(mtlPaths, objPath, options) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -609,10 +509,10 @@ function removeEmptyMeshes(meshes) {
|
||||
return meshes.filter(function(mesh) {
|
||||
// Remove empty primitives
|
||||
mesh.primitives = mesh.primitives.filter(function(primitive) {
|
||||
return primitive.indices.length > 0;
|
||||
return primitive.indices.length > 0 && primitive.positions.length > 0;
|
||||
});
|
||||
// Valid meshes must have at least one primitive and contain positions
|
||||
return (mesh.primitives.length > 0) && (mesh.positions.length > 0);
|
||||
// Valid meshes must have at least one primitive
|
||||
return (mesh.primitives.length > 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
36
package.json
36
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obj2gltf",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.2",
|
||||
"description": "Convert OBJ model format to glTF",
|
||||
"license": "Apache-2.0",
|
||||
"contributors": [
|
||||
@ -26,26 +26,26 @@
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.1",
|
||||
"cesium": "^1.39.0",
|
||||
"fs-extra": "^4.0.2",
|
||||
"jpeg-js": "^0.3.3",
|
||||
"mime": "^2.0.3",
|
||||
"pngjs": "^3.3.0",
|
||||
"yargs": "^10.0.3"
|
||||
"bluebird": "^3.5.2",
|
||||
"cesium": "^1.50.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"jpeg-js": "^0.3.4",
|
||||
"mime": "^2.3.1",
|
||||
"pngjs": "^3.3.3",
|
||||
"yargs": "^12.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cloc": "^2.3.3",
|
||||
"coveralls": "^3.0.0",
|
||||
"eslint": "^4.4.1",
|
||||
"eslint-config-cesium": "^2.0.1",
|
||||
"gulp": "^3.9.1",
|
||||
"jasmine": "^2.7.0",
|
||||
"jasmine-spec-reporter": "^4.2.0",
|
||||
"jsdoc": "^3.5.4",
|
||||
"nyc": "^11.1.0",
|
||||
"cloc": "^2.3.4",
|
||||
"coveralls": "^3.0.2",
|
||||
"eslint": "^5.6.1",
|
||||
"eslint-config-cesium": "^6.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"jasmine": "^3.2.0",
|
||||
"jasmine-spec-reporter": "^4.2.1",
|
||||
"jsdoc": "^3.5.5",
|
||||
"nyc": "^13.0.1",
|
||||
"open": "^0.0.5",
|
||||
"requirejs": "^2.3.4"
|
||||
"requirejs": "^2.3.6"
|
||||
},
|
||||
"scripts": {
|
||||
"jsdoc": "jsdoc ./lib -R ./README.md -d doc",
|
||||
|
13
specs/data/box-mixed-attributes-2/box-mixed-attributes-2.mtl
Normal file
13
specs/data/box-mixed-attributes-2/box-mixed-attributes-2.mtl
Normal file
@ -0,0 +1,13 @@
|
||||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 0.100000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.100000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd cesium.png
|
67
specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj
Normal file
67
specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj
Normal file
@ -0,0 +1,67 @@
|
||||
# Blender v2.78 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib box-mixed-attributes-2.mtl
|
||||
o Cube
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 0.0000 1.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
# Using default material
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/5/2 4/6/2 8/7/2 7/8/2
|
||||
f 7/9 8/10 6/11 5/12
|
||||
f 5/13 6/14 2/15 1/16
|
||||
f 3//5 7//5 5//5 1//5
|
||||
f 8//6 4//6 2//6 6//6
|
||||
usemtl Material
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/5/2 4/6/2 8/7/2 7/8/2
|
||||
f 7/9 8/10 6/11 5/12
|
||||
f 5/13 6/14 2/15 1/16
|
||||
f 3//5 7//5 5//5 1//5
|
||||
f 8//6 4//6 2//6 6//6
|
||||
usemtl Missing
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/5/2 4/6/2 8/7/2 7/8/2
|
||||
f 7/9 8/10 6/11 5/12
|
||||
f 5/13 6/14 2/15 1/16
|
||||
f 3//5 7//5 5//5 1//5
|
||||
f 8//6 4//6 2//6 6//6
|
||||
o CubeCopy
|
||||
usemtl Material
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/5/2 4/6/2 8/7/2 7/8/2
|
||||
f 7/9 8/10 6/11 5/12
|
||||
f 5/13 6/14 2/15 1/16
|
||||
f 3//5 7//5 5//5 1//5
|
||||
f 8//6 4//6 2//6 6//6
|
BIN
specs/data/box-mixed-attributes-2/cesium.png
Normal file
BIN
specs/data/box-mixed-attributes-2/cesium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
12
specs/data/box-mixed-attributes/box-mixed-attributes.mtl
Normal file
12
specs/data/box-mixed-attributes/box-mixed-attributes.mtl
Normal file
@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 0.100000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.100000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
46
specs/data/box-mixed-attributes/box-mixed-attributes.obj
Normal file
46
specs/data/box-mixed-attributes/box-mixed-attributes.obj
Normal file
@ -0,0 +1,46 @@
|
||||
# Blender v2.78 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib box-mixed-attributes.mtl
|
||||
o Cube
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 0.0000 1.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
usemtl Material
|
||||
s off
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3 4 8 7
|
||||
f 7/9/3 8/10/3 6/11/3 5/12/3
|
||||
f 5/13/4 6/14/4 2/15/4 1/16/4
|
||||
f 3//5 7//5 5//5 1//5
|
||||
f 8/19 4/6 2/15 6/20
|
46
specs/data/box-windows-paths/box-windows-paths.obj
Normal file
46
specs/data/box-windows-paths/box-windows-paths.obj
Normal file
@ -0,0 +1,46 @@
|
||||
# Blender v2.78 (sub 0) OBJ File: 'box.blend'
|
||||
# www.blender.org
|
||||
mtllib materials\\box-windows-paths.mtl
|
||||
o Cube
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 1.0000
|
||||
vt 1.0000 0.0000
|
||||
vt 1.0000 1.0000
|
||||
vt 0.0000 0.0000
|
||||
vt 0.0000 1.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
usemtl Material
|
||||
s off
|
||||
f 1/1/1 2/2/1 4/3/1 3/4/1
|
||||
f 3/5/2 4/6/2 8/7/2 7/8/2
|
||||
f 7/9/3 8/10/3 6/11/3 5/12/3
|
||||
f 5/13/4 6/14/4 2/15/4 1/16/4
|
||||
f 3/5/5 7/17/5 5/18/5 1/16/5
|
||||
f 8/19/6 4/6/6 2/15/6 6/20/6
|
13
specs/data/box-windows-paths/materials/box-windows-paths.mtl
Normal file
13
specs/data/box-windows-paths/materials/box-windows-paths.mtl
Normal file
@ -0,0 +1,13 @@
|
||||
# Blender MTL File: 'box.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd .\images\cesium.png
|
BIN
specs/data/box-windows-paths/materials/images/cesium.png
Normal file
BIN
specs/data/box-windows-paths/materials/images/cesium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
@ -4,14 +4,17 @@ var Promise = require('bluebird');
|
||||
var obj2gltf = require('../../lib/obj2gltf');
|
||||
var createGltf = require('../../lib/createGltf');
|
||||
var loadObj = require('../../lib/loadObj');
|
||||
var getDefaultMaterial = require('../../lib/loadMtl').getDefaultMaterial;
|
||||
|
||||
var clone = Cesium.clone;
|
||||
var defined = Cesium.defined;
|
||||
var WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
var boxObjPath = 'specs/data/box/box.obj';
|
||||
var groupObjPath = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj';
|
||||
var complexObjPath = 'specs/data/box-complex-material/box-complex-material.obj';
|
||||
var noMaterialsObjPath = 'specs/data/box-no-materials/box-no-materials.obj';
|
||||
var mixedAttributesObjPath = 'specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj';
|
||||
|
||||
var options;
|
||||
|
||||
@ -20,6 +23,7 @@ describe('createGltf', function() {
|
||||
var groupObjData;
|
||||
var complexObjData;
|
||||
var noMaterialsObjData;
|
||||
var mixedAttributesObjData;
|
||||
|
||||
beforeEach(function(done) {
|
||||
options = clone(obj2gltf.defaults);
|
||||
@ -42,6 +46,10 @@ describe('createGltf', function() {
|
||||
loadObj(noMaterialsObjPath, options)
|
||||
.then(function(data) {
|
||||
noMaterialsObjData = data;
|
||||
}),
|
||||
loadObj(mixedAttributesObjPath, options)
|
||||
.then(function(data) {
|
||||
mixedAttributesObjData = data;
|
||||
})
|
||||
]).then(done);
|
||||
});
|
||||
@ -133,42 +141,108 @@ describe('createGltf', function() {
|
||||
});
|
||||
|
||||
it('runs without normals', function() {
|
||||
boxObjData.nodes[0].meshes[0].normals.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
|
||||
|
||||
var gltf = createGltf(boxObjData, options);
|
||||
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
|
||||
var attributes = gltf.meshes[0].primitives[0].attributes;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeUndefined();
|
||||
expect(attributes.TEXCOORD_0).toBeDefined();
|
||||
});
|
||||
|
||||
it('runs without uvs', function() {
|
||||
boxObjData.nodes[0].meshes[0].uvs.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
|
||||
|
||||
var gltf = createGltf(boxObjData, options);
|
||||
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
|
||||
var attributes = gltf.meshes[0].primitives[0].attributes;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeDefined();
|
||||
expect(attributes.TEXCOORD_0).toBeUndefined();
|
||||
});
|
||||
|
||||
it('runs without uvs and normals', function() {
|
||||
boxObjData.nodes[0].meshes[0].normals.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].uvs.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
|
||||
|
||||
var gltf = createGltf(boxObjData, options);
|
||||
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
|
||||
var attributes = gltf.meshes[0].primitives[0].attributes;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeUndefined();
|
||||
expect(attributes.TEXCOORD_0).toBeUndefined();
|
||||
});
|
||||
|
||||
it('splits incompatible materials', function() {
|
||||
var gltf = createGltf(mixedAttributesObjData, options);
|
||||
var materials = gltf.materials;
|
||||
var meshes = gltf.meshes;
|
||||
|
||||
var referenceMaterial = mixedAttributesObjData.materials[0];
|
||||
delete referenceMaterial.name;
|
||||
referenceMaterial.pbrMetallicRoughness.baseColorTexture = {
|
||||
index : 0
|
||||
};
|
||||
|
||||
var referenceMaterialNoTextures = clone(referenceMaterial, true);
|
||||
referenceMaterialNoTextures.pbrMetallicRoughness.baseColorTexture = undefined;
|
||||
|
||||
var defaultMaterial = getDefaultMaterial(options);
|
||||
delete defaultMaterial.name;
|
||||
|
||||
var materialNames = materials.map(function(material) {
|
||||
var name = material.name;
|
||||
delete material.name;
|
||||
return name;
|
||||
});
|
||||
|
||||
// Expect three copies of each material for
|
||||
// * positions/normals/uvs
|
||||
// * positions/normals
|
||||
// * positions/uvs
|
||||
expect(materialNames).toEqual([
|
||||
'default',
|
||||
'default-2',
|
||||
'default-3',
|
||||
'Material',
|
||||
'Material-2',
|
||||
'Material-3',
|
||||
'Missing',
|
||||
'Missing-2',
|
||||
'Missing-3'
|
||||
]);
|
||||
|
||||
expect(materials.length).toBe(9);
|
||||
expect(materials[0]).toEqual(defaultMaterial);
|
||||
expect(materials[1]).toEqual(defaultMaterial);
|
||||
expect(materials[2]).toEqual(defaultMaterial);
|
||||
expect(materials[3]).toEqual(referenceMaterial);
|
||||
expect(materials[4]).toEqual(referenceMaterial);
|
||||
expect(materials[5]).toEqual(referenceMaterialNoTextures);
|
||||
expect(materials[6]).toEqual(defaultMaterial);
|
||||
expect(materials[7]).toEqual(defaultMaterial);
|
||||
expect(materials[8]).toEqual(defaultMaterial);
|
||||
|
||||
// Test that primitives without uvs reference materials without textures
|
||||
var meshesLength = meshes.length;
|
||||
for (var i = 0; i < meshesLength; ++i) {
|
||||
var mesh = meshes[i];
|
||||
var primitives = mesh.primitives;
|
||||
var primitivesLength = primitives.length;
|
||||
for (var j = 0; j < primitivesLength; ++j) {
|
||||
var primitive = primitives[j];
|
||||
var material = materials[primitive.material];
|
||||
if (!defined(primitive.attributes.TEXCOORD_0)) {
|
||||
expect(material.pbrMetallicRoughness.baseColorTexture).toBeUndefined();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function expandObjData(objData, duplicatesLength) {
|
||||
var mesh = objData.nodes[0].meshes[0];
|
||||
var indices = mesh.primitives[0].indices;
|
||||
var positions = mesh.positions;
|
||||
var normals = mesh.normals;
|
||||
var uvs = mesh.uvs;
|
||||
var primitive = objData.nodes[0].meshes[0].primitives[0];
|
||||
var indices = primitive.indices;
|
||||
var positions = primitive.positions;
|
||||
var normals = primitive.normals;
|
||||
var uvs = primitive.uvs;
|
||||
|
||||
var indicesLength = indices.length;
|
||||
var vertexCount = positions.length / 3;
|
||||
@ -192,12 +266,12 @@ describe('createGltf', function() {
|
||||
|
||||
it('detects need to use uint32 indices', function() {
|
||||
expandObjData(boxObjData, 2731); // Right above 65536 limit
|
||||
var mesh = boxObjData.nodes[0].meshes[0];
|
||||
var indicesLength = mesh.primitives[0].indices.length;
|
||||
var vertexCount = mesh.positions.length / 3;
|
||||
var primitive = boxObjData.nodes[0].meshes[0].primitives[0];
|
||||
var indicesLength = primitive.indices.length;
|
||||
var vertexCount = primitive.positions.length / 3;
|
||||
|
||||
var gltf = createGltf(boxObjData, options);
|
||||
var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0];
|
||||
primitive = gltf.meshes[0].primitives[0];
|
||||
var indicesAccessor = gltf.accessors[primitive.indices];
|
||||
expect(indicesAccessor.count).toBe(indicesLength);
|
||||
expect(indicesAccessor.max[0]).toBe(vertexCount - 1);
|
||||
|
@ -34,9 +34,11 @@ var objExternalResourcesInRootPath = 'specs/data/box-external-resources-in-root/
|
||||
var objTexturedPath = 'specs/data/box-textured/box-textured.obj';
|
||||
var objMissingTexturePath = 'specs/data/box-missing-texture/box-missing-texture.obj';
|
||||
var objSubdirectoriesPath = 'specs/data/box-subdirectories/box-textured.obj';
|
||||
var objWindowsPaths = 'specs/data/box-windows-paths/box-windows-paths.obj';
|
||||
var objInvalidContentsPath = 'specs/data/box/box.mtl';
|
||||
var objConcavePath = 'specs/data/concave/concave.obj';
|
||||
var objUnnormalizedPath = 'specs/data/box-unnormalized/box-unnormalized.obj';
|
||||
var objMixedAttributesPath = 'specs/data/box-mixed-attributes/box-mixed-attributes.obj';
|
||||
var objInvalidPath = 'invalid.obj';
|
||||
|
||||
function getMeshes(data) {
|
||||
@ -93,9 +95,9 @@ describe('loadObj', function() {
|
||||
|
||||
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.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');
|
||||
}), done).toResolve();
|
||||
@ -104,10 +106,10 @@ describe('loadObj', function() {
|
||||
it('loads obj with normals', function(done) {
|
||||
expect(loadObj(objNormalsPath, options)
|
||||
.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);
|
||||
var 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);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
@ -115,8 +117,8 @@ describe('loadObj', function() {
|
||||
expect(loadObj(objUnnormalizedPath, options)
|
||||
.then(function(data) {
|
||||
var scratchNormal = new Cesium.Cartesian3();
|
||||
var mesh = getMeshes(data)[0];
|
||||
var normals = mesh.normals;
|
||||
var primitive = getPrimitives(data)[0];
|
||||
var normals = primitive.normals;
|
||||
var normalsLength = normals.length / 3;
|
||||
for (var i = 0; i < normalsLength; ++i) {
|
||||
var normalX = normals.get(i * 3);
|
||||
@ -131,10 +133,10 @@ describe('loadObj', function() {
|
||||
it('loads obj with uvs', function(done) {
|
||||
expect(loadObj(objUvsPath, options)
|
||||
.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);
|
||||
var 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);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
@ -144,8 +146,8 @@ describe('loadObj', function() {
|
||||
loadObj(objNegativeIndicesPath, options)
|
||||
])
|
||||
.then(function(results) {
|
||||
var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer();
|
||||
var positions = getMeshes(results[1])[0].positions.toFloatBuffer();
|
||||
var positionsReference = getPrimitives(results[0])[0].positions.toFloatBuffer();
|
||||
var positions = getPrimitives(results[1])[0].positions.toFloatBuffer();
|
||||
expect(positions).toEqual(positionsReference);
|
||||
}), done).toResolve();
|
||||
});
|
||||
@ -153,9 +155,8 @@ describe('loadObj', function() {
|
||||
it('loads obj with triangle faces', function(done) {
|
||||
expect(loadObj(objTrianglesPath, options)
|
||||
.then(function(data) {
|
||||
var mesh = getMeshes(data)[0];
|
||||
var primitive = getPrimitives(data)[0];
|
||||
expect(mesh.positions.length / 3).toBe(24);
|
||||
expect(primitive.positions.length / 3).toBe(24);
|
||||
expect(primitive.indices.length).toBe(36);
|
||||
}), done).toResolve();
|
||||
});
|
||||
@ -255,9 +256,8 @@ describe('loadObj', function() {
|
||||
it('loads obj with concave face containing 5 vertices', function(done) {
|
||||
expect(loadObj(objConcavePath, options)
|
||||
.then(function(data) {
|
||||
var mesh = getMeshes(data)[0];
|
||||
var primitive = getPrimitives(data)[0];
|
||||
expect(mesh.positions.length / 3).toBe(30);
|
||||
expect(primitive.positions.length / 3).toBe(30);
|
||||
expect(primitive.indices.length).toBe(48);
|
||||
}), done).toResolve();
|
||||
});
|
||||
@ -309,6 +309,13 @@ describe('loadObj', function() {
|
||||
expect(primitives[0].material).toBe('Red');
|
||||
expect(primitives[1].material).toBe('Green');
|
||||
expect(primitives[2].material).toBe('Blue');
|
||||
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var indices = primitives[i].indices;
|
||||
for (var j = 0; j < indices.length; ++j) {
|
||||
expect(indices.get(j)).toBeLessThan(8);
|
||||
}
|
||||
}
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
@ -478,6 +485,27 @@ describe('loadObj', function() {
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('loads obj with windows paths', function(done) {
|
||||
expect(loadObj(objWindowsPaths, options)
|
||||
.then(function(data) {
|
||||
var baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture;
|
||||
expect(baseColorTexture.name).toBe('cesium');
|
||||
expect(baseColorTexture.source).toBeDefined();
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('separates faces that don\'t use the same attributes as other faces in the primitive', function(done) {
|
||||
expect(loadObj(objMixedAttributesPath, options)
|
||||
.then(function(data) {
|
||||
var 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
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('throws when file has invalid contents', function(done) {
|
||||
expect(loadObj(objInvalidContentsPath, options), done).toRejectWith(RuntimeError);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user