import {
|
ComponentDatatype,
|
DracoLoader,
|
GltfBufferViewLoader,
|
GltfDracoLoader,
|
Resource,
|
ResourceCache,
|
ResourceLoaderState,
|
when,
|
} from "../../Source/Cesium.js";
|
import createScene from "../createScene.js";
|
import loaderProcess from "../loaderProcess.js";
|
import pollToPromise from "../pollToPromise.js";
|
import waitForLoaderProcess from "../waitForLoaderProcess.js";
|
|
describe("Scene/GltfDracoLoader", function () {
|
var bufferTypedArray = new Uint8Array([1, 3, 7, 15, 31, 63, 127, 255]);
|
var bufferArrayBuffer = bufferTypedArray.buffer;
|
|
var decodedPositions = new Uint16Array([0, 0, 0, 65535, 65535, 65535, 0, 65535, 0]); // prettier-ignore
|
var decodedNormals = new Uint8Array([0, 255, 128, 128, 255, 0]);
|
var decodedIndices = new Uint16Array([0, 1, 2]);
|
|
var gltfUri = "https://example.com/model.glb";
|
var gltfResource = new Resource({
|
url: gltfUri,
|
});
|
|
var decodeDracoResults = {
|
indexArray: {
|
typedArray: decodedIndices,
|
numberOfIndices: decodedIndices.length,
|
},
|
attributeData: {
|
POSITION: {
|
array: decodedPositions,
|
data: {
|
byteOffset: 0,
|
byteStride: 6,
|
componentDatatype: ComponentDatatype.UNSIGNED_SHORT,
|
componentsPerAttribute: 3,
|
normalized: false,
|
quantization: {
|
octEncoded: false,
|
quantizationBits: 14,
|
minValues: [-1.0, -1.0, -1.0],
|
range: 2.0,
|
},
|
},
|
},
|
NORMAL: {
|
array: decodedNormals,
|
data: {
|
byteOffset: 0,
|
byteStride: 2,
|
componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
|
componentsPerAttribute: 2,
|
normalized: false,
|
quantization: {
|
octEncoded: true,
|
quantizationBits: 10,
|
},
|
},
|
},
|
},
|
};
|
|
var gltfDraco = {
|
buffers: [
|
{
|
uri: "external.bin",
|
byteLength: 8,
|
},
|
],
|
bufferViews: [
|
{
|
buffer: 0,
|
byteOffset: 4,
|
byteLength: 4,
|
},
|
],
|
accessors: [
|
{
|
componentType: 5126,
|
count: 3,
|
max: [-1.0, -1.0, -1.0],
|
min: [1.0, 1.0, 1.0],
|
type: "VEC3",
|
},
|
{
|
componentType: 5126,
|
count: 3,
|
type: "VEC3",
|
},
|
{
|
componentType: 5123,
|
count: 3,
|
type: "SCALAR",
|
},
|
],
|
meshes: [
|
{
|
primitives: [
|
{
|
attributes: {
|
POSITION: 0,
|
NORMAL: 1,
|
},
|
indices: 2,
|
extensions: {
|
KHR_draco_mesh_compression: {
|
bufferView: 0,
|
attributes: {
|
POSITION: 0,
|
NORMAL: 1,
|
},
|
},
|
},
|
},
|
],
|
},
|
],
|
};
|
|
var dracoExtension =
|
gltfDraco.meshes[0].primitives[0].extensions.KHR_draco_mesh_compression;
|
|
var scene;
|
|
beforeAll(function () {
|
scene = createScene();
|
});
|
|
afterAll(function () {
|
scene.destroyForSpecs();
|
});
|
|
afterEach(function () {
|
ResourceCache.clearForSpecs();
|
});
|
|
it("throws if resourceCache is undefined", function () {
|
expect(function () {
|
return new GltfDracoLoader({
|
resourceCache: undefined,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if gltf is undefined", function () {
|
expect(function () {
|
return new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: undefined,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if draco is undefined", function () {
|
expect(function () {
|
return new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: undefined,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if gltfResource is undefined", function () {
|
expect(function () {
|
return new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: undefined,
|
baseResource: gltfResource,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if baseResource is undefined", function () {
|
expect(function () {
|
return new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: undefined,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("rejects promise if buffer view fails to load", function () {
|
var error = new Error("404 Not Found");
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
when.reject(error)
|
);
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
dracoLoader.load();
|
|
return dracoLoader.promise
|
.then(function (dracoLoader) {
|
fail();
|
})
|
.otherwise(function (runtimeError) {
|
expect(runtimeError.message).toBe(
|
"Failed to load Draco\nFailed to load buffer view\nFailed to load external buffer: https://example.com/external.bin\n404 Not Found"
|
);
|
});
|
});
|
|
it("rejects promise if draco decoding fails", function () {
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
when.resolve(bufferArrayBuffer)
|
);
|
|
var error = new Error("Draco decode failed");
|
spyOn(DracoLoader, "decodeBufferView").and.returnValue(when.reject(error));
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
dracoLoader.load();
|
|
return waitForLoaderProcess(dracoLoader, scene)
|
.then(function (dracoLoader) {
|
fail();
|
})
|
.otherwise(function (runtimeError) {
|
expect(runtimeError.message).toBe(
|
"Failed to load Draco\nDraco decode failed"
|
);
|
});
|
});
|
|
it("loads draco", function () {
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
when.resolve(bufferArrayBuffer)
|
);
|
|
// Simulate decodeBufferView not being ready for a few frames
|
// Then simulate the promise not resolving for another few frames
|
var deferredPromise = when.defer();
|
var decodeBufferViewCallsTotal = 3;
|
var decodeBufferViewCallsCount = 0;
|
var processCallsTotal = 6;
|
var processCallsCount = 0;
|
spyOn(DracoLoader, "decodeBufferView").and.callFake(function () {
|
if (decodeBufferViewCallsCount++ === decodeBufferViewCallsTotal) {
|
return deferredPromise.promise;
|
}
|
return undefined;
|
});
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
dracoLoader.load();
|
|
return pollToPromise(function () {
|
loaderProcess(dracoLoader, scene);
|
if (processCallsCount++ === processCallsTotal) {
|
deferredPromise.resolve(decodeDracoResults);
|
}
|
return (
|
dracoLoader._state === ResourceLoaderState.READY ||
|
dracoLoader._state === ResourceLoaderState.FAILED
|
);
|
}).then(function () {
|
return dracoLoader.promise.then(function (dracoLoader) {
|
loaderProcess(dracoLoader, scene); // Check that calling process after load doesn't break anything
|
expect(dracoLoader.decodedData.indices).toBe(
|
decodeDracoResults.indexArray
|
);
|
expect(dracoLoader.decodedData.vertexAttributes).toBe(
|
decodeDracoResults.attributeData
|
);
|
});
|
});
|
});
|
|
it("destroys draco loader", function () {
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
when.resolve(bufferArrayBuffer)
|
);
|
|
spyOn(DracoLoader, "decodeBufferView").and.returnValue(
|
when.resolve(decodeDracoResults)
|
);
|
|
var unloadBufferView = spyOn(
|
GltfBufferViewLoader.prototype,
|
"unload"
|
).and.callThrough();
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
dracoLoader.load();
|
|
return waitForLoaderProcess(dracoLoader, scene).then(function (
|
dracoLoader
|
) {
|
expect(dracoLoader.decodedData).toBeDefined();
|
expect(dracoLoader.isDestroyed()).toBe(false);
|
|
dracoLoader.destroy();
|
|
expect(dracoLoader.decodedData).not.toBeDefined();
|
expect(dracoLoader.isDestroyed()).toBe(true);
|
expect(unloadBufferView).toHaveBeenCalled();
|
});
|
});
|
|
function resolveBufferViewAfterDestroy(reject) {
|
var deferredPromise = when.defer();
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
deferredPromise.promise
|
);
|
|
spyOn(DracoLoader, "decodeBufferView").and.returnValue(
|
when.resolve(decodeDracoResults)
|
);
|
|
// Load a copy of the buffer view into the cache so that the buffer view
|
// promise resolves even if the draco loader is destroyed
|
var bufferViewLoaderCopy = ResourceCache.loadBufferView({
|
gltf: gltfDraco,
|
bufferViewId: 0,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
expect(dracoLoader.decodedData).not.toBeDefined();
|
|
dracoLoader.load();
|
dracoLoader.destroy();
|
|
if (reject) {
|
deferredPromise.reject(new Error());
|
} else {
|
deferredPromise.resolve(bufferArrayBuffer);
|
}
|
|
expect(dracoLoader.decodedData).not.toBeDefined();
|
expect(dracoLoader.isDestroyed()).toBe(true);
|
|
ResourceCache.unload(bufferViewLoaderCopy);
|
}
|
|
it("handles resolving buffer view after destroy", function () {
|
resolveBufferViewAfterDestroy(false);
|
});
|
|
it("handles rejecting buffer view after destroy", function () {
|
resolveBufferViewAfterDestroy(true);
|
});
|
|
function resolveDracoAfterDestroy(reject) {
|
spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
|
when.resolve(bufferArrayBuffer)
|
);
|
|
var deferredPromise = when.defer();
|
var decodeBufferView = spyOn(DracoLoader, "decodeBufferView").and.callFake(
|
function () {
|
return deferredPromise.promise;
|
}
|
);
|
|
var dracoLoader = new GltfDracoLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfDraco,
|
draco: dracoExtension,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
expect(dracoLoader.decodedData).not.toBeDefined();
|
|
dracoLoader.load();
|
loaderProcess(dracoLoader, scene);
|
expect(decodeBufferView).toHaveBeenCalled(); // Make sure the decode actually starts
|
|
dracoLoader.destroy();
|
|
if (reject) {
|
deferredPromise.reject(new Error());
|
} else {
|
deferredPromise.resolve(decodeDracoResults);
|
}
|
|
expect(dracoLoader.decodedData).not.toBeDefined();
|
expect(dracoLoader.isDestroyed()).toBe(true);
|
}
|
|
it("handles resolving draco after destroy", function () {
|
resolveDracoAfterDestroy(false);
|
});
|
|
it("handles rejecting draco after destroy", function () {
|
resolveDracoAfterDestroy(true);
|
});
|
});
|