mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-27 10:30:16 -05:00
289 lines
11 KiB
JavaScript
289 lines
11 KiB
JavaScript
'use strict';
|
|
const Cesium = require('cesium');
|
|
const obj2gltf = require('../../lib/obj2gltf');
|
|
const createGltf = require('../../lib/createGltf');
|
|
const loadObj = require('../../lib/loadObj');
|
|
const { getDefaultMaterial } = require('../../lib/loadMtl');
|
|
|
|
const clone = Cesium.clone;
|
|
const defined = Cesium.defined;
|
|
const WebGLConstants = Cesium.WebGLConstants;
|
|
|
|
const boxObjPath = 'specs/data/box/box.obj';
|
|
const groupObjPath = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj';
|
|
const complexObjPath = 'specs/data/box-complex-material/box-complex-material.obj';
|
|
const noMaterialsObjPath = 'specs/data/box-no-materials/box-no-materials.obj';
|
|
const mixedAttributesObjPath = 'specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj';
|
|
|
|
let options;
|
|
|
|
describe('createGltf', () => {
|
|
let boxObjData;
|
|
let groupObjData;
|
|
let complexObjData;
|
|
let noMaterialsObjData;
|
|
let mixedAttributesObjData;
|
|
|
|
beforeEach(async () => {
|
|
options = clone(obj2gltf.defaults);
|
|
options.overridingTextures = {};
|
|
options.logger = () => {};
|
|
|
|
boxObjData = await loadObj(boxObjPath, options);
|
|
groupObjData = await loadObj(groupObjPath, options);
|
|
complexObjData = await loadObj(complexObjPath, options);
|
|
noMaterialsObjData = await loadObj(noMaterialsObjPath, options);
|
|
mixedAttributesObjData = await loadObj(mixedAttributesObjPath, options);
|
|
});
|
|
|
|
it('simple gltf', () => {
|
|
const gltf = createGltf(boxObjData, options);
|
|
|
|
expect(gltf.materials.length).toBe(1);
|
|
expect(gltf.scene).toBe(0);
|
|
expect(gltf.scenes[0].nodes[0]).toBe(0);
|
|
expect(gltf.nodes.length).toBe(1);
|
|
expect(gltf.meshes.length).toBe(1);
|
|
|
|
const primitives = gltf.meshes[0].primitives;
|
|
const primitive = primitives[0];
|
|
const attributes = primitive.attributes;
|
|
const positionAccessor = gltf.accessors[attributes.POSITION];
|
|
const normalAccessor = gltf.accessors[attributes.NORMAL];
|
|
const uvAccessor = gltf.accessors[attributes.TEXCOORD_0];
|
|
const indexAccessor = gltf.accessors[primitive.indices];
|
|
|
|
expect(primitives.length).toBe(1);
|
|
expect(positionAccessor.count).toBe(24);
|
|
expect(normalAccessor.count).toBe(24);
|
|
expect(uvAccessor.count).toBe(24);
|
|
expect(indexAccessor.count).toBe(36);
|
|
});
|
|
|
|
it('does not combine buffers when that buffer would exceed the Node buffer size limit', () => {
|
|
spyOn(createGltf, '_getBufferMaxByteLength').and.returnValue(0);
|
|
const clonedOptions = clone(options, true);
|
|
clonedOptions.separate = true;
|
|
|
|
const gltf = createGltf(boxObjData, clonedOptions);
|
|
expect(gltf.accessors.length).toBe(4);
|
|
expect(gltf.buffers.length).toBe(4);
|
|
expect(gltf.bufferViews.length).toBe(4);
|
|
|
|
const length = gltf.buffers.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
const accessor = gltf.accessors[i];
|
|
const bufferView = gltf.bufferViews[i];
|
|
const buffer = gltf.buffers[i];
|
|
expect(accessor.bufferView).toBe(i);
|
|
expect(accessor.byteOffset).toBe(0);
|
|
expect(bufferView.buffer).toBe(i);
|
|
expect(bufferView.byteOffset).toBe(0);
|
|
expect(bufferView.byteLength).toBe(buffer.byteLength);
|
|
}
|
|
});
|
|
|
|
it('multiple nodes, meshes, and primitives', () => {
|
|
const gltf = createGltf(groupObjData, options);
|
|
|
|
expect(gltf.materials.length).toBe(3);
|
|
expect(gltf.scene).toBe(0);
|
|
expect(gltf.scenes[0].nodes[0]).toBe(0);
|
|
expect(gltf.nodes.length).toBe(4);
|
|
expect(gltf.nodes[0].mesh).toBeUndefined();
|
|
expect(gltf.nodes[0].children.length).toBe(3);
|
|
expect(gltf.meshes.length).toBe(3);
|
|
|
|
// Check for two primitives in each mesh
|
|
const length = gltf.meshes.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
const mesh = gltf.meshes[i];
|
|
expect(mesh.primitives.length).toBe(2);
|
|
}
|
|
});
|
|
|
|
it('multiple textures', () => {
|
|
const gltf = createGltf(complexObjData, options);
|
|
const material = gltf.materials[0];
|
|
const pbr = material.pbrMetallicRoughness;
|
|
const textures = [pbr.metallicRoughnessTexture, pbr.baseColorTexture, material.emissiveTexture, material.normalTexture, material.occlusionTexture];
|
|
expect(textures.map((texture) => {
|
|
return texture.index;
|
|
}).sort()).toEqual([0, 1, 2, 3, 4]);
|
|
expect(gltf.samplers[0]).toBeDefined();
|
|
});
|
|
|
|
it('creates default material', () => {
|
|
const gltf = createGltf(noMaterialsObjData, options);
|
|
const material = gltf.materials[0];
|
|
const pbr = material.pbrMetallicRoughness;
|
|
expect(material.name).toBe('default');
|
|
expect(pbr.baseColorTexture).toBeUndefined();
|
|
expect(pbr.metallicRoughnessTexture).toBeUndefined();
|
|
expect(pbr.baseColorFactor).toEqual([0.5, 0.5, 0.5, 1.0]);
|
|
expect(pbr.metallicFactor).toBe(0.0); // No metallic
|
|
expect(pbr.roughnessFactor).toBe(1.0); // Fully rough
|
|
expect(material.emissiveTexture).toBeUndefined();
|
|
expect(material.normalTexture).toBeUndefined();
|
|
expect(material.ambientTexture).toBeUndefined();
|
|
expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]);
|
|
expect(material.alphaMode).toBe('OPAQUE');
|
|
expect(material.doubleSided).toBe(false);
|
|
});
|
|
|
|
it('adds KHR_materials_pbrSpecularGlossiness extension when specularGlossiness is set', () => {
|
|
options.specularGlossiness = true;
|
|
const gltf = createGltf(noMaterialsObjData, options);
|
|
expect(gltf.extensionsUsed).toEqual(['KHR_materials_pbrSpecularGlossiness']);
|
|
expect(gltf.extensionsRequired).toEqual(['KHR_materials_pbrSpecularGlossiness']);
|
|
});
|
|
|
|
it('adds KHR_materials_unlit extension when unlit is set', () => {
|
|
options.unlit = true;
|
|
const gltf = createGltf(noMaterialsObjData, options);
|
|
expect(gltf.extensionsUsed).toEqual(['KHR_materials_unlit']);
|
|
expect(gltf.extensionsRequired).toEqual(['KHR_materials_unlit']);
|
|
});
|
|
|
|
it('runs without normals', () => {
|
|
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
|
|
|
|
const gltf = createGltf(boxObjData, options);
|
|
const attributes = gltf.meshes[0].primitives[0].attributes;
|
|
expect(attributes.POSITION).toBeDefined();
|
|
expect(attributes.NORMAL).toBeUndefined();
|
|
expect(attributes.TEXCOORD_0).toBeDefined();
|
|
});
|
|
|
|
it('runs without uvs', () => {
|
|
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
|
|
|
|
const gltf = createGltf(boxObjData, options);
|
|
const 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', () => {
|
|
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
|
|
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
|
|
|
|
const gltf = createGltf(boxObjData, options);
|
|
const attributes = gltf.meshes[0].primitives[0].attributes;
|
|
expect(attributes.POSITION).toBeDefined();
|
|
expect(attributes.NORMAL).toBeUndefined();
|
|
expect(attributes.TEXCOORD_0).toBeUndefined();
|
|
});
|
|
|
|
it('splits incompatible materials', () => {
|
|
const gltf = createGltf(mixedAttributesObjData, options);
|
|
const materials = gltf.materials;
|
|
const meshes = gltf.meshes;
|
|
|
|
const referenceMaterial = mixedAttributesObjData.materials[0];
|
|
delete referenceMaterial.name;
|
|
referenceMaterial.pbrMetallicRoughness.baseColorTexture = {
|
|
index : 0
|
|
};
|
|
|
|
const referenceMaterialNoTextures = clone(referenceMaterial, true);
|
|
referenceMaterialNoTextures.pbrMetallicRoughness.baseColorTexture = undefined;
|
|
|
|
const defaultMaterial = getDefaultMaterial(options);
|
|
delete defaultMaterial.name;
|
|
|
|
const materialNames = materials.map((material) => {
|
|
const 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
|
|
const meshesLength = meshes.length;
|
|
for (let i = 0; i < meshesLength; ++i) {
|
|
const mesh = meshes[i];
|
|
const primitives = mesh.primitives;
|
|
const primitivesLength = primitives.length;
|
|
for (let j = 0; j < primitivesLength; ++j) {
|
|
const primitive = primitives[j];
|
|
const material = materials[primitive.material];
|
|
if (!defined(primitive.attributes.TEXCOORD_0)) {
|
|
expect(material.pbrMetallicRoughness.baseColorTexture).toBeUndefined();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function expandObjData(objData, duplicatesLength) {
|
|
const primitive = objData.nodes[0].meshes[0].primitives[0];
|
|
const indices = primitive.indices;
|
|
const positions = primitive.positions;
|
|
const normals = primitive.normals;
|
|
const uvs = primitive.uvs;
|
|
|
|
const indicesLength = indices.length;
|
|
const vertexCount = positions.length / 3;
|
|
|
|
for (let i = 1; i < duplicatesLength; ++i) {
|
|
for (let j = 0; j < vertexCount; ++j) {
|
|
positions.push(0.0);
|
|
positions.push(0.0);
|
|
positions.push(0.0);
|
|
normals.push(0.0);
|
|
normals.push(0.0);
|
|
normals.push(0.0);
|
|
uvs.push(0.0);
|
|
uvs.push(0.0);
|
|
}
|
|
for (let k = 0; k < indicesLength; ++k) {
|
|
indices.push(indices.get(k) + vertexCount * i);
|
|
}
|
|
}
|
|
}
|
|
|
|
it('detects need to use uint32 indices', () => {
|
|
expandObjData(boxObjData, 2731); // Right above 65536 limit
|
|
let primitive = boxObjData.nodes[0].meshes[0].primitives[0];
|
|
const indicesLength = primitive.indices.length;
|
|
const vertexCount = primitive.positions.length / 3;
|
|
|
|
const gltf = createGltf(boxObjData, options);
|
|
primitive = gltf.meshes[0].primitives[0];
|
|
const indicesAccessor = gltf.accessors[primitive.indices];
|
|
expect(indicesAccessor.count).toBe(indicesLength);
|
|
expect(indicesAccessor.max[0]).toBe(vertexCount - 1);
|
|
expect(indicesAccessor.componentType).toBe(WebGLConstants.UNSIGNED_INT);
|
|
|
|
const positionAccessor = gltf.accessors[primitive.attributes.POSITION];
|
|
expect(positionAccessor.count).toBe(vertexCount);
|
|
});
|
|
});
|