"use strict"; const Cesium = require("cesium"); const fsExtra = require("fs-extra"); const loadMtl = require("../../lib/loadMtl"); const loadTexture = require("../../lib/loadTexture"); const obj2gltf = require("../../lib/obj2gltf"); const clone = Cesium.clone; const coloredMaterialPath = "specs/data/box/box.mtl"; const texturedMaterialPath = "specs/data/box-complex-material/box-complex-material.mtl"; const texturedWithOptionsMaterialPath = "specs/data/box-texture-options/box-texture-options.mtl"; const multipleMaterialsPath = "specs/data/box-multiple-materials/box-multiple-materials.mtl"; const externalMaterialPath = "specs/data/box-external-resources/box-external-resources.mtl"; const resourcesInRootMaterialPath = "specs/data/box-resources-in-root/box-resources-in-root.mtl"; const externalInRootMaterialPath = "specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl"; const transparentMaterialPath = "specs/data/box-transparent/box-transparent.mtl"; const sharedTexturesMaterialPath = "specs/data/box-shared-textures/box-shared-textures.mtl"; const sharedTexturesMaterial2Path = "specs/data/box-shared-textures-2/box-shared-textures-2.mtl"; const diffuseTexturePath = "specs/data/box-textured/cesium.png"; const transparentDiffuseTexturePath = "specs/data/box-complex-material/diffuse.png"; const alphaTexturePath = "specs/data/box-complex-material-alpha/alpha.png"; const ambientTexturePath = "specs/data/box-complex-material/ambient.gif"; const normalTexturePath = "specs/data/box-complex-material/bump.png"; const emissiveTexturePath = "specs/data/box-complex-material/emission.jpg"; const specularTexturePath = "specs/data/box-complex-material/specular.jpeg"; const specularShininessTexturePath = "specs/data/box-complex-material/shininess.png"; let diffuseTexture; let transparentDiffuseTexture; let alphaTexture; let ambientTexture; let normalTexture; let emissiveTexture; let specularTexture; let specularShininessTexture; const checkTransparencyOptions = { checkTransparency: true, }; const decodeOptions = { decode: true, }; let options; describe("loadMtl", () => { beforeAll(async () => { diffuseTexture = await loadTexture(diffuseTexturePath, decodeOptions); transparentDiffuseTexture = await loadTexture( transparentDiffuseTexturePath, checkTransparencyOptions, ); alphaTexture = await loadTexture(alphaTexturePath, decodeOptions); ambientTexture = await loadTexture(ambientTexturePath); normalTexture = await loadTexture(normalTexturePath); emissiveTexture = await loadTexture(emissiveTexturePath); specularTexture = await loadTexture(specularTexturePath, decodeOptions); specularShininessTexture = await loadTexture( specularShininessTexturePath, decodeOptions, ); }); beforeEach(() => { options = clone(obj2gltf.defaults); options.overridingTextures = {}; options.logger = () => {}; }); it("loads mtl", async () => { options.metallicRoughness = true; const materials = await loadMtl(coloredMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeUndefined(); expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(pbr.baseColorFactor).toEqual([0.64, 0.64, 0.64, 1.0]); expect(pbr.metallicFactor).toBe(0.5); expect(pbr.roughnessFactor).toBe(96.078431); expect(material.name).toBe("Material"); expect(material.emissiveTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined(); expect(material.ambientTexture).toBeUndefined(); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.1]); expect(material.alphaMode).toBe("OPAQUE"); expect(material.doubleSided).toBe(false); }); it("loads mtl with textures", async () => { options.metallicRoughness = true; const materials = await loadMtl(texturedMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.metallicRoughnessTexture).toBeDefined(); expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]); expect(pbr.metallicFactor).toBe(1.0); expect(pbr.roughnessFactor).toBe(1.0); expect(material.name).toBe("Material"); expect(material.emissiveTexture).toBeDefined(); expect(material.normalTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined(); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("loads mtl with textures having options", async () => { options.metallicRoughness = true; const materials = await loadMtl(texturedWithOptionsMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.metallicRoughnessTexture).toBeDefined(); expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]); expect(pbr.metallicFactor).toBe(1.0); expect(pbr.roughnessFactor).toBe(1.0); expect(material.name).toBe("Material"); expect(material.emissiveTexture).toBeDefined(); expect(material.normalTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined(); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("loads mtl with multiple materials", async () => { options.metallicRoughness = true; const materials = await loadMtl(multipleMaterialsPath, options); expect(materials.length).toBe(3); 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("sets overriding textures", async () => { spyOn(fsExtra, "readFile").and.callThrough(); options.overridingTextures = { metallicRoughnessOcclusionTexture: alphaTexturePath, baseColorTexture: alphaTexturePath, emissiveTexture: emissiveTexturePath, }; const materials = await loadMtl(texturedMaterialPath, options); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture.name).toBe("alpha"); expect(pbr.metallicRoughnessTexture.name).toBe("alpha"); expect(material.emissiveTexture.name).toBe("emission"); expect(material.normalTexture.name).toBe("bump"); expect(fsExtra.readFile.calls.count()).toBe(3); }); it("loads texture outside of the mtl directory", async () => { const materials = await loadMtl(externalMaterialPath, options); const material = materials[0]; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.name).toBe("cesium"); }); it("does not load texture outside of the mtl directory when secure is true", async () => { const spy = jasmine.createSpy("logger"); options.logger = spy; options.secure = true; const materials = await loadMtl(externalMaterialPath, options); const material = materials[0]; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; expect(baseColorTexture).toBeUndefined(); expect( spy.calls .argsFor(0)[0] .indexOf( "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", ) >= 0, ).toBe(true); expect(spy.calls.argsFor(1)[0].indexOf("ENOENT") >= 0).toBe(true); expect( spy.calls.argsFor(2)[0].indexOf("Could not read texture file") >= 0, ).toBe(true); }); it("loads textures from root directory when the texture paths do not exist", async () => { const materials = await loadMtl(resourcesInRootMaterialPath, options); const material = materials[0]; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.name).toBe("cesium"); }); it("loads textures from root directory when texture is outside of the mtl directory and secure is true", async () => { options.secure = true; const materials = await loadMtl(externalInRootMaterialPath, options); const material = materials[0]; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.name).toBe("cesium"); }); it("alpha of 0.0 is treated as 1.0", async () => { const materials = await loadMtl(transparentMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeUndefined(); expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(pbr.baseColorFactor[3]).toEqual(1.0); expect(material.alphaMode).toBe("OPAQUE"); expect(material.doubleSided).toBe(false); }); it("ambient texture is ignored if it is the same as the diffuse texture", async () => { const materials = await loadMtl(sharedTexturesMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.occlusionTexture).toBeUndefined(); }); it("texture referenced by specular is decoded", async () => { const materials = await loadMtl(sharedTexturesMaterialPath, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture.pixels).toBeDefined(); expect(pbr.baseColorTexture.source).toBeDefined(); expect(pbr.metallicRoughnessTexture.pixels).toBeDefined(); expect(pbr.metallicRoughnessTexture.source).toBeUndefined(); }); it("texture referenced by diffuse and emissive is not decoded", async () => { const materials = await loadMtl(sharedTexturesMaterial2Path, options); expect(materials.length).toBe(1); const material = materials[0]; const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBe(material.emissiveTexture); expect(pbr.baseColorTexture.pixels).toBeUndefined(); expect(pbr.baseColorTexture.source).toBeDefined(); }); describe("metallicRoughness", () => { it("creates default material", () => { const material = loadMtl._createMaterial(undefined, options); const pbr = material.pbrMetallicRoughness; 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("creates material with textures", () => { options.metallicRoughness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, ambientTexture: ambientTexture, normalTexture: normalTexture, emissiveTexture: emissiveTexture, specularTexture: specularTexture, specularShininessTexture: specularShininessTexture, }, options, ); const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.metallicRoughnessTexture).toBeDefined(); expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 1.0]); expect(pbr.metallicFactor).toBe(1.0); expect(pbr.roughnessFactor).toBe(1.0); expect(material.emissiveTexture).toBeDefined(); expect(material.normalTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined(); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.alphaMode).toBe("OPAQUE"); expect(material.doubleSided).toBe(false); }); it("packs occlusion in metallic roughness texture", () => { options.metallicRoughness = true; options.packOcclusion = true; const material = loadMtl._createMaterial( { ambientTexture: alphaTexture, specularTexture: specularTexture, specularShininessTexture: specularShininessTexture, }, options, ); const pbr = material.pbrMetallicRoughness; expect(pbr.metallicRoughnessTexture).toBeDefined(); expect(pbr.metallicRoughnessTexture).toBe(material.occlusionTexture); }); it("does not create metallic roughness texture if decoded texture data is not available", () => { options.metallicRoughness = true; options.packOcclusion = true; const material = loadMtl._createMaterial( { ambientTexture: ambientTexture, // Is a .gif which can't be decoded specularTexture: specularTexture, specularShininessTexture: specularShininessTexture, }, options, ); const pbr = material.pbrMetallicRoughness; expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(material.occlusionTexture).toBeUndefined(); }); it("sets material for transparent diffuse texture", () => { options.metallicRoughness = true; const material = loadMtl._createMaterial( { diffuseTexture: transparentDiffuseTexture, }, options, ); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("packs alpha texture in base color texture", () => { options.metallicRoughness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, alphaTexture: alphaTexture, }, options, ); const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBeDefined(); let hasBlack = false; let hasWhite = false; const pixels = pbr.baseColorTexture.pixels; const pixelsLength = pixels.length / 4; for (let i = 0; i < pixelsLength; ++i) { const alpha = pixels[i * 4 + 3]; hasBlack = hasBlack || alpha === 0; hasWhite = hasWhite || alpha === 255; } expect(hasBlack).toBe(true); expect(hasWhite).toBe(true); expect(pbr.baseColorFactor[3]).toEqual(1); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("uses diffuse texture if diffuse and alpha are the same", () => { options.metallicRoughness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, alphaTexture: diffuseTexture, }, options, ); const pbr = material.pbrMetallicRoughness; expect(pbr.baseColorTexture).toBe(diffuseTexture); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("uses doubleSidedMaterial option", () => { options.metallicRoughness = true; options.doubleSidedMaterial = true; const material = loadMtl._createMaterial(undefined, options); expect(material.doubleSided).toBe(true); }); }); describe("specularGlossiness", () => { it("creates default material", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial(undefined, options); const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; expect(pbr.diffuseTexture).toBeUndefined(); expect(pbr.specularGlossinessTexture).toBeUndefined(); expect(pbr.diffuseFactor).toEqual([0.5, 0.5, 0.5, 1.0]); expect(pbr.specularFactor).toEqual([0.0, 0.0, 0.0]); // No specular color expect(pbr.glossinessFactor).toEqual(0.0); // Rough surface expect(material.emissiveTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined(); expect(material.occlusionTexture).toBeUndefined(); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]); expect(material.alphaMode).toBe("OPAQUE"); expect(material.doubleSided).toBe(false); }); it("creates material with textures", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, ambientTexture: ambientTexture, normalTexture: normalTexture, emissiveTexture: emissiveTexture, specularTexture: specularTexture, specularShininessTexture: specularShininessTexture, }, options, ); const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; expect(pbr.diffuseTexture).toBeDefined(); expect(pbr.specularGlossinessTexture).toBeDefined(); expect(pbr.diffuseFactor).toEqual([1.0, 1.0, 1.0, 1.0]); expect(pbr.specularFactor).toEqual([1.0, 1.0, 1.0]); expect(pbr.glossinessFactor).toEqual(1.0); expect(material.emissiveTexture).toBeDefined(); expect(material.normalTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined(); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.alphaMode).toBe("OPAQUE"); expect(material.doubleSided).toBe(false); }); it("does not create specular glossiness texture if decoded texture data is not available", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial( { specularTexture: ambientTexture, // Is a .gif which can't be decoded specularShininessTexture: specularShininessTexture, }, options, ); const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; expect(pbr.specularGlossinessTexture).toBeUndefined(); }); it("sets material for transparent diffuse texture", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial( { diffuseTexture: transparentDiffuseTexture, }, options, ); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("packs alpha texture in diffuse texture", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, alphaTexture: alphaTexture, }, options, ); const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; expect(pbr.diffuseTexture).toBeDefined(); let hasBlack = false; let hasWhite = false; const pixels = pbr.diffuseTexture.pixels; const pixelsLength = pixels.length / 4; for (let i = 0; i < pixelsLength; ++i) { const alpha = pixels[i * 4 + 3]; hasBlack = hasBlack || alpha === 0; hasWhite = hasWhite || alpha === 255; } expect(hasBlack).toBe(true); expect(hasWhite).toBe(true); expect(pbr.diffuseFactor[3]).toEqual(1); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("uses diffuse texture if diffuse and alpha are the same", () => { options.specularGlossiness = true; const material = loadMtl._createMaterial( { diffuseTexture: diffuseTexture, alphaTexture: diffuseTexture, }, options, ); const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; expect(pbr.diffuseTexture).toEqual(diffuseTexture); expect(material.alphaMode).toBe("BLEND"); expect(material.doubleSided).toBe(true); }); it("uses doubleSidedMaterial option", () => { options.specularGlossiness = true; options.doubleSidedMaterial = true; const material = loadMtl._createMaterial(undefined, options); expect(material.doubleSided).toBe(true); }); }); });