You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Cesium-Prequel/Specs/Scene/ModelInstanceCollectionSpec.js

737 lines
22 KiB
JavaScript

import { BoundingSphere } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { defaultValue } from "../../Source/Cesium.js";
import { HeadingPitchRange } from "../../Source/Cesium.js";
import { HeadingPitchRoll } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";
import { Matrix4 } from "../../Source/Cesium.js";
import { Axis } from "../../Source/Cesium.js";
import { PrimitiveType } from "../../Source/Cesium.js";
import { Resource } from "../../Source/Cesium.js";
import { Transforms } from "../../Source/Cesium.js";
import { Model } from "../../Source/Cesium.js";
import { ModelInstanceCollection } from "../../Source/Cesium.js";
import { ShadowMode } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";
import { when } from "../../Source/Cesium.js";
describe(
"Scene/ModelInstanceCollection",
function () {
var boxUrl = "./Data/Models/Box/CesiumBoxTest.gltf";
var cesiumAirUrl = "./Data/Models/CesiumAir/Cesium_Air.gltf";
var riggedFigureUrl =
"./Data/Models/rigged-figure-test/rigged-figure-test.gltf";
var movingBoxUrl = "./Data/Models/moving-box/moving-box.gltf";
var scene;
var boxRadius;
beforeAll(function () {
scene = createScene();
return loadModel(boxUrl).then(function (model) {
boxRadius = model.boundingSphere.radius;
scene.primitives.remove(model);
});
});
beforeEach(function () {
scene.morphTo3D(0.0);
});
afterAll(function () {
scene.destroyForSpecs();
});
afterEach(function () {
scene.primitives.removeAll();
});
function loadModel(url) {
var model = scene.primitives.add(
Model.fromGltf({
url: url,
})
);
return pollToPromise(function () {
// Render scene to progressively load the model
scene.renderForSpecs();
return model.ready;
}).then(function () {
return model;
});
}
function loadCollection(options) {
var collection = scene.primitives.add(
new ModelInstanceCollection(options)
);
return pollToPromise(function () {
// Render scene to progressively load the model
scene.renderForSpecs();
return collection.ready;
}).then(function () {
zoomTo(collection, 0);
return collection;
});
}
function createInstances(count, heightOffset) {
heightOffset = defaultValue(heightOffset, 0.0);
var spacing = 20.0;
var centerLongitude = -123.0744619;
var centerLatitude = 44.0503706;
var height = 5000.0 + heightOffset;
var instances = [];
for (var i = 0; i < count; ++i) {
var instanceHeight = height + spacing * i;
var position = Cartesian3.fromDegrees(
centerLongitude,
centerLatitude,
instanceHeight
);
var heading = Math.PI / 2.0;
var pitch = 0.0;
var roll = 0.0;
var hpr = new HeadingPitchRoll(heading, pitch, roll);
var modelMatrix = Transforms.headingPitchRollToFixedFrame(
position,
hpr
);
instances.push({
modelMatrix: modelMatrix,
});
}
return instances;
}
function getBoundingSphere(instances, modelRadius) {
var length = instances.length;
var points = new Array(length);
for (var i = 0; i < length; ++i) {
var translation = new Cartesian3();
Matrix4.getTranslation(instances[i].modelMatrix, translation);
points[i] = translation;
}
var boundingSphere = new BoundingSphere();
BoundingSphere.fromPoints(points, boundingSphere);
boundingSphere.radius += modelRadius;
return boundingSphere;
}
var centerScratch = new Cartesian3();
function zoomTo(collection, instance) {
var center = Matrix4.getTranslation(
collection._instances[instance].modelMatrix,
centerScratch
);
var camera = scene.camera;
camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, 10.0));
}
function expectRender(collection, expectColor, time) {
expectColor = defaultValue(expectColor, true);
collection.show = false;
expect(scene).toRender([0, 0, 0, 255]);
collection.show = true;
// Verify each instance
var length = collection.length;
for (var i = 0; i < length; ++i) {
zoomTo(collection, i);
if (expectColor) {
expect({
scene: scene,
time: time,
}).notToRender([0, 0, 0, 255]);
} else {
expect({
scene: scene,
time: time,
}).toRender([0, 0, 0, 255]);
}
}
}
function verifyPickedInstance(collection, instanceId) {
return function (result) {
expect(result.primitive).toBe(collection);
expect(result.modelMatrix).toBeDefined();
expect(result.instanceId).toBe(instanceId);
expect(result.model).toBe(collection._model);
};
}
function expectPick(collection) {
collection.show = false;
expect(scene).notToPick();
collection.show = true;
// Verify each instance
var length = collection.length;
for (var i = 0; i < length; ++i) {
zoomTo(collection, i);
expect(scene).toPickAndCall(verifyPickedInstance(collection, i));
}
}
it("throws if neither options.gltf nor options.url are provided", function () {
expect(function () {
return new ModelInstanceCollection();
}).toThrowDeveloperError();
});
it("throws when both options.gltf and options.url are provided", function () {
return loadModel(boxUrl).then(function (model) {
expect(function () {
return new ModelInstanceCollection({
url: boxUrl,
gltf: model.gltf,
});
}).toThrowDeveloperError();
});
});
it("sets properties", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expect(collection.ready).toEqual(true);
expect(collection.show).toEqual(true);
expect(collection.allowPicking).toEqual(true);
expect(collection.length).toEqual(4);
expect(collection.debugShowBoundingVolume).toEqual(false);
expect(collection.debugWireframe).toEqual(false);
expect(collection._dynamic).toEqual(false);
expect(collection._cull).toEqual(true);
expect(collection._model).toBeDefined();
expect(collection._model.ready).toEqual(true);
if (collection._instancingSupported) {
expect(collection._model.cacheKey).toEqual(boxUrl + "#instanced");
}
});
});
it("renders from url", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
});
});
it("renders from Resource", function () {
var resource = new Resource({
url: boxUrl,
});
return loadCollection({
url: resource,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
});
});
it("renders from gltf", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
});
});
it("resolves readyPromise", function () {
var collection = scene.primitives.add(
new ModelInstanceCollection({
url: boxUrl,
instances: createInstances(4),
})
);
return pollToPromise(function () {
scene.renderForSpecs();
return collection._model.ready;
}).then(function () {
scene.renderForSpecs();
scene.renderForSpecs();
return collection.readyPromise.then(function (collection) {
expect(collection.ready).toEqual(true);
});
});
});
it("rejects readyPromise on error", function () {
// Expect promise to be rejected in Model, then in ModelInstanceCollection.
var collection = scene.primitives.add(
new ModelInstanceCollection({
url: "invalid.gltf",
instances: createInstances(4),
})
);
collection.update(scene.frameState);
return collection.readyPromise
.then(function (collection) {
fail("should not resolve");
})
.otherwise(function (error) {
expect(collection.ready).toEqual(false);
});
});
it("renders one instance", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(1),
}).then(function (collection) {
expectRender(collection);
});
});
it("renders zero instances", function () {
var collection = scene.primitives.add(
new ModelInstanceCollection({
url: boxUrl,
instances: createInstances(0),
})
);
// Collection never reaches the ready state due to returning early
for (var i = 0; i < 10; ++i) {
expectRender(collection, false);
expect(collection.ready).toBe(false);
}
});
it("renders 100 instances", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(100),
}).then(function (collection) {
expectRender(collection);
});
});
it("renders cesiumAir", function () {
return loadCollection({
url: cesiumAirUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
});
});
it("renders rigged figure", function () {
return loadCollection({
url: riggedFigureUrl,
instances: createInstances(4),
}).then(function (collection) {
var instances = collection._instances;
// Rotate instances to account for empty space between legs of rigged model.
for (var i = 0; i < instances.length; ++i) {
instances[i].modelMatrix = Matrix4.multiply(
instances[i].modelMatrix,
Axis.Y_UP_TO_X_UP,
new Matrix4()
);
}
expectRender(collection);
});
});
it("renders when instancing is disabled", function () {
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("renders when dynamic is true", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
dynamic: true,
}).then(function (collection) {
expectRender(collection);
});
});
it("verify bounding volume", function () {
var instances = createInstances(4);
return loadCollection({
url: boxUrl,
instances: instances,
}).then(function (collection) {
var boundingSphere = getBoundingSphere(instances, boxRadius);
expect(collection._boundingSphere.center).toEqual(
boundingSphere.center
);
expect(collection._boundingSphere.radius).toEqual(
boundingSphere.radius
);
});
});
it("renders bounding volume", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
collection.debugShowBoundingVolume = true;
expectRender(collection);
});
});
it("renders in wireframe", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
collection.debugWireframe = true;
scene.renderForSpecs();
expect(collection._drawCommands[0].primitiveType).toEqual(
PrimitiveType.LINES
);
});
});
it("renders with animations", function () {
// Test that all instances are being animated.
// The moving box is in view on frame 1 and out of view by frame 5.
return loadCollection({
url: movingBoxUrl,
instances: createInstances(4),
}).then(function (collection) {
collection.activeAnimations.addAll();
// Render when animation is in view
var time = JulianDate.now();
expectRender(collection, true, time);
// Render when animation is out of view
time = JulianDate.addSeconds(time, 0.1, new JulianDate());
expectRender(collection, false, time);
});
});
it("renders with animations when instancing is disabled", function () {
// Instance transforms are updated differently when instancing is disabled
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: movingBoxUrl,
instances: createInstances(4),
}).then(function (collection) {
collection.activeAnimations.addAll();
// Render when animation is in view
var time = JulianDate.now();
expectRender(collection, true, time);
// Render when animation is out of view
time = JulianDate.addSeconds(time, 0.1, new JulianDate());
expectRender(collection, false, time);
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("renders two model instance collections that use the same cache key", function () {
var collections = [];
var promises = [];
promises.push(
loadCollection({
url: boxUrl,
instances: createInstances(2),
}).then(function (collection) {
collections.push(collection);
})
);
promises.push(
loadCollection({
url: boxUrl,
instances: createInstances(2, 1000.0),
}).then(function (collection) {
collections.push(collection);
})
);
return when.all(promises).then(function () {
var resourcesFirst = collections[0]._model._rendererResources;
var resourcesSecond = collections[1]._model._rendererResources;
var name;
expect(collections[0]._model.cacheKey).toEqual(
collections[1]._model.cacheKey
);
zoomTo(collections[0], 0);
expectRender(collections[0]);
zoomTo(collections[1], 0);
expectRender(collections[1]);
// Check that buffers are equal
for (name in resourcesFirst.buffers) {
if (resourcesFirst.buffers.hasOwnProperty(name)) {
expect(resourcesFirst.buffers[name]).toEqual(
resourcesSecond.buffers[name]
);
}
}
// Check that programs are equal
for (name in resourcesFirst.programs) {
if (resourcesFirst.programs.hasOwnProperty(name)) {
expect(resourcesFirst.programs[name]).toEqual(
resourcesSecond.programs[name]
);
}
}
if (collections[0]._instancingSupported) {
// Check that vertex arrays are different, since each collection has a unique vertex buffer for instanced attributes.
for (name in resourcesFirst.vertexArrays) {
if (resourcesFirst.vertexArrays.hasOwnProperty(name)) {
expect(resourcesFirst.vertexArrays[name]).not.toEqual(
resourcesSecond.vertexArrays[name]
);
}
}
}
});
});
it("culls when out of view and cull is true", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
cull: true,
}).then(function (collection) {
scene.renderForSpecs();
expect(scene.frustumCommandsList.length).not.toEqual(0);
scene.camera.lookAt(
new Cartesian3(100000.0, 0.0, 0.0),
new HeadingPitchRange(0.0, 0.0, 10.0)
);
scene.renderForSpecs();
expect(scene.frustumCommandsList.length).toEqual(0);
});
});
it("does not cull when out of view and cull is false", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
cull: false,
}).then(function (collection) {
scene.renderForSpecs();
expect(scene.frustumCommandsList.length).not.toEqual(0);
scene.camera.lookAt(
new Cartesian3(100000.0, 0.0, 0.0),
new HeadingPitchRange(0.0, 0.0, 10.0)
);
scene.renderForSpecs();
expect(scene.frustumCommandsList.length).not.toEqual(0);
});
});
it("shadows", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
scene.renderForSpecs();
expect(collection._shadows).toBe(ShadowMode.ENABLED);
var drawCommand = collection._drawCommands[0];
expect(drawCommand.castShadows).toBe(true);
expect(drawCommand.receiveShadows).toBe(true);
collection.shadows = ShadowMode.DISABLED;
scene.renderForSpecs();
// Expect commands to have been recreated
drawCommand = collection._drawCommands[0];
expect(drawCommand.castShadows).toBe(false);
expect(drawCommand.receiveShadows).toBe(false);
});
});
it("picks", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectPick(collection);
});
});
it("picks when instancing is disabled", function () {
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectPick(collection);
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("moves instance", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expect(scene).toPickAndCall(function (result) {
var originalMatrix = result.modelMatrix;
result.modelMatrix = Matrix4.IDENTITY;
expect(scene).notToPick();
result.modelMatrix = originalMatrix;
expect(scene).toPickPrimitive(collection);
});
});
});
it("moves instance when instancing is disabled", function () {
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expect(scene).toPickAndCall(function (result) {
var originalMatrix = result.modelMatrix;
var originalRadius = collection._boundingSphere.radius;
result.modelMatrix = Matrix4.IDENTITY;
expect(scene).notToPick();
expect(collection._boundingSphere.radius).toBeGreaterThan(
originalRadius
);
result.modelMatrix = originalMatrix;
expect(scene).toPickPrimitive(collection);
});
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("renders in 2D", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
scene.morphTo2D(0.0);
expectRender(collection);
});
});
it("renders in 2D when instancing is disabled", function () {
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
scene.morphTo2D(0.0);
expectRender(collection);
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("renders in CV", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
scene.morphToColumbusView(0.0);
expectRender(collection);
});
});
it("renders in CV when instancing is disabled", function () {
// Disable extension
var instancedArrays = scene.context._instancedArrays;
scene.context._instancedArrays = undefined;
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expectRender(collection);
scene.morphToColumbusView(0.0);
expectRender(collection);
// Re-enable extension
scene.context._instancedArrays = instancedArrays;
});
});
it("does not render during morph", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
cull: false,
}).then(function () {
var commandList = scene.frameState.commandList;
scene.renderForSpecs();
expect(commandList.length).toBeGreaterThan(0);
scene.morphToColumbusView(1.0);
scene.renderForSpecs();
expect(commandList.length).toBe(0);
});
});
it("destroys", function () {
return loadCollection({
url: boxUrl,
instances: createInstances(4),
}).then(function (collection) {
expect(collection.isDestroyed()).toEqual(false);
scene.primitives.remove(collection);
expect(collection.isDestroyed()).toEqual(true);
});
});
},
"WebGL"
);