Merge remote-tracking branch 'origin' into polygon-culling-2d-fix

This commit is contained in:
Matt Schwartz 2025-07-15 15:52:17 -04:00
commit 2e827a8065
26 changed files with 332 additions and 169 deletions

26
.vscode/launch.json vendored
View File

@ -21,6 +21,32 @@
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
},
{
"request": "attach",
"name": "Attach to Karma",
"type": "chrome",
"port": 9333, // This is the remote debugging port specified in karma.conf.cjs
"webRoot": "${workspaceFolder}",
"timeout": 60000
},
{
"type": "node",
"request": "launch",
"name": "Launch Test Suite",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "test", "--", "--includeName", "${fileDirnameBasename}${/}${fileBasenameNoExtension}", "--debug"],
"console": "integratedTerminal"
}
],
"compounds": [
{
"name": "Launch Test Suite and Debug in VSCode",
"configurations": [
"Launch Test Suite",
"Attach to Karma"
]
}
]
}

View File

@ -1,12 +1,14 @@
# Change Log
## 1.132 - 2025-07-02
## 1.132 - 2025-08-01
### @cesium/engine
### Fixes :wrench:
- Fixes incorrect polygon culling in 2D scene mode [#1552](https://github.com/CesiumGS/cesium/issues/1552)
- Fixes material flashing when changing properties [#1640](https://github.com/CesiumGS/cesium/issues/1640), [12716](https://github.com/CesiumGS/cesium/issues/12716)
- Updated the type of many properties and functions of `Scene` to clarify that they may be `undefined`. For the full list check PR: [#12736](https://github.com/CesiumGS/cesium/pull/12736)
#### Additions :tada:

View File

@ -424,3 +424,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
- [Cody Butler](https://github.com/CodyBu)
- [Hiwen](https://github.com/Hiwen)
- [Matt Schwartz](https://github.com/mzschwartz5)
- [Pamela Augustine](https://github.com/pamelaAugustine)

View File

@ -16,7 +16,7 @@ All new code should have 100% code coverage and should pass all tests. Always ru
- [Run Only Non-WebGL Tests](#run-only-non-webgl-tests)
- [Run All Tests Against the Minified Release Version of CesiumJS](#run-all-tests-against-the-minified-release-version-of-cesiumjs)
- [Run a Single Test or Suite](#run-a-single-test-or-suite)
- [Using Browser Debugging Tools](#using-browser-debugging-tools)
- [Debugging Tests in the Browser or IDE](#debugging-tests-in-the-browser-or-ide)
- [Running the Tests in the Browser](#running-the-tests-in-the-browser)
- [Run All Tests](#run-all-tests)
- [Run with WebGL validation](#run-with-webgl-validation)
@ -122,13 +122,13 @@ Alternatively, test suites can be run from the command line with the `includeNam
`npm run test -- --includeName Cartesian2`
#### Using Browser Debugging Tools
#### Debugging Tests in the Browser or IDE
If it is helpful to step through a unit test in a browser debugger, run the tests with the `debug` flag:
If it is helpful to step through a unit test in a browser debugger or your IDE, run the tests with the `debug` flag:
`npm run test -- --debug`
The `--debug` flag will prevent the Karma browser from closing after running the tests, and clicking the "Debug" button will open a new tab that can be used for placing breakpoints and stepping through the code.
The `--debug` flag will prevent the Karma browser from closing after running the tests, and clicking the "Debug" button will open a new tab that can be used for placing breakpoints and stepping through the code. Alternatively, run the "Launch Test Suite and Debug in VSCode" launch configuration (which opens chrome, attaches VSCode, and prepares the test suite of the current file), set breakpoints directly in the Spec file, and then click "Debug" in Chrome to run the tests. Similar behavior may be possible in other IDEs by attaching to port 9333 (Karma's configured remote debugging port) after running the npm test command above.
![Karma](8.jpg)

View File

@ -77,3 +77,5 @@ you quit VSCode, so should be restarted manually on next launch.
## Debugging CesiumJS
To debug CesiumJS using the VSCode Chrome debugger, run the `"Launch Server"` configuration. If the server was already started in the terminal using `npm start`, use the `"Launch in Chrome"` configuration.
To debug a specific unit test suite, run the "Launch Test Suite and Debug in VSCode" configuration. This will start the server, launch chrome, and prepare to run the tests in the currently open file. Set one or more breakpoints in VSCode and then click "Debug" in the browser. If the server is already started, use the "Attach to Karma" configuration and then continue setting breakpoints.

View File

@ -999,11 +999,13 @@ export async function test() {
const release = argv.release ? argv.release : false;
const failTaskOnError = argv.failTaskOnError ? argv.failTaskOnError : false;
const suppressPassed = argv.suppressPassed ? argv.suppressPassed : false;
const debug = argv.debug ? false : true;
const debug = argv.debug ? true : false;
const debugCanvasWidth = argv.debugCanvasWidth;
const debugCanvasHeight = argv.debugCanvasHeight;
const includeName = argv.includeName ? argv.includeName : "";
const isProduction = argv.production;
const includeName = argv.includeName
? argv.includeName.replace(/Spec$/, "")
: "";
let workspace = argv.workspace;
if (workspace) {
@ -1017,7 +1019,7 @@ export async function test() {
});
}
let browsers = ["Chrome"];
let browsers = debug ? ["ChromeDebugging"] : ["Chrome"];
if (argv.browsers) {
browsers = argv.browsers.split(",");
}
@ -1087,7 +1089,7 @@ export async function test() {
karmaConfigFile,
{
port: 9876,
singleRun: debug,
singleRun: !debug,
browsers: browsers,
specReporter: {
suppressErrorSummary: false,

View File

@ -23,24 +23,18 @@ function Batch(
usingSphericalTextureCoordinates,
zIndex,
) {
this.primitives = primitives; // scene level primitive collection
this.primitives = primitives; // scene level primitive collection (each Batch manages its own Primitive from this collection)
this.classificationType = classificationType;
this.appearanceType = appearanceType;
this.materialProperty = materialProperty;
this.updaters = new AssociativeArray();
this.updaters = new AssociativeArray(); // GeometryUpdaters that manage the visual representation of the primitive.
this.createPrimitive = true;
this.primitive = undefined; // a GroundPrimitive encapsulating all the entities
this.oldPrimitive = undefined;
this.oldPrimitive = undefined; // a GroundPrimitive that is being replaced by the current primitive, but will still be shown until the current primitive is ready.
this.geometry = new AssociativeArray();
this.material = undefined;
this.updatersWithAttributes = new AssociativeArray();
this.attributes = new AssociativeArray();
this.invalidated = false;
this.removeMaterialSubscription =
materialProperty.definitionChanged.addEventListener(
Batch.prototype.onMaterialChanged,
this,
);
this.subscriptions = new AssociativeArray();
this.showsUpdated = new AssociativeArray();
this.usingSphericalTextureCoordinates = usingSphericalTextureCoordinates;
@ -48,10 +42,6 @@ function Batch(
this.rectangleCollisionCheck = new RectangleCollisionChecker();
}
Batch.prototype.onMaterialChanged = function () {
this.invalidated = true;
};
Batch.prototype.overlapping = function (rectangle) {
return this.rectangleCollisionCheck.collides(rectangle);
};
@ -71,6 +61,13 @@ Batch.prototype.isMaterial = function (updater) {
return defined(material) && material.equals(updaterMaterial);
};
/**
* Adds an updater to the Batch, and signals for a new Primitive to be created on the next update.
* @param {JulianDate} time
* @param {GeometryUpdater} updater
* @param {GeometryInstance} geometryInstance
* @private
*/
Batch.prototype.add = function (time, updater, geometryInstance) {
const id = updater.id;
this.updaters.set(id, updater);
@ -100,6 +97,13 @@ Batch.prototype.add = function (time, updater, geometryInstance) {
this.createPrimitive = true;
};
/**
* Remove an updater from the Batch, and potentially signals for a new Primitive to be created
* on the next update.
* @param {GeometryUpdater} updater
* @returns true if the updater was removed, false if it was not found.
* @private
*/
Batch.prototype.remove = function (updater) {
const id = updater.id;
const geometryInstance = this.geometry.get(id);
@ -120,6 +124,13 @@ Batch.prototype.remove = function (updater) {
return false;
};
/**
* Update a Batch, creating a new primitive, if necessary, or swapping out an old primitive for a new one that's ready.
* A new primitive is created whenever an updater is added to or removed from a Batch.
* @param {JulianDate} time
* @returns a boolean indicating whether the Batch was updated.
* @private
*/
Batch.prototype.update = function (time) {
let isUpdated = true;
let primitive = this.primitive;
@ -295,6 +306,10 @@ Batch.prototype.getBoundingSphere = function (updater, result) {
return BoundingSphereState.DONE;
};
/**
* Removes a Batch's primitive (and oldPrimitive, if it exists).
* @private
*/
Batch.prototype.destroy = function () {
const primitive = this.primitive;
const primitives = this.primitives;
@ -305,10 +320,11 @@ Batch.prototype.destroy = function () {
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
}
this.removeMaterialSubscription();
};
/**
* A container of Batch objects of ground geometry primitives, where a Batch is grouped by material,
* texture coordinate type, and spatial overlap.
* @private
*/
function StaticGroundGeometryPerMaterialBatch(
@ -316,12 +332,20 @@ function StaticGroundGeometryPerMaterialBatch(
classificationType,
appearanceType,
) {
this._items = [];
this._primitives = primitives;
this._items = []; // array of Batch objects, each containing representing a primitive and a set of updaters that manage the visual representation of the primitive.
this._primitives = primitives; // scene level primitive collection
this._classificationType = classificationType;
this._appearanceType = appearanceType;
}
/**
* Adds an geometry updater to a Batch. Tries to find a preexisting compatible Batch, or else creates a new Batch.
* Used by Visualizer classes to add and update (remove->add) a primitive's Updater set.
*
* @param {JulianDate} time
* @param {GeometryUpdater} updater A GeometryUpdater that manages the visual representation of a primitive.
* @private
*/
StaticGroundGeometryPerMaterialBatch.prototype.add = function (time, updater) {
const items = this._items;
const length = items.length;
@ -361,21 +385,30 @@ StaticGroundGeometryPerMaterialBatch.prototype.add = function (time, updater) {
items.push(batch);
};
/**
* Removes an updater from a Batch. Defers potential deletion until the next update.
* @param {GeometryUpdater} updater A GeometryUpdater that manages the visual representation of a primitive.
* @private
*/
StaticGroundGeometryPerMaterialBatch.prototype.remove = function (updater) {
const items = this._items;
const length = items.length;
for (let i = length - 1; i >= 0; i--) {
const item = items[i];
if (item.remove(updater)) {
if (item.updaters.length === 0) {
items.splice(i, 1);
item.destroy();
}
// If the item is now empty, delete it (deferred until the next update,
// in case a new updater is added to the same item first).
break;
}
}
};
/**
* Updates all the items (Batches) in the collection, and deletes any that are empty.
* @param {JulianDate} time
* @returns a boolean indicating whether any of the items (Batches) were updated.
* @private
*/
StaticGroundGeometryPerMaterialBatch.prototype.update = function (time) {
let i;
const items = this._items;
@ -383,13 +416,8 @@ StaticGroundGeometryPerMaterialBatch.prototype.update = function (time) {
for (i = length - 1; i >= 0; i--) {
const item = items[i];
if (item.invalidated) {
if (item.updaters.length === 0) {
items.splice(i, 1);
const updaters = item.updaters.values;
const updatersLength = updaters.length;
for (let h = 0; h < updatersLength; h++) {
this.add(time, updaters[h]);
}
item.destroy();
}
}

View File

@ -119,7 +119,7 @@ import ImageryLayerCollection from "./ImageryLayerCollection.js";
* @property {string|number} [instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority.
* @property {boolean} [showCreditsOnScreen=false] Whether to display the credits of this tileset on screen.
* @property {SplitDirection} [splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset.
* @property {boolean} [enableCollision=false] When <code>true</code>, enables collisions for camera or CPU picking. While this is <code>true</code> the camera will be prevented from going below the tileset surface if {@link ScreenSpaceCameraController#enableCollisionDetection} is true.
* @property {boolean} [enableCollision=false] When <code>true</code>, enables collisions for camera or CPU picking. While this is <code>true</code> the camera will be prevented from going below the tileset surface if {@link ScreenSpaceCameraController#enableCollisionDetection} is true. This also affects the behavior of {@link HeightReference.CLAMP_TO_GROUND} when clamping to 3D Tiles surfaces. If <code>enableCollision</code> is <code>false</code>, entities may not be correctly clamped to the tileset geometry.
* @property {boolean} [projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has been created.
* @property {boolean} [enablePick=false] Whether to allow collision and CPU picking with <code>pick</code> when using WebGL 1. If using WebGL 2 or above, this option will be ignored. If using WebGL 1 and this is true, the <code>pick</code> operation will work correctly, but it will use more memory to do so. If running with WebGL 1 and this is false, the model will use less memory, but <code>pick</code> will always return <code>undefined</code>. This cannot be set after the tileset has loaded.
* @property {boolean} [asynchronouslyLoadImagery=false] Whether loading imagery that is draped over the tileset should be done asynchronously. If this is <code>true</code>, then tile content will be displayed with its original texture until the imagery texture is loaded. If this is <code>false</code>, then the tile content will not be displayed until the imagery is ready.

View File

@ -80,7 +80,7 @@ ForEach.accessorWithSemantic = function (gltf, semantic, handler) {
return value;
}
}
}
},
);
if (defined(valueForEach)) {
@ -102,7 +102,7 @@ ForEach.accessorWithSemantic = function (gltf, semantic, handler) {
return value;
}
}
}
},
);
});
});
@ -124,7 +124,7 @@ ForEach.accessorContainingVertexAttributeData = function (gltf, handler) {
return value;
}
}
}
},
);
if (defined(valueForEach)) {
@ -143,7 +143,7 @@ ForEach.accessorContainingVertexAttributeData = function (gltf, handler) {
return value;
}
}
}
},
);
});
});
@ -322,7 +322,7 @@ ForEach.program = function (gltf, handler) {
if (usesExtension(gltf, "KHR_techniques_webgl")) {
return ForEach.object(
gltf.extensions.KHR_techniques_webgl.programs,
handler
handler,
);
}
@ -341,7 +341,7 @@ ForEach.shader = function (gltf, handler) {
if (usesExtension(gltf, "KHR_techniques_webgl")) {
return ForEach.object(
gltf.extensions.KHR_techniques_webgl.shaders,
handler
handler,
);
}
@ -410,7 +410,7 @@ ForEach.technique = function (gltf, handler) {
if (usesExtension(gltf, "KHR_techniques_webgl")) {
return ForEach.object(
gltf.extensions.KHR_techniques_webgl.techniques,
handler
handler,
);
}

View File

@ -1,7 +1,6 @@
import addToArray from "./addToArray.js";
import ForEach from "./ForEach.js";
import getAccessorByteStride from "./getAccessorByteStride.js";
import Frozen from "../../Core/Frozen.js";
import defined from "../../Core/defined.js";
import WebGLConstants from "../../Core/WebGLConstants.js";
@ -62,7 +61,7 @@ function addDefaults(gltf) {
});
ForEach.material(gltf, function (material) {
const extensions = material.extensions ?? Frozen.EMPTY_OBJECT;
const extensions = material.extensions ?? {};
const materialsCommon = extensions.KHR_materials_common;
if (defined(materialsCommon)) {
const technique = materialsCommon.technique;

View File

@ -1,3 +1,5 @@
/**
* Adds an element to an array and returns the element's index.
*

View File

@ -52,7 +52,7 @@ function findAccessorMinMax(gltf, accessor) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
components
components,
);
for (let j = 0; j < numberOfComponents; j++) {
const value = components[j];

View File

@ -16,11 +16,11 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getInt8(
byteOffset + i * componentTypeByteLength
byteOffset + i * componentTypeByteLength,
);
}
};
@ -30,11 +30,11 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getUint8(
byteOffset + i * componentTypeByteLength
byteOffset + i * componentTypeByteLength,
);
}
};
@ -44,12 +44,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getInt16(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};
@ -59,12 +59,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getUint16(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};
@ -74,12 +74,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getInt32(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};
@ -89,12 +89,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getUint32(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};
@ -104,12 +104,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getFloat32(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};
@ -119,12 +119,12 @@ function getComponentReader(componentType) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
result
result,
) {
for (let i = 0; i < numberOfComponents; ++i) {
result[i] = dataView.getFloat64(
byteOffset + i * componentTypeByteLength,
true
true,
);
}
};

View File

@ -87,7 +87,7 @@ function moveTechniqueRenderStates(gltf) {
blendFunctions.blendEquationSeparate ?? defaultBlendEquation,
blendFactors: getSupportedBlendFactors(
blendFunctions.blendFuncSeparate,
defaultBlendFactors
defaultBlendFactors,
),
};
}

View File

@ -44,7 +44,7 @@ function moveTechniquesToExtension(gltf) {
technique.attributes[attributeName] = {
semantic: parameterLegacy.semantic,
};
}
},
);
ForEach.techniqueUniform(
@ -64,7 +64,7 @@ function moveTechniquesToExtension(gltf) {
mappedUniforms[techniqueId] = {};
}
mappedUniforms[techniqueId][parameterName] = uniformName;
}
},
);
if (!defined(seenPrograms[techniqueLegacy.program])) {
@ -92,7 +92,7 @@ function moveTechniquesToExtension(gltf) {
// Store the index of the new technique to reference instead.
updatedTechniqueIndices[techniqueId] = addToArray(
extension.techniques,
technique
technique,
);
});

View File

@ -43,7 +43,7 @@ function readHeader(glb, byteOffset, count) {
for (let i = 0; i < count; ++i) {
header[i] = dataView.getUint32(
glb.byteOffset + byteOffset + i * sizeOfUint32,
true
true,
);
}
return header;

View File

@ -16,7 +16,7 @@ import defined from "../../Core/defined.js";
function readAccessorPacked(gltf, accessor) {
const byteStride = getAccessorByteStride(gltf, accessor);
const componentTypeByteLength = ComponentDatatype.getSizeInBytes(
accessor.componentType
accessor.componentType,
);
const numberOfComponents = numberOfComponentsForType(accessor.type);
const count = accessor.count;
@ -41,7 +41,7 @@ function readAccessorPacked(gltf, accessor) {
byteOffset,
numberOfComponents,
componentTypeByteLength,
components
components,
);
for (let j = 0; j < numberOfComponents; ++j) {
values[i * numberOfComponents + j] = components[j];

View File

@ -87,7 +87,7 @@ Remove.accessor = function (gltf, accessorId) {
if (attributeAccessorId > accessorId) {
primitive.attributes[semantic]--;
}
}
},
);
// Update accessor ids for the targets.
@ -98,7 +98,7 @@ Remove.accessor = function (gltf, accessorId) {
if (attributeAccessorId > accessorId) {
target[semantic]--;
}
}
},
);
});
const indices = primitive.indices;
@ -548,7 +548,7 @@ getListOfElementsIdsInUse.accessor = function (gltf) {
const attributeAccessorId =
node.extensions.EXT_mesh_gpu_instancing.attributes[key];
usedAccessorIds[attributeAccessorId] = true;
}
},
);
}
});
@ -587,9 +587,8 @@ getListOfElementsIdsInUse.buffer = function (gltf) {
defined(bufferView.extensions) &&
defined(bufferView.extensions.EXT_meshopt_compression)
) {
usedBufferIds[
bufferView.extensions.EXT_meshopt_compression.buffer
] = true;
usedBufferIds[bufferView.extensions.EXT_meshopt_compression.buffer] =
true;
}
});

View File

@ -42,7 +42,7 @@ function updateAccessorComponentTypes(gltf) {
function convertType(gltf, accessor, updatedComponentType) {
const typedArray = ComponentDatatype.createTypedArray(
updatedComponentType,
readAccessorPacked(gltf, accessor)
readAccessorPacked(gltf, accessor),
);
const newBuffer = new Uint8Array(typedArray.buffer);
accessor.bufferView = addBuffer(gltf, newBuffer);

View File

@ -13,7 +13,6 @@ import Cartesian3 from "../../Core/Cartesian3.js";
import Cartesian4 from "../../Core/Cartesian4.js";
import clone from "../../Core/clone.js";
import ComponentDatatype from "../../Core/ComponentDatatype.js";
import Frozen from "../../Core/Frozen.js";
import defined from "../../Core/defined.js";
import Matrix4 from "../../Core/Matrix4.js";
import Quaternion from "../../Core/Quaternion.js";
@ -40,11 +39,13 @@ const updateFunctions = {
* @private
*/
function updateVersion(gltf, options) {
options = options ?? Frozen.EMPTY_OBJECT;
options = options ?? {};
const targetVersion = options.targetVersion;
let version = gltf.version;
gltf.asset = gltf.asset ?? { version: "1.0" };
gltf.asset = gltf.asset ?? {
version: "1.0",
};
gltf.asset.version = gltf.asset.version ?? "1.0";
version = (version ?? gltf.asset.version).toString();
@ -176,7 +177,7 @@ function updateAnimations(gltf) {
componentType,
source.buffer,
byteOffset,
length
length,
);
for (let j = 0; j < count; j++) {
@ -416,7 +417,7 @@ function objectsToArrays(gltf) {
primitive,
function (accessorId, semantic) {
primitive.attributes[semantic] = globalMapping.accessors[accessorId];
}
},
);
if (defined(primitive.material)) {
primitive.material = globalMapping.materials[primitive.material];
@ -636,7 +637,7 @@ function requireAttributeSetIndex(gltf) {
} else if (semantic === "COLOR") {
primitive.attributes.COLOR_0 = accessorId;
}
}
},
);
delete primitive.attributes.TEXCOORD;
delete primitive.attributes.COLOR;
@ -695,7 +696,7 @@ function underscoreApplicationSpecificSemantics(gltf) {
mappedSemantics[semantic] = newSemantic;
}
}
}
},
);
for (const semantic in mappedSemantics) {
if (Object.prototype.hasOwnProperty.call(mappedSemantics, semantic)) {
@ -756,7 +757,7 @@ function requireByteLength(gltf) {
accessor.byteOffset + accessor.count * accessorByteStride;
bufferView.byteLength = Math.max(
bufferView.byteLength ?? 0,
accessorByteEnd
accessorByteEnd,
);
}
});
@ -861,7 +862,7 @@ function isNodeEmpty(node) {
Cartesian3.fromArray(node.scale).equals(new Cartesian3(1.0, 1.0, 1.0))) &&
(!defined(node.rotation) ||
Cartesian4.fromArray(node.rotation).equals(
new Cartesian4(0.0, 0.0, 0.0, 1.0)
new Cartesian4(0.0, 0.0, 0.0, 1.0),
)) &&
(!defined(node.matrix) ||
Matrix4.fromColumnMajorArray(node.matrix).equals(Matrix4.IDENTITY)) &&
@ -1028,7 +1029,7 @@ function srgbToLinear(srgb) {
linear[i] = Math.pow(
// eslint-disable-next-line no-loss-of-precision
(c + 0.055) * 0.94786729857819905213270142180095,
2.4
2.4,
);
}
}
@ -1037,7 +1038,7 @@ function srgbToLinear(srgb) {
}
function convertTechniquesToPbr(gltf, options) {
options = options ?? Frozen.EMPTY_OBJECT;
options = options ?? {};
const baseColorTextureNames =
options.baseColorTextureNames ?? defaultBaseColorTextureNames;
const baseColorFactorNames =
@ -1083,8 +1084,7 @@ function assignAsEmissive(material, emissive) {
function convertMaterialsCommonToPbr(gltf) {
// Future work: convert KHR_materials_common lights to KHR_lights_punctual
ForEach.material(gltf, function (material) {
const materialsCommon = (material.extensions ?? Frozen.EMPTY_OBJECT)
.KHR_materials_common;
const materialsCommon = (material.extensions ?? {}).KHR_materials_common;
if (!defined(materialsCommon)) {
// Nothing to do
return;

View File

@ -12,7 +12,7 @@ const HeightReference = {
NONE: 0,
/**
* The position is clamped to the terrain and 3D Tiles.
* The position is clamped to the terrain and 3D Tiles. When clamping to 3D Tilesets such as photorealistic 3D Tiles, ensure the tileset has {@link Cesium3DTileset#enableCollision} set to <code>true</code>. Otherwise, the entity may not be correctly clamped to the tileset surface.
* @type {number}
* @constant
*/

View File

@ -388,8 +388,8 @@ Object.defineProperties(ImageryLayer.prototype, {
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of the thrown error.
* @memberof Imagery.prototype
* @type {Event<Imagery.ErrorEventCallback>}
* @memberof ImageryLayer.prototype
* @type {Event<ImageryLayer.ErrorEventCallback>}
* @readonly
*/
errorEvent: {
@ -518,7 +518,7 @@ ImageryLayer.DEFAULT_APPLY_COLOR_TO_ALPHA_THRESHOLD = 0.004;
* viewer.imageryLayers.add(imageryLayer);
*
* imageryLayer.readyEvent.addEventListener(provider => {
* imageryLayer.provider.errorEvent.addEventListener(error => {
* imageryLayer.imageryProvider.errorEvent.addEventListener(error => {
* alert(`Encountered an error while loading imagery tiles! ${error}`);
* });
* });
@ -527,9 +527,9 @@ ImageryLayer.DEFAULT_APPLY_COLOR_TO_ALPHA_THRESHOLD = 0.004;
* alert(`Encountered an error while creating an imagery layer! ${error}`);
* });
*
* @see ImageryLayer.errorEvent
* @see ImageryLayer.readyEvent
* @see ImageryLayer.provider
* @see ImageryLayer#errorEvent
* @see ImageryLayer#readyEvent
* @see ImageryLayer#imageryProvider
* @see ImageryLayer.fromWorldImagery
*/
ImageryLayer.fromProviderAsync = function (imageryProviderPromise, options) {
@ -576,7 +576,7 @@ ImageryLayer.fromProviderAsync = function (imageryProviderPromise, options) {
* viewer.imageryLayers.add(imageryLayer);
*
* imageryLayer.readyEvent.addEventListener(provider => {
* imageryLayer.provider.errorEvent.addEventListener(error => {
* imageryLayer.imageryProvider.errorEvent.addEventListener(error => {
* alert(`Encountered an error while loading imagery tiles! ${error}`);
* });
* });
@ -585,9 +585,9 @@ ImageryLayer.fromProviderAsync = function (imageryProviderPromise, options) {
* alert(`Encountered an error while creating an imagery layer! ${error}`);
* });
*
* @see ImageryLayer.errorEvent
* @see ImageryLayer.readyEvent
* @see ImageryLayer.provider
* @see ImageryLayer#errorEvent
* @see ImageryLayer#readyEvent
* @see ImageryLayer#imageryProvider
*/
ImageryLayer.fromWorldImagery = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;

View File

@ -272,7 +272,7 @@ function computePickingDrawingBufferRectangle(
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {object} Object containing the picked primitive.
* @returns {object | undefined} Object containing the picked primitive.
*/
Picking.prototype.pick = function (scene, windowPosition, width, height) {
//>>includeStart('debug', pragmas.debug);
@ -923,6 +923,16 @@ function getTilesets(primitives, objectsToExclude, tilesets) {
}
}
/**
* @private
* @param {Picking} picking
* @param {Scene} scene
* @param {Ray} ray
* @param {Object[] | undefined} objectsToExclude
* @param {number | undefined} width
* @param {Function} callback
* @returns {Promise<Cartesian3 | undefined>}
*/
function launchMostDetailedRayPick(
picking,
scene,
@ -1266,6 +1276,12 @@ const scratchSurfaceNormal = new Cartesian3();
const scratchSurfaceRay = new Ray();
const scratchCartographic = new Cartographic();
/**
* @private
* @param {Scene} scene
* @param {Cartographic} cartographic
* @returns {Ray}
*/
function getRayForSampleHeight(scene, cartographic) {
const ellipsoid = scene.ellipsoid;
const height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
@ -1287,6 +1303,12 @@ function getRayForSampleHeight(scene, cartographic) {
return ray;
}
/**
* @private
* @param {Scene} scene
* @param {Cartesian3} cartesian
* @returns {Ray}
*/
function getRayForClampToHeight(scene, cartesian) {
const ellipsoid = scene.ellipsoid;
const cartographic = Cartographic.fromCartesian(
@ -1338,6 +1360,16 @@ function sampleHeightMostDetailed(
);
}
/**
* @private
* @param {Picking} picking
* @param {Scene} scene
* @param {Cartesian3} cartesian
* @param {Object[]} [objectsToExclude]
* @param {number} [width]
* @param {Cartesian3} [result]
* @returns {Promise<Cartesian3 | undefined>}
*/
function clampToHeightMostDetailed(
picking,
scene,
@ -1483,6 +1515,14 @@ Picking.prototype.sampleHeightMostDetailed = function (
);
};
/**
* @private
* @param {Scene} scene
* @param {Cartesian3[]} cartesians
* @param {Object[]} [objectsToExclude]
* @param {number} [width]
* @returns {Promise<Array<Cartesian3 | undefined>>}
*/
Picking.prototype.clampToHeightMostDetailed = function (
scene,
cartesians,

View File

@ -276,7 +276,7 @@ function Scene(options) {
/**
* The {@link SkyBox} used to draw the stars.
*
* @type {SkyBox}
* @type {SkyBox | undefined}
* @default undefined
*
* @see Scene#backgroundColor
@ -286,7 +286,7 @@ function Scene(options) {
/**
* The sky atmosphere drawn around the globe.
*
* @type {SkyAtmosphere}
* @type {SkyAtmosphere | undefined}
* @default undefined
*/
this.skyAtmosphere = undefined;
@ -294,7 +294,7 @@ function Scene(options) {
/**
* The {@link Sun}.
*
* @type {Sun}
* @type {Sun | undefined}
* @default undefined
*/
this.sun = undefined;
@ -311,13 +311,13 @@ function Scene(options) {
/**
* The {@link Moon}
*
* @type Moon
* @type {Moon | undefined}
* @default undefined
*/
this.moon = undefined;
/**
* The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined.
* The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is <code>undefined</code>.
*
* @type {Color}
* @default {@link Color.BLACK}
@ -406,7 +406,7 @@ function Scene(options) {
* The default is <code>undefined</code>, indicating that all commands are executed.
* </p>
*
* @type Function
* @type {Function | undefined}
*
* @default undefined
*
@ -1306,11 +1306,11 @@ Object.defineProperties(Scene.prototype, {
},
/**
* Gets the simulation time when the scene was last rendered. Returns undefined if the scene has not yet been
* rendered.
* Gets the simulation time when the scene was last rendered. Returns <code>undefined</code>
* if the scene has not yet been rendered.
* @memberof Scene.prototype
*
* @type {JulianDate}
* @type {JulianDate | undefined}
* @readonly
*/
lastRenderTime: {
@ -1343,7 +1343,7 @@ Object.defineProperties(Scene.prototype, {
*
* @memberof Scene.prototype
*
* @type {object}
* @type {Object | undefined}
* @readonly
*
* @default undefined
@ -4371,8 +4371,8 @@ Scene.prototype.clampLineWidth = function (width) {
};
/**
* Returns an object with a `primitive` property that contains the first (top) primitive in the scene
* at a particular window coordinate or undefined if nothing is at the location. Other properties may
* Returns an object with a <code>primitive</code> property that contains the first (top) primitive in the scene
* at a particular window coordinate or <code>undefined</code> if nothing is at the location. Other properties may
* potentially be set depending on the type of primitive and may be used to further identify the picked object.
* <p>
* When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
@ -4390,7 +4390,7 @@ Scene.prototype.clampLineWidth = function (width) {
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {object} Object containing the picked primitive.
* @returns {Object | undefined} Object containing the picked primitive or <code>undefined</code> if nothing is at the location.
*/
Scene.prototype.pick = function (windowPosition, width, height) {
return this._picking.pick(this, windowPosition, width, height);
@ -4398,7 +4398,7 @@ Scene.prototype.pick = function (windowPosition, width, height) {
/**
* Returns a {@link VoxelCell} for the voxel sample rendered at a particular window coordinate,
* or undefined if no voxel is rendered at that position.
* or <code>undefined</code> if no voxel is rendered at that position.
*
* @example
* On left click, report the value of the "color" property at that voxel sample.
@ -4412,7 +4412,7 @@ Scene.prototype.pick = function (windowPosition, width, height) {
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {VoxelCell|undefined} Information about the voxel cell rendered at the picked position.
* @returns {VoxelCell|undefined} Information about the voxel cell rendered at the picked position or <code>undefined</code> if no voxel is rendered at that position.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
@ -4454,13 +4454,13 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) {
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {string|undefined} schemaId The ID of the metadata schema to pick values
* from. If this is `undefined`, then it will pick the values from the object
* from. If this is <code>undefined</code>, then it will pick the values from the object
* that match the given class- and property name, regardless of the schema ID.
* @param {string} className The name of the metadata class to pick
* values from
* @param {string} propertyName The name of the metadata property to pick
* values from
* @returns {MetadataValue|undefined} The metadata value, or `undefined` when
* @returns {MetadataValue|undefined} The metadata value, or <code>undefined</code> when
* no matching metadata was found at the given position
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
@ -4529,7 +4529,7 @@ Scene.prototype.pickMetadata = function (
* Pick the schema of the metadata of the object at the given position
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @returns {MetadataSchema} The metadata schema, or `undefined` if there is no object with
* @returns {MetadataSchema | undefined} The metadata schema, or <code>undefined</code> if there is no object with
* associated metadata at the given position.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
@ -4598,7 +4598,7 @@ Scene.prototype.pickPosition = function (windowPosition, result) {
};
/**
* Returns a list of objects, each containing a `primitive` property, for all primitives at
* Returns a list of objects, each containing a <code>primitive</code> property, for all primitives at
* a particular window coordinate position. Other properties may also be set depending on the
* type of primitive and may be used to further identify the picked object. The primitives in
* the list are ordered by their visual order in the scene (front to back).
@ -4667,7 +4667,7 @@ function updateRequestRenderModeDeferCheckPass(scene) {
* @param {Ray} ray The ray.
* @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
* @param {number} [width=0.1] Width of the intersection volume in meters.
* @returns {object} An object containing the object and position of the first intersection.
* @returns {object | undefined} An object containing the object and position of the first intersection or <code>undefined</code> if there are no intersections.
*
* @exception {DeveloperError} Ray intersections are only supported in 3D mode.
*/
@ -4778,7 +4778,7 @@ Scene.prototype.drillPickFromRayMostDetailed = function (
* @param {Cartographic} position The cartographic position to sample height from.
* @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from.
* @param {number} [width=0.1] Width of the intersection volume in meters.
* @returns {number} The height. This may be <code>undefined</code> if there was no scene geometry to sample height from.
* @returns {number | undefined} The height. This may be <code>undefined</code> if there was no scene geometry to sample height from.
*
* @example
* const position = new Cesium.Cartographic(-1.31968, 0.698874);
@ -4809,7 +4809,7 @@ Scene.prototype.sampleHeight = function (position, objectsToExclude, width) {
* @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to.
* @param {number} [width=0.1] Width of the intersection volume in meters.
* @param {Cartesian3} [result] An optional object to return the clamped position.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. This may be <code>undefined</code> if there was no scene geometry to clamp to.
* @returns {Cartesian3 | undefined} The modified result parameter or a new Cartesian3 instance if one was not provided. This may be <code>undefined</code> if there was no scene geometry to clamp to.
*
* @example
* // Clamp an entity to the underlying scene geometry
@ -4843,12 +4843,12 @@ Scene.prototype.clampToHeight = function (
* using the maximum level of detail for 3D Tilesets in the scene. The height of the input positions is ignored.
* Returns a promise that is resolved when the query completes. Each point height is modified in place.
* If a height cannot be determined because no geometry can be sampled at that location, or another error occurs,
* the height is set to undefined.
* the height is set to <code>undefined</code>.
*
* @param {Cartographic[]} positions The cartographic positions to update with sampled heights.
* @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from.
* @param {number} [width=0.1] Width of the intersection volume in meters.
* @returns {Promise<Cartographic[]>} A promise that resolves to the provided list of positions when the query has completed.
* @returns {Promise<Array<Cartographic | undefined>>} A promise that resolves to the provided list of positions when the query has completed. Positions may become <code>undefined</code> if the height cannot be determined.
*
* @example
* const positions = [
@ -4888,7 +4888,7 @@ Scene.prototype.sampleHeightMostDetailed = function (
* @param {Cartesian3[]} cartesians The cartesian positions to update with clamped positions.
* @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to.
* @param {number} [width=0.1] Width of the intersection volume in meters.
* @returns {Promise<Cartesian3[]>} A promise that resolves to the provided list of positions when the query has completed.
* @returns {Promise<Array<Cartesian3 | undefined>>} A promise that resolves to the provided list of positions when the query has completed. Positions may become <code>undefined</code> if they cannot be clamped.
*
* @example
* const cartesians = [
@ -4925,7 +4925,7 @@ Scene.prototype.clampToHeightMostDetailed = function (
*
* @param {Cartesian3} position The position in cartesian coordinates.
* @param {Cartesian2} [result] An optional object to return the input position transformed to canvas coordinates.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be <code>undefined</code> if the input position is near the center of the ellipsoid.
* @returns {Cartesian2 | undefined} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be <code>undefined</code> if the input position is near the center of the ellipsoid.
*
* @example
* // Output the canvas position of longitude/latitude (0, 0) every time the mouse moves.

View File

@ -266,6 +266,8 @@ function ScreenSpaceCameraController(scene) {
this._minimumTrackBallHeight = this.minimumTrackBallHeight;
/**
* When disabled, the values of <code>maximumZoomDistance</code> and <code>minimumZoomDistance</code> are ignored.
* Also used in conjunction with {@link Cesium3DTileset#enableCollision} to prevent the camera from moving through or below a 3D Tileset surface.
* This may also affect clamping behavior when using {@link HeightReference.CLAMP_TO_GROUND} on 3D Tiles.
* @type {boolean}
* @default true
*/

View File

@ -18,6 +18,8 @@ import {
ClassificationType,
GroundPrimitive,
MaterialAppearance,
GeometryVisualizer,
EntityCollection,
} from "../../index.js";
import createScene from "../../../../Specs/createScene.js";
@ -51,11 +53,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
return;
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
ClassificationType.BOTH,
MaterialAppearance,
);
const entityCollection = new EntityCollection();
const geometryVisualizer = new GeometryVisualizer(scene, entityCollection);
const ellipse = new EllipseGraphics();
ellipse.semiMajorAxis = new ConstantProperty(2);
@ -77,31 +76,29 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
ellipse: ellipse2,
});
const updater = new EllipseGeometryUpdater(entity, scene);
const updater2 = new EllipseGeometryUpdater(entity2, scene);
batch.add(time, updater);
batch.add(time, updater2);
entityCollection.add(entity);
entityCollection.add(entity2);
return pollToPromise(function () {
scene.initializeFrame();
const isUpdated = batch.update(time);
const isUpdated = geometryVisualizer.update(time);
scene.render(time);
return isUpdated;
})
.then(function () {
expect(scene.primitives.length).toEqual(1);
expect(scene.groundPrimitives.length).toEqual(1);
ellipse.material.cellAlpha = new ConstantProperty(0.5);
return pollToPromise(function () {
scene.initializeFrame();
const isUpdated = batch.update(time);
const isUpdated = geometryVisualizer.update(time);
scene.render(time);
return isUpdated;
});
})
.then(function () {
expect(scene.primitives.length).toEqual(2);
batch.removeAllPrimitives();
expect(scene.groundPrimitives.length).toEqual(2);
geometryVisualizer.destroy();
});
});
@ -139,7 +136,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
});
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -153,8 +150,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
scene.render(validTime);
return isUpdated;
}).then(function () {
expect(scene.primitives.length).toEqual(1);
let primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
let primitive = scene.groundPrimitives.get(0);
let attributes = primitive.getGeometryInstanceAttributes(entity);
expect(attributes.distanceDisplayCondition).toEqualEpsilon(
[1.0, 2.0],
@ -164,7 +161,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
batch.update(outOfRangeTime);
scene.render(outOfRangeTime);
primitive = scene.primitives.get(0);
primitive = scene.groundPrimitives.get(0);
attributes = primitive.getGeometryInstanceAttributes(entity);
expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]);
@ -206,7 +203,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
});
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -220,15 +217,15 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
scene.render(validTime);
return isUpdated;
}).then(function () {
expect(scene.primitives.length).toEqual(1);
let primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
let primitive = scene.groundPrimitives.get(0);
let attributes = primitive.getGeometryInstanceAttributes(entity);
expect(attributes.show).toEqual([1]);
batch.update(outOfRangeTime);
scene.render(outOfRangeTime);
primitive = scene.primitives.get(0);
primitive = scene.groundPrimitives.get(0);
attributes = primitive.getGeometryInstanceAttributes(entity);
expect(attributes.show).toEqual([0]);
@ -246,7 +243,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -285,8 +282,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
batch.add(time, updater1);
return pollToPromise(renderScene)
.then(function () {
expect(scene.primitives.length).toEqual(1);
const primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
const primitive = scene.groundPrimitives.get(0);
expect(primitive.show).toBeTruthy();
})
.then(function () {
@ -295,22 +292,22 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
.then(function () {
return pollToPromise(function () {
renderScene();
return scene.primitives.length === 2;
return scene.groundPrimitives.length === 2;
});
})
.then(function () {
let showCount = 0;
expect(scene.primitives.length).toEqual(2);
showCount += !!scene.primitives.get(0).show;
showCount += !!scene.primitives.get(1).show;
expect(scene.groundPrimitives.length).toEqual(2);
showCount += !!scene.groundPrimitives.get(0).show;
showCount += !!scene.groundPrimitives.get(1).show;
expect(showCount).toEqual(1);
})
.then(function () {
return pollToPromise(renderScene);
})
.then(function () {
expect(scene.primitives.length).toEqual(1);
const primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
const primitive = scene.groundPrimitives.get(0);
expect(primitive.show).toBeTruthy();
batch.removeAllPrimitives();
@ -327,7 +324,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -363,7 +360,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
scene.render(time);
return isUpdated;
}).then(function () {
expect(scene.primitives.length).toEqual(2);
expect(scene.groundPrimitives.length).toEqual(2);
batch.removeAllPrimitives();
});
@ -379,7 +376,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -412,7 +409,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
scene.render(time);
return isUpdated;
}).then(function () {
expect(scene.primitives.length).toEqual(1);
expect(scene.groundPrimitives.length).toEqual(1);
batch.removeAllPrimitives();
});
@ -432,7 +429,7 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.primitives,
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
@ -471,8 +468,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
return pollToPromise(renderScene)
.then(function () {
expect(scene.primitives.length).toEqual(1);
const primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
const primitive = scene.groundPrimitives.get(0);
const attributes = primitive.getGeometryInstanceAttributes(entity1);
expect(attributes.show).toEqual([1]);
@ -481,8 +478,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
return pollToPromise(renderScene);
})
.then(function () {
expect(scene.primitives.length).toEqual(1);
const primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
const primitive = scene.groundPrimitives.get(0);
const attributes = primitive.getGeometryInstanceAttributes(entity1);
expect(attributes.show).toEqual([0]);
@ -490,8 +487,8 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
return pollToPromise(renderScene);
})
.then(function () {
expect(scene.primitives.length).toEqual(1);
const primitive = scene.primitives.get(0);
expect(scene.groundPrimitives.length).toEqual(1);
const primitive = scene.groundPrimitives.get(0);
let attributes = primitive.getGeometryInstanceAttributes(entity1);
expect(attributes.show).toEqual([0]);
@ -501,4 +498,67 @@ describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
batch.removeAllPrimitives();
});
});
/**
* Written originally in response to issue #1640, where materials flashed white and transparent when changing their properties,
* because doing so caused a batch to be destroyed and recreated prematurely which, in turn, caused the associated
* material (and primitive) to be destroyed and recreated.
*/
it("doesn't immediately remove an empty batch that may be reused before the next update", async function () {
if (
!GroundPrimitive.isSupported(scene) ||
!GroundPrimitive.supportsMaterials(scene)
) {
// Don't fail if materials on GroundPrimitive not supported
return;
}
const batch = new StaticGroundGeometryPerMaterialBatch(
scene.groundPrimitives,
ClassificationType.BOTH,
MaterialAppearance,
);
const ellipse = new EllipseGraphics();
const ellipseMaterial = new GridMaterialProperty();
ellipse.semiMajorAxis = new ConstantProperty(2);
ellipse.semiMinorAxis = new ConstantProperty(1);
ellipse.material = ellipseMaterial;
const entity = new Entity({
position: new Cartesian3(1234, 5678, 9101112),
ellipse: ellipse,
});
const updater = new EllipseGeometryUpdater(entity, scene);
batch.add(time, updater);
await pollToPromise(function () {
scene.initializeFrame();
const isUpdated = batch.update(time);
scene.render(time);
return isUpdated;
});
// Remove and add back the updater before the next batch update
// Removal should defer deletion of the batch, and the updater should find the old batch still works upon re-adding
// Thus, the material should not be destroyed and recreated (and will have the same guid / be the same object)
const materialBefore = scene.groundPrimitives.get(0).appearance.material;
batch.remove(updater);
batch.add(time, updater);
await pollToPromise(function () {
scene.initializeFrame();
const isUpdated = batch.update(time);
scene.render(time);
return isUpdated;
});
const groundPrimitives = scene.groundPrimitives;
const materialAfter = groundPrimitives.get(0).appearance.material;
expect(materialBefore).toEqual(materialAfter);
expect(groundPrimitives.length).toEqual(1);
batch.removeAllPrimitives();
});
});