mirror of https://github.com/CesiumGS/cesium.git
Merge branch 'main' into label-clamping-performance
This commit is contained in:
commit
7e376b6450
|
|
@ -12,7 +12,8 @@
|
|||
- Improved performance and reduced memory usage of `Event` class. [#12896](https://github.com/CesiumGS/cesium/pull/12896)
|
||||
- Fixes vertical misalignment of glyphs in labels with small fonts [#8474](https://github.com/CesiumGS/cesium/issues/8474)
|
||||
- Prevent runtime errors for certain forms of invalid PNTS files [#12872](https://github.com/CesiumGS/cesium/issues/12872)
|
||||
- Improved performance of clamped labels [#12905](https://github.com/CesiumGS/cesium/pull/12905)
|
||||
- Improved performance of clamped labels. [#12905](https://github.com/CesiumGS/cesium/pull/12905)
|
||||
- Fixes issue where multiple instances of a Gaussian splat tileset would transform tile positions incorrectly and render out of position. [#12795](https://github.com/CesiumGS/cesium/issues/12795)
|
||||
|
||||
#### Additions :tada:
|
||||
|
||||
|
|
|
|||
|
|
@ -431,3 +431,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
|
|||
- [Easy Mahaffey](https://github.com/easymaahffey)
|
||||
- [Pamela Augustine](https://github.com/pamelaAugustine)
|
||||
- [宋时旺](https://github.com/BlockCnFuture)
|
||||
- [Marco Zhan](https://github.com/marcoYxz)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ function handleBuildWarnings(result) {
|
|||
export async function build() {
|
||||
// Configure build options from command line arguments.
|
||||
const minify = argv.minify ?? false;
|
||||
const removePragmas = argv.pragmas ?? false;
|
||||
const removePragmas = argv.removePragmas ?? false;
|
||||
const sourcemap = argv.sourcemap ?? true;
|
||||
const node = argv.node ?? true;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,90 @@ function ClippingPolygon(options) {
|
|||
//>>includeEnd('debug');
|
||||
|
||||
this._ellipsoid = options.ellipsoid ?? Ellipsoid.default;
|
||||
this._positions = [...options.positions];
|
||||
this._positions = copyArrayCartesian3(options.positions);
|
||||
|
||||
/**
|
||||
* A copy of the input positions.
|
||||
*
|
||||
* This is used to detect modifications of the positions in
|
||||
* <code>coputeRectangle</code>: The rectangle only has
|
||||
* to be re-computed when these positions have changed.
|
||||
*
|
||||
* @type {Cartesian3[]|undefined}
|
||||
* @private
|
||||
*/
|
||||
this._cachedPositions = undefined;
|
||||
|
||||
/**
|
||||
* A cached version of the rectangle that is computed in
|
||||
* <code>computeRectangle</code>.
|
||||
*
|
||||
* This is only re-computed when the positions have changed, as
|
||||
* determined by comparing the <code>_positions</code> to the
|
||||
* <code>_cachedPositions</code>
|
||||
*
|
||||
* @type {Rectangle|undefined}
|
||||
* @private
|
||||
*/
|
||||
this._cachedRectangle = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy of the given array.
|
||||
*
|
||||
* If the input is undefined, then <code>undefined</code> is returned.
|
||||
*
|
||||
* Otherwise, the result will be a copy of the given array, where
|
||||
* each element is copied with <code>Cartesian3.clone</code>.
|
||||
*
|
||||
* @param {Cartesian3[]|undefined} input The input array
|
||||
* @returns {Cartesian3[]|undefined} The copy
|
||||
*/
|
||||
function copyArrayCartesian3(input) {
|
||||
if (!defined(input)) {
|
||||
return undefined;
|
||||
}
|
||||
const n = input.length;
|
||||
const output = Array(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
output[i] = Cartesian3.clone(input[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given arrays are component-wise equal.
|
||||
*
|
||||
* When both arrays are undefined, then <code>true</code> is returned.
|
||||
* When only one array is defined, or they are both defined but have
|
||||
* different lengths, then <code>false</code> is returned.
|
||||
*
|
||||
* Otherwise, returns whether the corresponding elements of the arrays
|
||||
* are equal, as of <code>Cartesian3.equals</code>.
|
||||
*
|
||||
* @param {Cartesian3[]|undefined} a The first array
|
||||
* @param {Cartesian3[]|undefined} b The second array
|
||||
* @returns {boolean} Whether the arrays are equal
|
||||
*/
|
||||
function equalsArrayCartesian3(a, b) {
|
||||
if (!defined(a) && !defined(b)) {
|
||||
return true;
|
||||
}
|
||||
if (defined(a) !== defined(b)) {
|
||||
return false;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
const n = a.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const ca = a[i];
|
||||
const cb = b[i];
|
||||
if (!Cartesian3.equals(ca, cb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Object.defineProperties(ClippingPolygon.prototype, {
|
||||
|
|
@ -138,12 +221,18 @@ ClippingPolygon.equals = function (left, right) {
|
|||
* @returns {Rectangle} The result rectangle
|
||||
*/
|
||||
ClippingPolygon.prototype.computeRectangle = function (result) {
|
||||
return PolygonGeometry.computeRectangleFromPositions(
|
||||
if (equalsArrayCartesian3(this._positions, this._cachedPositions)) {
|
||||
return Rectangle.clone(this._cachedRectangle, result);
|
||||
}
|
||||
const rectangle = PolygonGeometry.computeRectangleFromPositions(
|
||||
this.positions,
|
||||
this.ellipsoid,
|
||||
undefined,
|
||||
result,
|
||||
);
|
||||
this._cachedPositions = copyArrayCartesian3(this._positions);
|
||||
this._cachedRectangle = Rectangle.clone(rectangle);
|
||||
return rectangle;
|
||||
};
|
||||
|
||||
const scratchRectangle = new Rectangle();
|
||||
|
|
|
|||
|
|
@ -31,15 +31,28 @@ function GaussianSplat3DTileContent(loader, tileset, tile, resource) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Original position, scale and rotation values for splats. Used to maintain
|
||||
* consistency when multiple transforms may occur. Downstream consumers otherwise may not know
|
||||
* the underlying data was modified.
|
||||
* Local copy of the position attribute buffer that has been transformed into root tile space. Originals are kept in the gltf loader.
|
||||
* Used for rendering
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
this._originalPositions = undefined;
|
||||
this._originalRotations = undefined;
|
||||
this._originalScales = undefined;
|
||||
this._positions = undefined;
|
||||
|
||||
/**
|
||||
* Local copy of the rotation attribute buffer that has been transformed into root tile space. Originals are kept in the gltf loader.
|
||||
* Used for rendering
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
this._rotations = undefined;
|
||||
|
||||
/**
|
||||
* Local copy of the scale attribute buffer that has been transformed into root tile space. Originals are kept in the gltf loader.
|
||||
* Used for rendering
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
this._scales = undefined;
|
||||
|
||||
/**
|
||||
* glTF primitive data that contains the Gaussian splat data needed for rendering.
|
||||
|
|
@ -368,6 +381,38 @@ Object.defineProperties(GaussianSplat3DTileContent.prototype, {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the transformed positions of this tile's Gaussian splats.
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
positions: {
|
||||
get: function () {
|
||||
return this._positions;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Get the transformed rotations of this tile's Gaussian splats.
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
rotations: {
|
||||
get: function () {
|
||||
return this._rotations;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the transformed scales of this tile's Gaussian splats.
|
||||
* @type {undefined|Float32Array}
|
||||
* @private
|
||||
*/
|
||||
scales: {
|
||||
get: function () {
|
||||
return this._scales;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The number of spherical harmonic coefficients used for the Gaussian splats.
|
||||
* @type {number}
|
||||
|
|
@ -629,21 +674,21 @@ GaussianSplat3DTileContent.prototype.update = function (primitive, frameState) {
|
|||
this.worldTransform = loader.components.scene.nodes[0].matrix;
|
||||
this._ready = true;
|
||||
|
||||
this._originalPositions = new Float32Array(
|
||||
this._positions = new Float32Array(
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
this.gltfPrimitive,
|
||||
VertexAttributeSemantic.POSITION,
|
||||
).typedArray,
|
||||
);
|
||||
|
||||
this._originalRotations = new Float32Array(
|
||||
this._rotations = new Float32Array(
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
this.gltfPrimitive,
|
||||
VertexAttributeSemantic.ROTATION,
|
||||
).typedArray,
|
||||
);
|
||||
|
||||
this._originalScales = new Float32Array(
|
||||
this._scales = new Float32Array(
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
this.gltfPrimitive,
|
||||
VertexAttributeSemantic.SCALE,
|
||||
|
|
|
|||
|
|
@ -450,9 +450,9 @@ GaussianSplatPrimitive.transformTile = function (tile) {
|
|||
computedModelMatrix,
|
||||
scratchMatrix4A,
|
||||
);
|
||||
const positions = tile.content._originalPositions;
|
||||
const rotations = tile.content._originalRotations;
|
||||
const scales = tile.content._originalScales;
|
||||
const positions = tile.content.positions;
|
||||
const rotations = tile.content.rotations;
|
||||
const scales = tile.content.scales;
|
||||
const attributePositions = ModelUtility.getAttributeBySemantic(
|
||||
gltfPrimitive,
|
||||
VertexAttributeSemantic.POSITION,
|
||||
|
|
@ -471,19 +471,19 @@ GaussianSplatPrimitive.transformTile = function (tile) {
|
|||
const position = new Cartesian3();
|
||||
const rotation = new Quaternion();
|
||||
const scale = new Cartesian3();
|
||||
for (let i = 0; i < positions.length / 3; ++i) {
|
||||
position.x = positions[i * 3];
|
||||
position.y = positions[i * 3 + 1];
|
||||
position.z = positions[i * 3 + 2];
|
||||
for (let i = 0; i < attributePositions.length / 3; ++i) {
|
||||
position.x = attributePositions[i * 3];
|
||||
position.y = attributePositions[i * 3 + 1];
|
||||
position.z = attributePositions[i * 3 + 2];
|
||||
|
||||
rotation.x = rotations[i * 4];
|
||||
rotation.y = rotations[i * 4 + 1];
|
||||
rotation.z = rotations[i * 4 + 2];
|
||||
rotation.w = rotations[i * 4 + 3];
|
||||
rotation.x = attributeRotations[i * 4];
|
||||
rotation.y = attributeRotations[i * 4 + 1];
|
||||
rotation.z = attributeRotations[i * 4 + 2];
|
||||
rotation.w = attributeRotations[i * 4 + 3];
|
||||
|
||||
scale.x = scales[i * 3];
|
||||
scale.y = scales[i * 3 + 1];
|
||||
scale.z = scales[i * 3 + 2];
|
||||
scale.x = attributeScales[i * 3];
|
||||
scale.y = attributeScales[i * 3 + 1];
|
||||
scale.z = attributeScales[i * 3 + 2];
|
||||
|
||||
Matrix4.fromTranslationQuaternionRotationScale(
|
||||
position,
|
||||
|
|
@ -498,18 +498,18 @@ GaussianSplatPrimitive.transformTile = function (tile) {
|
|||
Matrix4.getRotation(scratchMatrix4C, rotation);
|
||||
Matrix4.getScale(scratchMatrix4C, scale);
|
||||
|
||||
attributePositions[i * 3] = position.x;
|
||||
attributePositions[i * 3 + 1] = position.y;
|
||||
attributePositions[i * 3 + 2] = position.z;
|
||||
positions[i * 3] = position.x;
|
||||
positions[i * 3 + 1] = position.y;
|
||||
positions[i * 3 + 2] = position.z;
|
||||
|
||||
attributeRotations[i * 4] = rotation.x;
|
||||
attributeRotations[i * 4 + 1] = rotation.y;
|
||||
attributeRotations[i * 4 + 2] = rotation.z;
|
||||
attributeRotations[i * 4 + 3] = rotation.w;
|
||||
rotations[i * 4] = rotation.x;
|
||||
rotations[i * 4 + 1] = rotation.y;
|
||||
rotations[i * 4 + 2] = rotation.z;
|
||||
rotations[i * 4 + 3] = rotation.w;
|
||||
|
||||
attributeScales[i * 3] = scale.x;
|
||||
attributeScales[i * 3 + 1] = scale.y;
|
||||
attributeScales[i * 3 + 2] = scale.z;
|
||||
scales[i * 3] = scale.x;
|
||||
scales[i * 3 + 1] = scale.y;
|
||||
scales[i * 3 + 2] = scale.z;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -860,21 +860,27 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
|
|||
const aggregateAttributeValues = (
|
||||
componentDatatype,
|
||||
getAttributeCallback,
|
||||
numberOfComponents,
|
||||
) => {
|
||||
let aggregate;
|
||||
let offset = 0;
|
||||
for (const tile of tiles) {
|
||||
const primitive = tile.content.gltfPrimitive;
|
||||
const attribute = getAttributeCallback(primitive);
|
||||
const content = tile.content;
|
||||
const attribute = getAttributeCallback(content);
|
||||
const componentsPerAttribute = defined(numberOfComponents)
|
||||
? numberOfComponents
|
||||
: AttributeType.getNumberOfComponents(attribute.type);
|
||||
const buffer = defined(attribute.typedArray)
|
||||
? attribute.typedArray
|
||||
: attribute;
|
||||
if (!defined(aggregate)) {
|
||||
aggregate = ComponentDatatype.createTypedArray(
|
||||
componentDatatype,
|
||||
totalElements *
|
||||
AttributeType.getNumberOfComponents(attribute.type),
|
||||
totalElements * componentsPerAttribute,
|
||||
);
|
||||
}
|
||||
aggregate.set(attribute.typedArray, offset);
|
||||
offset += attribute.typedArray.length;
|
||||
aggregate.set(buffer, offset);
|
||||
offset += buffer.length;
|
||||
}
|
||||
return aggregate;
|
||||
};
|
||||
|
|
@ -906,36 +912,27 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
|
|||
|
||||
this._positions = aggregateAttributeValues(
|
||||
ComponentDatatype.FLOAT,
|
||||
(gltfPrimitive) =>
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
gltfPrimitive,
|
||||
VertexAttributeSemantic.POSITION,
|
||||
),
|
||||
(content) => content.positions,
|
||||
3,
|
||||
);
|
||||
|
||||
this._scales = aggregateAttributeValues(
|
||||
ComponentDatatype.FLOAT,
|
||||
(gltfPrimitive) =>
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
gltfPrimitive,
|
||||
VertexAttributeSemantic.SCALE,
|
||||
),
|
||||
(content) => content.scales,
|
||||
3,
|
||||
);
|
||||
|
||||
this._rotations = aggregateAttributeValues(
|
||||
ComponentDatatype.FLOAT,
|
||||
(gltfPrimitive) =>
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
gltfPrimitive,
|
||||
VertexAttributeSemantic.ROTATION,
|
||||
),
|
||||
(content) => content.rotations,
|
||||
4,
|
||||
);
|
||||
|
||||
this._colors = aggregateAttributeValues(
|
||||
ComponentDatatype.UNSIGNED_BYTE,
|
||||
(gltfPrimitive) =>
|
||||
(content) =>
|
||||
ModelUtility.getAttributeBySemantic(
|
||||
gltfPrimitive,
|
||||
content.gltfPrimitive,
|
||||
VertexAttributeSemantic.COLOR,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
|
||||
import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js";
|
||||
import createScene from "../../../../Specs/createScene.js";
|
||||
import pollToPromise from "../../../../Specs/pollToPromise.js";
|
||||
|
||||
describe(
|
||||
"Scene/GaussianSplat3DTileContent",
|
||||
|
|
@ -96,6 +97,7 @@ describe(
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("Create and destroy GaussianSplat3DTileContent", async function () {
|
||||
const tileset = await Cesium3DTilesTester.loadTileset(
|
||||
scene,
|
||||
|
|
@ -116,6 +118,69 @@ describe(
|
|||
expect(tile.isDestroyed()).toBe(true);
|
||||
expect(tile.content).toBeUndefined();
|
||||
});
|
||||
|
||||
it("Load multiple instances of Gaussian splat tileset and validate transformed attributes", async function () {
|
||||
const tileset = await Cesium3DTilesTester.loadTileset(
|
||||
scene,
|
||||
tilesetUrl,
|
||||
options,
|
||||
);
|
||||
scene.camera.lookAt(
|
||||
tileset.boundingSphere.center,
|
||||
new HeadingPitchRange(0.0, -1.57, tileset.boundingSphere.radius),
|
||||
);
|
||||
|
||||
const tileset2 = await Cesium3DTilesTester.loadTileset(
|
||||
scene,
|
||||
tilesetUrl,
|
||||
options,
|
||||
);
|
||||
|
||||
const tile = await Cesium3DTilesTester.waitForTileContentReady(
|
||||
scene,
|
||||
tileset.root,
|
||||
);
|
||||
|
||||
scene.camera.lookAt(
|
||||
tileset2.boundingSphere.center,
|
||||
new HeadingPitchRange(0.0, -1.57, tileset2.boundingSphere.radius),
|
||||
);
|
||||
|
||||
const tile2 = await Cesium3DTilesTester.waitForTileContentReady(
|
||||
scene,
|
||||
tileset2.root,
|
||||
);
|
||||
const content = tile.content;
|
||||
const content2 = tile2.content;
|
||||
|
||||
expect(content).toBeDefined();
|
||||
expect(content instanceof GaussianSplat3DTileContent).toBe(true);
|
||||
expect(content2).toBeDefined();
|
||||
expect(content2 instanceof GaussianSplat3DTileContent).toBe(true);
|
||||
|
||||
await pollToPromise(function () {
|
||||
scene.renderForSpecs();
|
||||
return (
|
||||
tile.content._transformed === true &&
|
||||
tile2.content._transformed === true
|
||||
);
|
||||
});
|
||||
|
||||
const positions1 = tile.content._positions;
|
||||
const positions2 = tile2.content._positions;
|
||||
|
||||
expect(positions1.every((p, i) => p === positions2[i])).toBe(true);
|
||||
|
||||
const rotations1 = tile.content._rotations;
|
||||
const rotations2 = tile2.content._rotations;
|
||||
|
||||
expect(rotations1.every((r, i) => r === rotations2[i])).toBe(true);
|
||||
|
||||
const scales1 = tile.content._scales;
|
||||
const scales2 = tile2.content._scales;
|
||||
|
||||
expect(scales1.every((s, i) => s === scales2[i])).toBe(true);
|
||||
});
|
||||
},
|
||||
"WebGL",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ if (import.meta.url.endsWith(`${pathToFileURL(process.argv[1])}`)) {
|
|||
let buildGalleryOptions;
|
||||
|
||||
try {
|
||||
const config = await import(configPath);
|
||||
const config = await import(pathToFileURL(configPath).href);
|
||||
const { root, publicDir, gallery, sourceUrl } = config.default;
|
||||
|
||||
// Paths are specified relative to the config file
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
|||
import { EOL } from "node:os";
|
||||
import path from "node:path";
|
||||
import { finished } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import { globby } from "globby";
|
||||
|
|
@ -387,6 +387,9 @@ export async function bundleWorkers(options) {
|
|||
workerConfig.logOverride = {
|
||||
"empty-import-meta": "silent",
|
||||
};
|
||||
workerConfig.plugins = options.removePragmas
|
||||
? [stripPragmaPlugin]
|
||||
: undefined;
|
||||
} else {
|
||||
workerConfig.format = "esm";
|
||||
workerConfig.splitting = true;
|
||||
|
|
@ -616,7 +619,7 @@ const externalResolvePlugin = {
|
|||
export async function getSandcastleConfig() {
|
||||
const configPath = "packages/sandcastle/sandcastle.config.js";
|
||||
const configImportPath = path.join(projectRoot, configPath);
|
||||
const config = await import(configImportPath);
|
||||
const config = await import(pathToFileURL(configImportPath).href);
|
||||
const options = config.default;
|
||||
return {
|
||||
...options,
|
||||
|
|
@ -650,7 +653,9 @@ export async function buildSandcastleGallery(includeDevelopment) {
|
|||
__dirname,
|
||||
"../packages/sandcastle/scripts/buildGallery.js",
|
||||
);
|
||||
const { buildGalleryList } = await import(buildGalleryScriptPath);
|
||||
const { buildGalleryList } = await import(
|
||||
pathToFileURL(buildGalleryScriptPath).href
|
||||
);
|
||||
|
||||
await buildGalleryList({
|
||||
rootDirectory,
|
||||
|
|
|
|||
Loading…
Reference in New Issue