Merge pull request #12963 from CesiumGS/3d-tiles-terrain

3d tiles terrain
This commit is contained in:
jjspace 2025-11-03 14:58:59 +00:00 committed by GitHub
commit 7d3efcb544
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 6216 additions and 108 deletions

View File

@ -0,0 +1,139 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Apply materials to the globe." />
<meta name="cesium-sandcastle-labels" content="Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<div id="zoomButtons"></div>
</div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const terrainProvider = await Cesium.Cesium3DTilesTerrainProvider.fromIonAssetId(
3923568,
{
requestVertexNormals: true, // Needed for hillshade lighting
requestWaterMask: true, // Needed to distinguish land from water
},
);
const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: terrainProvider,
scene3DOnly: true,
sceneModePicker: false,
navigationHelpButton: false,
});
// Create a globe material for shading elevation only on land
const customElevationMaterial = new Cesium.Material({
fabric: {
type: "ElevationLand",
materials: {
waterMaskMaterial: {
type: "WaterMask",
},
elevationRampMaterial: {
type: "ElevationRamp",
},
},
components: {
diffuse: "elevationRampMaterial.diffuse",
alpha: "1.0 - waterMaskMaterial.alpha", // We'll need the inverse of the watermask to shade land
},
},
translucent: false,
});
const minHeight = -414.0; // approximate dead sea elevation
const maxHeight = 8777.0; // approximate everest elevation
const elevationRamp = [0.0, 0.045, 0.45, 0.5, 0.55, 1.0];
function getColorRamp() {
const ramp = document.createElement("canvas");
ramp.width = 100;
ramp.height = 1;
const ctx = ramp.getContext("2d");
const values = elevationRamp;
const grd = ctx.createLinearGradient(0, 0, 100, 0);
// See https://gis.stackexchange.com/questions/25099/choosing-colour-ramp-to-use-for-elevation
grd.addColorStop(values[0], "#344f31");
grd.addColorStop(values[1], "#5b8742");
grd.addColorStop(values[2], "#e6daa5");
grd.addColorStop(values[3], "#fdc771");
grd.addColorStop(values[4], "#b99d89");
grd.addColorStop(values[5], "#f0f0f0");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 100, 1);
return ramp;
}
const globe = viewer.scene.globe;
const material = customElevationMaterial;
const shadingUniforms = material.materials.elevationRampMaterial.uniforms;
globe.showWaterEffect = false;
globe.enableLighting = true;
shadingUniforms.minimumHeight = minHeight;
shadingUniforms.maximumHeight = maxHeight;
shadingUniforms.image = getColorRamp();
globe.material = material;
// Light the scene with a hillshade effect similar to https://pro.arcgis.com/en/pro-app/latest/tool-reference/3d-analyst/how-hillshade-works.htm
const scene = viewer.scene;
scene.light = new Cesium.DirectionalLight({
direction: new Cesium.Cartesian3(1, 0, 0), // Updated every frame
});
// Update the light position base on the camera
const scratchNormal = new Cesium.Cartesian3();
scene.preRender.addEventListener(function (scene, time) {
const surfaceNormal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(
scene.camera.positionWC,
scratchNormal,
);
const negativeNormal = Cesium.Cartesian3.negate(surfaceNormal, surfaceNormal);
scene.light.direction = Cesium.Cartesian3.normalize(
Cesium.Cartesian3.add(negativeNormal, scene.camera.rightWC, surfaceNormal),
scene.light.direction,
);
});
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -11,6 +11,7 @@
#### Additions :tada:
- Added experimental support for loading 3D Tiles as terrain, via `Cesium3DTilesTerrainProvider`. See [the PR](https://github.com/CesiumGS/cesium/pull/12963) for limitations on the types of 3D Tiles that can be used. [#12296](https://github.com/CesiumGS/cesium/issues/12296)
- Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765)
#### Fixes :wrench:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
{"asset":{"version":"1.1"},"geometricError":631380.3810809468,"metadata":{"class":"tilesetInfo","properties":{"description":"Entire Earth description","name":"Entire Earth"}},"root":{"boundingVolume":{"region":[-3.141592653589793,-1.5707963267948966,3.141592653589793,1.5707963267948966,-130.93199157714844,80.01030731201172]},"children":[{"boundingVolume":{"region":[-3.141592653589793,-1.5707963267948966,0.0,1.5707963267948966,-76.75321197509766,67.20038604736328]},"content":{"uri":"0/{level}/{x}/{y}.glb"},"geometricError":157845.0952702367,"implicitTiling":{"availableLevels":2,"subdivisionScheme":"QUADTREE","subtreeLevels":7,"subtrees":{"uri":"0/{level}/{x}/{y}.subtree"}}},{"boundingVolume":{"region":[0.0,-1.5707963267948966,3.141592653589793,1.5707963267948966,-130.93199157714844,80.01030731201172]},"content":{"uri":"1/{level}/{x}/{y}.glb"},"geometricError":157845.0952702367,"implicitTiling":{"availableLevels":2,"subdivisionScheme":"QUADTREE","subtreeLevels":7,"subtrees":{"uri":"1/{level}/{x}/{y}.subtree"}}}],"geometricError":315690.1905404734,"refine":"REPLACE"},"schema":{"classes":{"subtreeTile":{"properties":{"boundingSphere":{"array":true,"componentType":"FLOAT64","count":4,"semantic":"TILE_BOUNDING_SPHERE","type":"SCALAR"},"horizonOcclusionPoint":{"componentType":"FLOAT64","semantic":"TILE_HORIZON_OCCLUSION_POINT","type":"VEC3"},"maximumHeight":{"componentType":"FLOAT64","semantic":"TILE_MAXIMUM_HEIGHT","type":"SCALAR"},"minimumHeight":{"componentType":"FLOAT64","semantic":"TILE_MINIMUM_HEIGHT","type":"SCALAR"}}},"tilesetInfo":{"properties":{"description":{"semantic":"DESCRIPTION","type":"STRING"},"name":{"semantic":"NAME","type":"STRING"}}}}}}

View File

@ -0,0 +1,930 @@
import BoundingSphere from "./BoundingSphere.js";
import Cartesian2 from "./Cartesian2.js";
import Cartesian3 from "./Cartesian3.js";
import Cesium3DTilesTerrainGeometryProcessor from "./Cesium3DTilesTerrainGeometryProcessor.js";
import CesiumMath from "./Math.js";
import Check from "./Check.js";
import defined from "./defined.js";
import DeveloperError from "./DeveloperError.js";
import Frozen from "./Frozen.js";
import Intersections2D from "./Intersections2D.js";
import OrientedBoundingBox from "./OrientedBoundingBox.js";
import Rectangle from "./Rectangle.js";
import TaskProcessor from "./TaskProcessor.js";
import TerrainData from "./TerrainData.js";
import TerrainEncoding from "./TerrainEncoding.js";
import TerrainMesh from "./TerrainMesh.js";
/**
* Terrain data for a single tile where the terrain data is represented as a glb (binary glTF).
*
* @alias Cesium3DTilesTerrainData
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
* @constructor
*
* @param {object} options Object with the following properties:
* @param {Object.<string,*>} options.gltf The parsed glTF JSON.
* @param {number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
* @param {number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
* @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
* @param {OrientedBoundingBox} options.orientedBoundingBox An oriented bounding box containing all of the vertices in the mesh.
* @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh. If this point
* is below the horizon, the entire tile is assumed to be below the horizon as well.
* The point is expressed in ellipsoid-scaled coordinates.
* @param {number} options.skirtHeight The height of the skirt to add on the edges of the tile.
* @param {boolean} [options.requestVertexNormals=false] Indicates whether normals should be loaded.
* @param {boolean} [options.requestWaterMask=false] Indicates whether water mask data should be loaded.
* @param {Credit[]} [options.credits] Array of credits for this tile.
* @param {number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
* If a child's bit is set, geometry will be requested for that tile as well when it
* is needed. If the bit is cleared, the child tile is not requested and geometry is
* instead upsampled from the parent. The bit values are as follows:
* <table>
* <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
* <tr><td>0</td><td>1</td><td>Southwest</td></tr>
* <tr><td>1</td><td>2</td><td>Southeast</td></tr>
* <tr><td>2</td><td>4</td><td>Northwest</td></tr>
* <tr><td>3</td><td>8</td><td>Northeast</td></tr>
* </table>
* @param {Uint8Array} [options.waterMask] The buffer containing the water mask.
* @see TerrainData
* @see QuantizedMeshTerrainData
* @see HeightmapTerrainData
* @see GoogleEarthEnterpriseTerrainData
*/
function Cesium3DTilesTerrainData(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug)
Check.defined("options.gltf", options.gltf);
Check.typeOf.number("options.minimumHeight", options.minimumHeight);
Check.typeOf.number("options.maximumHeight", options.maximumHeight);
Check.typeOf.object("options.boundingSphere", options.boundingSphere);
Check.typeOf.object(
"option.orientedBoundingBox",
options.orientedBoundingBox,
);
Check.typeOf.object(
"options.horizonOcclusionPoint",
options.horizonOcclusionPoint,
);
Check.typeOf.number("options.skirtHeight", options.skirtHeight);
//>>includeEnd('debug');
this._minimumHeight = options.minimumHeight;
this._maximumHeight = options.maximumHeight;
this._skirtHeight = options.skirtHeight;
this._boundingSphere = BoundingSphere.clone(
options.boundingSphere,
new BoundingSphere(),
);
this._orientedBoundingBox = OrientedBoundingBox.clone(
options.orientedBoundingBox,
new OrientedBoundingBox(),
);
this._horizonOcclusionPoint = Cartesian3.clone(
options.horizonOcclusionPoint,
new Cartesian3(),
);
this._hasVertexNormals = options.requestVertexNormals ?? false;
this._hasWaterMask = options.requestWaterMask ?? false;
this._hasWebMercatorT = true;
this._credits = options.credits;
this._childTileMask = options.childTileMask ?? 15;
this._gltf = options.gltf;
/**
* @private
* @type {TerrainMesh|undefined}
*/
this._mesh = undefined;
this._waterMask = options.waterMask;
}
Object.defineProperties(Cesium3DTilesTerrainData.prototype, {
/**
* An array of credits for this tile.
* @memberof Cesium3DTilesTerrainData.prototype
* @type {Credit[]|undefined}
*/
credits: {
get: function () {
return this._credits;
},
},
/**
* The water mask included in this terrain data, if any. A water mask is a rectangular
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof Cesium3DTilesTerrainData.prototype
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
*/
waterMask: {
get: function () {
return this._waterMask;
},
},
});
/**
* Returns the terrain height at a specified longitude and latitude, or undefined if the mesh is undefined.
*
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {number} longitude The longitude in radians.
* @param {number} latitude The latitude in radians.
* @returns {number|undefined} The terrain height at the specified position, or undefined if the mesh is undefined.
* If the position is outside the rectangle, this method will extrapolate the height,
* which is likely to be wildly incorrect for positions far outside the rectangle.
*/
Cesium3DTilesTerrainData.prototype.interpolateHeight = function (
rectangle,
longitude,
latitude,
) {
const mesh = this._mesh;
if (mesh === undefined) {
return undefined;
}
const height = interpolateMeshHeight(mesh, rectangle, longitude, latitude);
return height;
};
/**
* Determines if a given child tile is available, based on the
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
* given, the availability of the southeast child tile is returned.
*
* @param {number} thisX The tile X coordinate of this (the parent) tile.
* @param {number} thisY The tile Y coordinate of this (the parent) tile.
* @param {number} childX The tile X coordinate of the child tile to check for availability.
* @param {number} childY The tile Y coordinate of the child tile to check for availability.
* @returns {boolean} True if the child tile is available; otherwise, false.
*/
Cesium3DTilesTerrainData.prototype.isChildAvailable = function (
thisX,
thisY,
childX,
childY,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("thisX", thisX);
Check.typeOf.number("thisY", thisY);
Check.typeOf.number("childX", childX);
Check.typeOf.number("childY", childY);
//>>includeEnd('debug');
let bitNumber = 2; // northwest child
if (childX !== thisX * 2) {
++bitNumber; // east child
}
if (childY !== thisY * 2) {
bitNumber -= 2; // south child
}
return (this._childTileMask & (1 << bitNumber)) !== 0;
};
const createMeshTaskName = "createVerticesFromCesium3DTilesTerrain";
const createMeshTaskProcessorNoThrottle = new TaskProcessor(createMeshTaskName);
const createMeshTaskProcessorThrottle = new TaskProcessor(
createMeshTaskName,
TerrainData.maximumAsynchronousTasks,
);
/**
* Creates a {@link TerrainMesh} from this terrain data.
*
* @private
*
* @param {object} options Object with the following properties:
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
* @param {number} options.level The level of the tile for which to create the terrain data.
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
* @param {boolean} [options.throttle=true] If true, indicates that this operation will need to be retried if too many asynchronous mesh creations are already in progress.
* @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
* asynchronous mesh creations are already in progress and the operation should
* be retried later.
*/
Cesium3DTilesTerrainData.prototype.createMesh = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug)
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
Check.typeOf.number("options.x", options.x);
Check.typeOf.number("options.y", options.y);
Check.typeOf.number("options.level", options.level);
//>>includeEnd('debug');
const throttle = options.throttle ?? true;
const createMeshTaskProcessor = throttle
? createMeshTaskProcessorThrottle
: createMeshTaskProcessorNoThrottle;
const tilingScheme = options.tilingScheme;
const ellipsoid = tilingScheme.ellipsoid;
const x = options.x;
const y = options.y;
const level = options.level;
const rectangle = tilingScheme.tileXYToRectangle(
x,
y,
level,
new Rectangle(),
);
const gltf = this._gltf;
const verticesPromise = createMeshTaskProcessor.scheduleTask({
ellipsoid: ellipsoid,
rectangle: rectangle,
hasVertexNormals: this._hasVertexNormals,
hasWaterMask: this._hasWaterMask,
hasWebMercatorT: this._hasWebMercatorT,
gltf: gltf,
minimumHeight: this._minimumHeight,
maximumHeight: this._maximumHeight,
boundingSphere: this._boundingSphere,
orientedBoundingBox: this._orientedBoundingBox,
horizonOcclusionPoint: this._horizonOcclusionPoint,
skirtHeight: this._skirtHeight,
exaggeration: options.exaggeration,
exaggerationRelativeHeight: options.exaggerationRelativeHeight,
});
if (!defined(verticesPromise)) {
// Too many active requests. Postponed.
return undefined;
}
const that = this;
return Promise.resolve(verticesPromise).then(function (result) {
const taskResult = result;
// Need to re-clone and re-wrap all buffers and complex objects to put them back into their normal state
const encoding = TerrainEncoding.clone(
taskResult.encoding,
new TerrainEncoding(),
);
const vertices = new Float32Array(taskResult.verticesBuffer);
const vertexCount = vertices.length / encoding.stride;
const vertexCountWithoutSkirts = taskResult.vertexCountWithoutSkirts;
// For consistency with glTF spec, 16 bit index buffer can't contain 65535
const SizedIndexType = vertexCount <= 65535 ? Uint16Array : Uint32Array;
const indices = new SizedIndexType(taskResult.indicesBuffer);
const westIndices = new SizedIndexType(taskResult.westIndicesBuffer);
const eastIndices = new SizedIndexType(taskResult.eastIndicesBuffer);
const southIndices = new SizedIndexType(taskResult.southIndicesBuffer);
const northIndices = new SizedIndexType(taskResult.northIndicesBuffer);
const indexCountWithoutSkirts = taskResult.indexCountWithoutSkirts;
const minimumHeight = that._minimumHeight;
const maximumHeight = that._maximumHeight;
const center = Cartesian3.clone(encoding.center, new Cartesian3());
const boundingSphere = BoundingSphere.clone(
that._boundingSphere,
new BoundingSphere(),
);
const horizonOcclusionPoint = Cartesian3.clone(
that._horizonOcclusionPoint,
new Cartesian3(),
);
const orientedBoundingBox = OrientedBoundingBox.clone(
that._orientedBoundingBox,
new OrientedBoundingBox(),
);
const mesh = new TerrainMesh(
center,
vertices,
indices,
indexCountWithoutSkirts,
vertexCountWithoutSkirts,
minimumHeight,
maximumHeight,
boundingSphere,
horizonOcclusionPoint,
encoding.stride,
orientedBoundingBox,
encoding,
westIndices,
southIndices,
eastIndices,
northIndices,
);
that._mesh = mesh;
return Promise.resolve(mesh);
});
};
/**
* Creates a {@link TerrainMesh} from this terrain data synchronously.
*
* @private
*
* @param {object} options Object with the following properties:
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
* @param {number} options.level The level of the tile for which to create the terrain data.
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
* @returns {Promise.<TerrainMesh>} A promise for the terrain mesh.
*/
Cesium3DTilesTerrainData.prototype._createMeshSync = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug)
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
Check.typeOf.number("options.x", options.x);
Check.typeOf.number("options.y", options.y);
Check.typeOf.number("options.level", options.level);
//>>includeEnd('debug');
const tilingScheme = options.tilingScheme;
const ellipsoid = tilingScheme.ellipsoid;
const x = options.x;
const y = options.y;
const level = options.level;
const rectangle = tilingScheme.tileXYToRectangle(
x,
y,
level,
new Rectangle(),
);
const meshPromise = Cesium3DTilesTerrainGeometryProcessor.createMesh({
ellipsoid: ellipsoid,
rectangle: rectangle,
hasVertexNormals: this._hasVertexNormals,
hasWebMercatorT: this._hasWebMercatorT,
gltf: this._gltf,
minimumHeight: this._minimumHeight,
maximumHeight: this._maximumHeight,
boundingSphere: this._boundingSphere,
orientedBoundingBox: this._orientedBoundingBox,
horizonOcclusionPoint: this._horizonOcclusionPoint,
skirtHeight: this._skirtHeight,
exaggeration: options.exaggeration,
exaggerationRelativeHeight: options.exaggerationRelativeHeight,
});
const that = this;
return Promise.resolve(meshPromise).then(function (mesh) {
that._mesh = mesh;
return Promise.resolve(mesh);
});
};
/**
* Upsamples this terrain data for use by a descendant tile.
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {number} thisLevel The level of this tile in the tiling scheme.
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if createMesh has not been called yet or too many asynchronous upsample operations are in progress and the request has been deferred.
*/
Cesium3DTilesTerrainData.prototype.upsample = function (
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
) {
// mesh is not defined, so there are no UVs yet, so exit early
const mesh = this._mesh;
if (mesh === undefined) {
return undefined;
}
const isSynchronous = false;
const upsampledTerrainData = upsampleMesh(
isSynchronous,
mesh,
this._skirtHeight,
this._credits,
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
);
return upsampledTerrainData;
};
/**
* Upsamples this terrain data for use by a descendant tile synchronously.
*
* @private
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {number} thisLevel The level of this tile in the tiling scheme.
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if createMesh has not been called yet.
*/
Cesium3DTilesTerrainData.prototype._upsampleSync = function (
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
) {
// mesh is not defined, so there are no UVs yet, so exit early
const mesh = this._mesh;
if (mesh === undefined) {
return undefined;
}
const isSynchronous = true;
const upsampledTerrainData = upsampleMesh(
isSynchronous,
mesh,
this._skirtHeight,
this._credits,
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
);
return upsampledTerrainData;
};
/**
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
* terrain data. If this value is false, the data was obtained from some other source, such
* as by downloading it from a remote server. This method should return true for instances
* returned from a call to {@link Cesium3DTilesTerrainData#upsample}.
*
* @returns {boolean} True if this instance was created by upsampling; otherwise, false.
*/
Cesium3DTilesTerrainData.prototype.wasCreatedByUpsampling = function () {
return false;
};
/**
* @private
* @constructor
*
* @param {object} options Object with the following properties:
* @param {TerrainMesh} options.terrainMesh The terrain mesh.
* @param {number} options.skirtHeight The height of the skirt to add on the edges of the tile.
* @param {Credit[]} [options.credits] Array of credits for this tile.
*/
function Cesium3DTilesUpsampleTerrainData(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug)
Check.defined("options.terrainMesh", options.terrainMesh);
Check.defined("options.skirtHeight", options.skirtHeight);
//>>includeEnd('debug');
this._mesh = options.terrainMesh;
this._skirtHeight = options.skirtHeight;
this._credits = options.credits;
}
/**
* Creates a {@link TerrainMesh} from this terrain data.
*
* @private
*
* @param {object} options Object with the following properties:
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
* @param {number} options.level The level of the tile for which to create the terrain data.
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
* @param {boolean} [options.throttle=true] If true, indicates that this operation will need to be retried if too many asynchronous mesh creations are already in progress.
* @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many asynchronous mesh creations are already in progress and the operation should be retried later.
*/
Cesium3DTilesUpsampleTerrainData.prototype.createMesh = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
Check.typeOf.number("options.x", options.x);
Check.typeOf.number("options.y", options.y);
Check.typeOf.number("options.level", options.level);
//>>includeEnd('debug');
return Promise.resolve(this._mesh);
};
/**
* Upsamples this terrain data for use by a descendant tile.
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {number} thisLevel The level of this tile in the tiling scheme.
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if too many asynchronous upsample operations are in progress and the request has been deferred.
*/
Cesium3DTilesUpsampleTerrainData.prototype.upsample = function (
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
) {
const isSynchronous = false;
const upsampledTerrainData = upsampleMesh(
isSynchronous,
this._mesh,
this._skirtHeight,
this._credits,
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
);
return upsampledTerrainData;
};
/**
* Upsamples this terrain data for use by a descendant tile synchronously.
*
* @private
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {number} thisLevel The level of this tile in the tiling scheme.
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.<TerrainData>} A promise for upsampled terrain data for the descendant tile.
*/
Cesium3DTilesUpsampleTerrainData.prototype._upsampleSync = function (
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
) {
const isSynchronous = true;
return upsampleMesh(
isSynchronous,
this._mesh,
this._skirtHeight,
this._credits,
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
);
};
/**
* Computes the terrain height at a specified longitude and latitude.
*
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {number} longitude The longitude in radians.
* @param {number} latitude The latitude in radians.
* @returns {number} The terrain height at the specified position. If the position is outside the rectangle, this method will extrapolate the height, which is likely to be wildly incorrect for positions far outside the rectangle.
*/
Cesium3DTilesUpsampleTerrainData.prototype.interpolateHeight = function (
rectangle,
longitude,
latitude,
) {
const mesh = this._mesh;
const height = interpolateMeshHeight(mesh, rectangle, longitude, latitude);
return height;
};
/**
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
* terrain data. If this value is false, the data was obtained from some other source, such
* as by downloading it from a remote server. This method should return true for instances
* returned from a call to {@link TerrainData#upsample}.
*
* @returns {boolean} True if this instance was created by upsampling; otherwise, false.
*/
Cesium3DTilesUpsampleTerrainData.prototype.wasCreatedByUpsampling =
function () {
return true;
};
/**
* Determines if a given child tile is available, based on the
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
* given, the availability of the southeast child tile is returned.
*
* @param {number} _thisX The tile X coordinate of this (the parent) tile.
* @param {number} _thisY The tile Y coordinate of this (the parent) tile.
* @param {number} _childX The tile X coordinate of the child tile to check for availability.
* @param {number} _childY The tile Y coordinate of the child tile to check for availability.
* @returns {boolean} True if the child tile is available; otherwise, false.
*/
Cesium3DTilesUpsampleTerrainData.prototype.isChildAvailable = function (
_thisX,
_thisY,
_childX,
_childY,
) {
// upsample tiles are dynamic so they don't have children
return false;
};
Object.defineProperties(Cesium3DTilesUpsampleTerrainData.prototype, {
/**
* An array of credits for this tile.
* @memberof Cesium3DTilesUpsampleTerrainData.prototype
* @type {Credit[]|undefined}
*/
credits: {
get: function () {
return this._credits;
},
},
/**
* The water mask included in this terrain data, if any. A water mask is a rectangular
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof Cesium3DTilesUpsampleTerrainData.prototype
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
*/
waterMask: {
get: function () {
// Note: watermask not needed because there's a fallback in another file that checks for ancestor tile water mask
return undefined;
},
},
});
const upsampleTaskProcessor = new TaskProcessor(
"upsampleVerticesFromCesium3DTilesTerrain",
TerrainData.maximumAsynchronousTasks,
);
/**
* Upsamples this terrain data for use by a descendant tile.
* @private
* @param {boolean} synchronous
* @param {TerrainMesh} thisMesh The mesh that is being upsampled
* @param {number} thisSkirtHeight The mesh's skirt height
* @param {Credit[]|undefined} credits The credits
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {number} thisLevel The level of this tile in the tiling scheme.
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if too many asynchronous upsample operations are in progress and the request has been deferred.
*/
function upsampleMesh(
synchronous,
thisMesh,
thisSkirtHeight,
credits,
tilingScheme,
thisX,
thisY,
thisLevel,
descendantX,
descendantY,
descendantLevel,
) {
//>>includeStart('debug', pragmas.debug)
Check.typeOf.bool("synchronous", synchronous);
Check.typeOf.object("thisMesh", thisMesh);
Check.typeOf.number("thisSkirtHeight", thisSkirtHeight);
Check.typeOf.object("tilingScheme", tilingScheme);
Check.typeOf.number("thisX", thisX);
Check.typeOf.number("thisY", thisY);
Check.typeOf.number("thisLevel", thisLevel);
Check.typeOf.number("descendantX", descendantX);
Check.typeOf.number("descendantY", descendantY);
Check.typeOf.number("descendantLevel", descendantLevel);
//>>includeEnd('debug');
const levelDifference = descendantLevel - thisLevel;
if (levelDifference > 1) {
throw new DeveloperError(
"Upsampling through more than one level at a time is not currently supported.",
);
}
const upsampleSkirtHeight = thisSkirtHeight * 0.5;
const isEastChild = thisX * 2 !== descendantX;
const isNorthChild = thisY * 2 === descendantY;
const upsampleRectangle = tilingScheme.tileXYToRectangle(
descendantX,
descendantY,
descendantLevel,
new Rectangle(),
);
const ellipsoid = tilingScheme.ellipsoid;
const options = {
isEastChild: isEastChild,
isNorthChild: isNorthChild,
rectangle: upsampleRectangle,
ellipsoid: ellipsoid,
skirtHeight: upsampleSkirtHeight,
parentVertices: thisMesh.vertices,
parentIndices: thisMesh.indices,
parentVertexCountWithoutSkirts: thisMesh.vertexCountWithoutSkirts,
parentIndexCountWithoutSkirts: thisMesh.indexCountWithoutSkirts,
parentMinimumHeight: thisMesh.minimumHeight,
parentMaximumHeight: thisMesh.maximumHeight,
parentEncoding: thisMesh.encoding,
};
if (synchronous) {
const upsampledMesh =
Cesium3DTilesTerrainGeometryProcessor.upsampleMesh(options);
const upsampledTerrainData = new Cesium3DTilesUpsampleTerrainData({
terrainMesh: upsampledMesh,
skirtHeight: upsampleSkirtHeight,
credits: credits,
});
return Promise.resolve(upsampledTerrainData);
}
const upsamplePromise = upsampleTaskProcessor.scheduleTask(options);
if (upsamplePromise === undefined) {
// Postponed
return undefined;
}
return upsamplePromise.then(function (taskResult) {
// Need to re-clone and re-wrap all buffers and complex objects to put them back into their normal state
const encoding = TerrainEncoding.clone(
taskResult.encoding,
new TerrainEncoding(),
);
const stride = encoding.stride;
const vertices = new Float32Array(taskResult.verticesBuffer);
const vertexCount = vertices.length / stride;
const vertexCountWithoutSkirts = taskResult.vertexCountWithoutSkirts;
// For consistency with glTF spec, 16 bit index buffer can't contain 65535
const SizedIndexType = vertexCount <= 65535 ? Uint16Array : Uint32Array;
const indices = new SizedIndexType(taskResult.indicesBuffer);
const westIndices = new SizedIndexType(taskResult.westIndicesBuffer);
const eastIndices = new SizedIndexType(taskResult.eastIndicesBuffer);
const southIndices = new SizedIndexType(taskResult.southIndicesBuffer);
const northIndices = new SizedIndexType(taskResult.northIndicesBuffer);
const indexCountWithoutSkirts = taskResult.indexCountWithoutSkirts;
const minimumHeight = taskResult.minimumHeight;
const maximumHeight = taskResult.maximumHeight;
const center = Cartesian3.clone(encoding.center, new Cartesian3());
const boundingSphere = BoundingSphere.clone(
taskResult.boundingSphere,
new BoundingSphere(),
);
const horizonOcclusionPoint = Cartesian3.clone(
taskResult.horizonOcclusionPoint,
new Cartesian3(),
);
const orientedBoundingBox = OrientedBoundingBox.clone(
taskResult.orientedBoundingBox,
new OrientedBoundingBox(),
);
const upsampledMesh = new TerrainMesh(
center,
vertices,
indices,
indexCountWithoutSkirts,
vertexCountWithoutSkirts,
minimumHeight,
maximumHeight,
boundingSphere,
horizonOcclusionPoint,
stride,
orientedBoundingBox,
encoding,
westIndices,
southIndices,
eastIndices,
northIndices,
);
const upsampledTerrainData = new Cesium3DTilesUpsampleTerrainData({
terrainMesh: upsampledMesh,
skirtHeight: upsampleSkirtHeight,
credits: credits,
});
return Promise.resolve(upsampledTerrainData);
});
}
const scratchUv0 = new Cartesian2();
const scratchUv1 = new Cartesian2();
const scratchUv2 = new Cartesian2();
const scratchBary = new Cartesian3();
/**
* Computes the terrain height at a specified longitude and latitude. Returns 0.0 if the position is outside the mesh.
* @private
* @param {TerrainMesh} mesh The terrain mesh.
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {number} longitude The longitude in radians.
* @param {number} latitude The latitude in radians.
* @returns {number} The terrain height at the specified position. If the position is outside the rectangle, this method will extrapolate the height, which is likely to be wildly incorrect for positions far outside the rectangle.
*/
function interpolateMeshHeight(mesh, rectangle, longitude, latitude) {
const u = CesiumMath.clamp(
(longitude - rectangle.west) / rectangle.width,
0.0,
1.0,
);
const v = CesiumMath.clamp(
(latitude - rectangle.south) / rectangle.height,
0.0,
1.0,
);
const { vertices, encoding, indices } = mesh;
for (let i = 0; i < mesh.indexCountWithoutSkirts; i += 3) {
const i0 = indices[i];
const i1 = indices[i + 1];
const i2 = indices[i + 2];
const uv0 = encoding.decodeTextureCoordinates(vertices, i0, scratchUv0);
const uv1 = encoding.decodeTextureCoordinates(vertices, i1, scratchUv1);
const uv2 = encoding.decodeTextureCoordinates(vertices, i2, scratchUv2);
const minU = Math.min(uv0.x, uv1.x, uv2.x);
const maxU = Math.max(uv0.x, uv1.x, uv2.x);
const minV = Math.min(uv0.y, uv1.y, uv2.y);
const maxV = Math.max(uv0.y, uv1.y, uv2.y);
if (u >= minU && u <= maxU && v >= minV && v <= maxV) {
const barycentric = Intersections2D.computeBarycentricCoordinates(
u,
v,
uv0.x,
uv0.y,
uv1.x,
uv1.y,
uv2.x,
uv2.y,
scratchBary,
);
if (
barycentric.x >= 0.0 &&
barycentric.y >= 0.0 &&
barycentric.z >= 0.0
) {
const h0 = encoding.decodeHeight(vertices, i0);
const h1 = encoding.decodeHeight(vertices, i1);
const h2 = encoding.decodeHeight(vertices, i2);
const height =
barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
return height;
}
}
}
// Position does not lie in any triangle in this mesh.
// This should not happen often since we start by clamping to the rectangle.
return 0.0;
}
export default Cesium3DTilesTerrainData;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -94,16 +94,12 @@ import TerrainProvider from "./TerrainProvider.js";
* @see GoogleEarthEnterpriseTerrainData
*/
function HeightmapTerrainData(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
if (!defined(options) || !defined(options.buffer)) {
throw new DeveloperError("options.buffer is required.");
}
if (!defined(options.width)) {
throw new DeveloperError("options.width is required.");
}
if (!defined(options.height)) {
throw new DeveloperError("options.height is required.");
}
Check.typeOf.object("options.buffer", options.buffer);
Check.typeOf.number("options.width", options.width);
Check.typeOf.number("options.height", options.height);
//>>includeEnd('debug');
this._buffer = options.buffer;
@ -158,7 +154,7 @@ Object.defineProperties(HeightmapTerrainData.prototype, {
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof HeightmapTerrainData.prototype
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|undefined}
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
*/
waterMask: {
get: function () {
@ -645,18 +641,10 @@ HeightmapTerrainData.prototype.isChildAvailable = function (
childY,
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(thisX)) {
throw new DeveloperError("thisX is required.");
}
if (!defined(thisY)) {
throw new DeveloperError("thisY is required.");
}
if (!defined(childX)) {
throw new DeveloperError("childX is required.");
}
if (!defined(childY)) {
throw new DeveloperError("childY is required.");
}
Check.typeOf.number("thisX", thisX);
Check.typeOf.number("thisY", thisY);
Check.typeOf.number("childX", childX);
Check.typeOf.number("childY", childY);
//>>includeEnd('debug');
let bitNumber = 2; // northwest child

View File

@ -92,52 +92,26 @@ import TerrainMesh from "./TerrainMesh.js";
* @see GoogleEarthEnterpriseTerrainData
*/
function QuantizedMeshTerrainData(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug)
if (!defined(options) || !defined(options.quantizedVertices)) {
throw new DeveloperError("options.quantizedVertices is required.");
}
if (!defined(options.indices)) {
throw new DeveloperError("options.indices is required.");
}
if (!defined(options.minimumHeight)) {
throw new DeveloperError("options.minimumHeight is required.");
}
if (!defined(options.maximumHeight)) {
throw new DeveloperError("options.maximumHeight is required.");
}
if (!defined(options.maximumHeight)) {
throw new DeveloperError("options.maximumHeight is required.");
}
if (!defined(options.boundingSphere)) {
throw new DeveloperError("options.boundingSphere is required.");
}
if (!defined(options.horizonOcclusionPoint)) {
throw new DeveloperError("options.horizonOcclusionPoint is required.");
}
if (!defined(options.westIndices)) {
throw new DeveloperError("options.westIndices is required.");
}
if (!defined(options.southIndices)) {
throw new DeveloperError("options.southIndices is required.");
}
if (!defined(options.eastIndices)) {
throw new DeveloperError("options.eastIndices is required.");
}
if (!defined(options.northIndices)) {
throw new DeveloperError("options.northIndices is required.");
}
if (!defined(options.westSkirtHeight)) {
throw new DeveloperError("options.westSkirtHeight is required.");
}
if (!defined(options.southSkirtHeight)) {
throw new DeveloperError("options.southSkirtHeight is required.");
}
if (!defined(options.eastSkirtHeight)) {
throw new DeveloperError("options.eastSkirtHeight is required.");
}
if (!defined(options.northSkirtHeight)) {
throw new DeveloperError("options.northSkirtHeight is required.");
}
Check.typeOf.object("options.quantizedVertices", options.quantizedVertices);
Check.typeOf.object("options.indices", options.indices);
Check.typeOf.number("options.minimumHeight", options.minimumHeight);
Check.typeOf.number("options.maximumHeight", options.maximumHeight);
Check.typeOf.object("options.boundingSphere", options.boundingSphere);
Check.typeOf.object(
"options.horizonOcclusionPoint",
options.horizonOcclusionPoint,
);
Check.typeOf.object("options.westIndices", options.westIndices);
Check.typeOf.object("options.southIndices", options.southIndices);
Check.typeOf.object("options.eastIndices", options.eastIndices);
Check.typeOf.object("options.northIndices", options.northIndices);
Check.typeOf.number("options.westSkirtHeight", options.westSkirtHeight);
Check.typeOf.number("options.southSkirtHeight", options.southSkirtHeight);
Check.typeOf.number("options.eastSkirtHeight", options.eastSkirtHeight);
Check.typeOf.number("options.northSkirtHeight", options.northSkirtHeight);
//>>includeEnd('debug');
this._quantizedVertices = options.quantizedVertices;
@ -729,18 +703,10 @@ QuantizedMeshTerrainData.prototype.isChildAvailable = function (
childY,
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(thisX)) {
throw new DeveloperError("thisX is required.");
}
if (!defined(thisY)) {
throw new DeveloperError("thisY is required.");
}
if (!defined(childX)) {
throw new DeveloperError("childX is required.");
}
if (!defined(childY)) {
throw new DeveloperError("childY is required.");
}
Check.typeOf.number("thisX", thisX);
Check.typeOf.number("thisY", thisY);
Check.typeOf.number("childX", childX);
Check.typeOf.number("childY", childY);
//>>includeEnd('debug');
let bitNumber = 2; // northwest child

View File

@ -2025,6 +2025,12 @@ Resource._Implementations.createImage = function (
* Wrapper for createImageBitmap
*
* @private
* @param {Blob} blob The image blob.
* @param {object} options An object containing the following properties:
* @param {boolean} options.flipY Whether to flip the image Y axis.
* @param {boolean} options.premultiplyAlpha Whether to premultiply the alpha channel.
* @param {boolean} options.skipColorSpaceConversion Whether to skip color space conversion.
* @returns {Promise<ImageBitmap>} A promise that resolves to the created image bitmap.
*/
Resource.createImageBitmapFromBlob = function (blob, options) {
Check.defined("options", options);

View File

@ -10,6 +10,7 @@ import DeveloperError from "./DeveloperError.js";
* @see HeightmapTerrainData
* @see QuantizedMeshTerrainData
* @see GoogleEarthEnterpriseTerrainData
* @see Cesium3DTilesTerrainData
*/
function TerrainData() {
DeveloperError.throwInstantiationError();
@ -29,7 +30,7 @@ Object.defineProperties(TerrainData.prototype, {
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof TerrainData.prototype
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|undefined}
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
*/
waterMask: {
get: DeveloperError.throwInstantiationError,

View File

@ -76,32 +76,36 @@ function TerrainEncoding(
quantization = TerrainQuantization.NONE;
}
toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
// Scale and bias from [0,1] to [ENU min, ENU max]
// Also compute the inverse of the scale and bias
let st = Matrix4.fromScale(dimensions, matrix4Scratch);
st = Matrix4.setTranslation(st, minimum, st);
const translation = Cartesian3.negate(minimum, cartesian3Scratch);
Matrix4.multiply(
Matrix4.fromTranslation(translation, matrix4Scratch),
toENU,
toENU,
let invSt = Matrix4.fromScale(
Cartesian3.fromElements(
1.0 / dimensions.x,
1.0 / dimensions.y,
1.0 / dimensions.z,
cartesian3Scratch,
),
matrix4Scratch2,
);
invSt = Matrix4.multiplyByTranslation(
invSt,
Cartesian3.negate(minimum, cartesian3Scratch),
invSt,
);
const scale = cartesian3Scratch;
scale.x = 1.0 / dimensions.x;
scale.y = 1.0 / dimensions.y;
scale.z = 1.0 / dimensions.z;
Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch), toENU, toENU);
matrix = Matrix4.clone(fromENU, new Matrix4());
let rtcOffset = Matrix4.getTranslation(fromENU, cartesian3Scratch);
rtcOffset = Cartesian3.subtract(rtcOffset, center, cartesian3Scratch);
matrix = Matrix4.setTranslation(matrix, rtcOffset, matrix);
matrix = Matrix4.multiply(matrix, st, matrix);
matrix = Matrix4.clone(fromENU);
Matrix4.setTranslation(matrix, Cartesian3.ZERO, matrix);
toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
toENU = Matrix4.multiply(invSt, toENU, toENU);
fromENU = Matrix4.clone(fromENU, new Matrix4());
const translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch);
const scaleMatrix = Matrix4.fromScale(dimensions, matrix4Scratch2);
const st = Matrix4.multiply(translationMatrix, scaleMatrix, matrix4Scratch);
Matrix4.multiply(fromENU, st, fromENU);
Matrix4.multiply(matrix, st, matrix);
fromENU = Matrix4.multiply(fromENU, st, new Matrix4());
}
/**
@ -570,6 +574,23 @@ TerrainEncoding.prototype.getOctEncodedNormal = function (
return Cartesian2.fromElements(x, y, result);
};
/**
* @param {Float32Array} buffer
* @param {number} index
* @param {Cartesian3} result
* @returns {Cartesian3}
*/
TerrainEncoding.prototype.decodeNormal = function (buffer, index, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("buffer", buffer);
Check.typeOf.number("index", index);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
const bufferIndex = (index = index * this.stride + this._offsetVertexNormal);
return AttributeCompression.octDecodeFloat(buffer[bufferIndex], result);
};
/**
* Decode a geodetic surface normal from the vertex buffer.
*

View File

@ -15,6 +15,8 @@ import CesiumMath from "./Math.js";
* @see CesiumTerrainProvider
* @see VRTheWorldTerrainProvider
* @see GoogleEarthEnterpriseTerrainProvider
* @see ArcGISTiledElevationTerrainProvider
* @see Cesium3DTilesTerrainProvider
*/
function TerrainProvider() {
DeveloperError.throwInstantiationError();
@ -236,7 +238,80 @@ TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices = function (
};
/**
* Calculates the number of skirt vertices given the edge indices.
* @private
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth Edge indices along the west side of the tile.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest Edge indices along the south side of the tile.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth Edge indices along the east side of the tile.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast Edge indices along the north side of the tile.
* @returns {number} The number of skirt vertices.
*/
TerrainProvider.getSkirtVertexCount = function (
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
) {
return (
westIndicesSouthToNorth.length +
southIndicesEastToWest.length +
eastIndicesNorthToSouth.length +
northIndicesWestToEast.length
);
};
/**
* Compute the number of skirt indices given the number of skirt vertices.
* Consider a 3x3 grid of vertices. There will be 8 skirt vertices around the edge:
* - 16 edge triangles
* - 48 indices
*
* |\|\|
* |/| |/|
* |/| |/|
* |\|\|
*
* @private
* @param {number} skirtVertexCount
* @returns {number}
*/
TerrainProvider.getSkirtIndexCount = function (skirtVertexCount) {
return (skirtVertexCount - 4) * 2 * 3;
};
/**
* Compute the number of skirt indices given the number of skirt vertices with filled corners.
* Consider a 3x3 grid of vertices. There will be 8 skirt vertices around the edge:
* - 16 edge triangles
* - 4 cap triangles
* - 60 indices
*
* /|\|\|\
* |/| |/|
* |/| |/|
* \|\|\|/
*
* @private
* @param {number} skirtVertexCount
* @returns {number}
*/
TerrainProvider.getSkirtIndexCountWithFilledCorners = function (
skirtVertexCount,
) {
return ((skirtVertexCount - 4) * 2 + 4) * 3;
};
/**
* Adds skirt indices.
* This does not add filled corners. Use {@link TerrainProvider.addSkirtIndicesWithFilledCorners} to add skirt indices with filled corners.
* @private
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth The indices of the vertices on the Western edge of the tile, ordered from South to North.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest The indices of the vertices on the Southern edge of the tile, ordered from East to West.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth The indices of the vertices on the Eastern edge of the tile, ordered from North to South.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast The indices of the vertices on the Northern edge of the tile, ordered from West to East.
* @param {number} vertexCount The number of vertices in the tile before adding skirt vertices.
* @param {Uint16Array|Uint32Array} indices The array of indices to which skirt indices are added.
* @param {number} offset The offset into the indices array at which to start adding skirt indices.
*/
TerrainProvider.addSkirtIndices = function (
westIndicesSouthToNorth,
@ -272,6 +347,82 @@ TerrainProvider.addSkirtIndices = function (
addSkirtIndices(northIndicesWestToEast, vertexIndex, indices, offset);
};
/**
* Adds skirt indices with filled corners.
* @private
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth The indices of the vertices on the Western edge of the tile, ordered from South to North.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest The indices of the vertices on the Southern edge of the tile, ordered from East to West.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth The indices of the vertices on the Eastern edge of the tile, ordered from North to South.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast The indices of the vertices on the Northern edge of the tile, ordered from West to East.
* @param {number} vertexCount The number of vertices in the tile before adding skirt vertices.
* @param {Uint16Array|Uint32Array} indices The array of indices to which skirt indices are added.
* @param {number} offset The offset into the indices array at which to start adding skirt indices.
*/
TerrainProvider.addSkirtIndicesWithFilledCorners = function (
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
vertexCount,
indices,
offset,
) {
// Add skirt indices without filled corners
TerrainProvider.addSkirtIndices(
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
vertexCount,
indices,
offset,
);
const skirtVertexCount = TerrainProvider.getSkirtVertexCount(
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
);
const skirtIndexCountWithoutCaps =
TerrainProvider.getSkirtIndexCount(skirtVertexCount);
const cornerStartIdx = offset + skirtIndexCountWithoutCaps;
const cornerSWIndex = westIndicesSouthToNorth[0];
const cornerNWIndex = northIndicesWestToEast[0];
const cornerNEIndex = eastIndicesNorthToSouth[0];
const cornerSEIndex = southIndicesEastToWest[0];
// Indices based on edge order in addSkirtIndices
const westSouthIndex = vertexCount;
const westNorthIndex = westSouthIndex + westIndicesSouthToNorth.length - 1;
const southEastIndex = westNorthIndex + 1;
const southWestIndex = southEastIndex + southIndicesEastToWest.length - 1;
const eastNorthIndex = southWestIndex + 1;
const eastSouthIndex = eastNorthIndex + eastIndicesNorthToSouth.length - 1;
const northWestIndex = eastSouthIndex + 1;
const northEastIndex = northWestIndex + northIndicesWestToEast.length - 1;
// Connect the corner vertices with the skirt vertices extending from the corner
indices[cornerStartIdx + 0] = cornerSWIndex;
indices[cornerStartIdx + 1] = westSouthIndex;
indices[cornerStartIdx + 2] = southWestIndex;
indices[cornerStartIdx + 3] = cornerSEIndex;
indices[cornerStartIdx + 4] = southEastIndex;
indices[cornerStartIdx + 5] = eastSouthIndex;
indices[cornerStartIdx + 6] = cornerNEIndex;
indices[cornerStartIdx + 7] = eastNorthIndex;
indices[cornerStartIdx + 8] = northEastIndex;
indices[cornerStartIdx + 9] = cornerNWIndex;
indices[cornerStartIdx + 10] = northWestIndex;
indices[cornerStartIdx + 11] = westNorthIndex;
};
function getEdgeIndices(width, height) {
const westIndicesSouthToNorth = new Array(height);
const southIndicesEastToWest = new Array(width);

View File

@ -4,7 +4,7 @@ import Check from "./Check.js";
* Finds an item in a sorted array.
*
* @function
* @param {Array} array The sorted array to search.
* @param {Array|Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} array The sorted array to search.
* @param {*} itemToFind The item to find in the array.
* @param {binarySearchComparator} comparator The function to use to compare the item to
* elements in the array.

View File

@ -3,12 +3,18 @@ import defined from "./defined.js";
import Resource from "./Resource.js";
/**
* Loads an image from a typed array.
* @param {Object} options An object containing the following properties:
* @param {Uint8Array} options.uint8Array The typed array containing the image data.
* @param {string} options.format The MIME format of the image (e.g., "image/png").
* @param {Request} [options.request] The request object to use to fetch the image.
* @param {boolean} [options.flipY=false] Whether to flip the image vertically.
* @param {boolean} [options.skipColorSpaceConversion=false] Whether to skip color space conversion.
* @returns {Promise<HTMLImageElement|HTMLCanvasElement|ImageBitmap>|undefined} A promise that resolves to the loaded image. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
* @private
*/
function loadImageFromTypedArray(options) {
const uint8Array = options.uint8Array;
const format = options.format;
const request = options.request;
const { uint8Array, format, request } = options;
const flipY = options.flipY ?? false;
const skipColorSpaceConversion = options.skipColorSpaceConversion ?? false;
//>>includeStart('debug', pragmas.debug);

View File

@ -56,7 +56,7 @@ function sort(array, compare, userDefinedObject, start, end) {
* A stable merge sort.
*
* @function mergeSort
* @param {Array} array The array to sort.
* @param {Array|Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} array The array to sort.
* @param {mergeSortComparator} comparator The function to use to compare elements in the array.
* @param {*} [userDefinedObject] Any item to pass as the third parameter to <code>comparator</code>.
*

View File

@ -929,7 +929,15 @@ function createWaterMaskTextureIfNeeded(context, surfaceTile) {
let texture;
const waterMaskLength = waterMask.length;
if (waterMaskLength === 1) {
if (waterMask instanceof ImageBitmap) {
texture = Texture.create({
context: context,
source: waterMask,
sampler: waterMaskData.sampler,
flipY: false,
skipColorSpaceConversion: true,
});
} else if (waterMaskLength === 1) {
// Length 1 means the tile is entirely land or entirely water.
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
if (waterMask[0] !== 0) {

View File

@ -125,6 +125,12 @@ Object.defineProperties(GltfBufferViewLoader.prototype, {
},
});
/**
* Load the resources associated with the loader.
* @private
* @param {GltfBufferViewLoader} loader
* @returns {Promise<GltfBufferViewLoader>}
*/
async function loadResources(loader) {
try {
const bufferLoader = getBufferLoader(loader);
@ -190,9 +196,18 @@ GltfBufferViewLoader.prototype.load = async function () {
return this._promise;
};
/**
* Get the buffer loader for the specified buffer view loader.
* Attempts to retrieve from the resource cache first. If a buffer loader is
* not found, creates a new buffer loader and adds it to the resource cache.
* @private
* @param {GltfBufferViewLoader} bufferViewLoader The loader.
* @returns {BufferLoader} The buffer loader.
*/
function getBufferLoader(bufferViewLoader) {
const resourceCache = bufferViewLoader._resourceCache;
const buffer = bufferViewLoader._buffer;
if (defined(buffer.uri)) {
const baseResource = bufferViewLoader._baseResource;
const resource = baseResource.getDerivedResource({
@ -202,9 +217,12 @@ function getBufferLoader(bufferViewLoader) {
resource: resource,
});
}
const source = buffer.extras?._pipeline?.source;
return resourceCache.getEmbeddedBufferLoader({
parentResource: bufferViewLoader._gltfResource,
bufferId: bufferViewLoader._bufferId,
typedArray: source,
});
}

View File

@ -141,7 +141,7 @@ void main()
#elif defined(INCLUDE_WEB_MERCATOR_Y)
float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;
float encodedNormal = 0.0;
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL) || defined(APPLY_MATERIAL)
float webMercatorT = textureCoordinates.y;
float encodedNormal = compressed0.w;
#else

View File

@ -0,0 +1,46 @@
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
/**
* @private
*
* @param {Cesium3DTilesTerrainGeometryProcessor.CreateMeshOptions} options An object describing options for mesh creation.
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
* @returns {Promise<object>} A promise that resolves to an object containing selected info from the created TerrainMesh.
*/
function createVerticesFromCesium3DTilesTerrain(options, transferableObjects) {
const meshPromise = Cesium3DTilesTerrainGeometryProcessor.createMesh(options);
return meshPromise.then(function (mesh) {
const verticesBuffer = mesh.vertices.buffer;
const indicesBuffer = mesh.indices.buffer;
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
transferableObjects.push(
verticesBuffer,
indicesBuffer,
westIndicesBuffer,
southIndicesBuffer,
eastIndicesBuffer,
northIndicesBuffer,
);
return {
verticesBuffer: verticesBuffer,
indicesBuffer: indicesBuffer,
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
encoding: mesh.encoding,
westIndicesBuffer: westIndicesBuffer,
southIndicesBuffer: southIndicesBuffer,
eastIndicesBuffer: eastIndicesBuffer,
northIndicesBuffer: northIndicesBuffer,
};
});
}
export default createTaskProcessorWorker(
createVerticesFromCesium3DTilesTerrain,
);

View File

@ -0,0 +1,55 @@
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
/**
* @private
* @param {Cesium3DTilesTerrainGeometryProcessor.UpsampleMeshOptions} options An object describing options for mesh upsampling.
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
* @returns {TerrainMeshProxy} An object containing selected info from the upsampled TerrainMesh.
*/
function upsampleVerticesFromCesium3DTilesTerrain(
options,
transferableObjects,
) {
const mesh = Cesium3DTilesTerrainGeometryProcessor.upsampleMesh(options);
const verticesBuffer = mesh.vertices.buffer;
const indicesBuffer = mesh.indices.buffer;
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
transferableObjects.push(
verticesBuffer,
indicesBuffer,
westIndicesBuffer,
southIndicesBuffer,
eastIndicesBuffer,
northIndicesBuffer,
);
/** @type {TerrainMeshProxy} */
const result = {
verticesBuffer: verticesBuffer,
indicesBuffer: indicesBuffer,
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
encoding: mesh.encoding,
westIndicesBuffer: westIndicesBuffer,
southIndicesBuffer: southIndicesBuffer,
eastIndicesBuffer: eastIndicesBuffer,
northIndicesBuffer: northIndicesBuffer,
minimumHeight: mesh.minimumHeight,
maximumHeight: mesh.maximumHeight,
boundingSphere: mesh.boundingSphere3D,
orientedBoundingBox: mesh.orientedBoundingBox,
horizonOcclusionPoint: mesh.horizonOcclusionPoint,
};
return result;
}
export default createTaskProcessorWorker(
upsampleVerticesFromCesium3DTilesTerrain,
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,91 @@
import {
Cesium3DTilesTerrainProvider,
Resource,
TerrainProvider,
} from "../../index.js";
describe("Core/Cesium3DTilesTerrainProvider", function () {
it("conforms to TerrainProvider interface", function () {
expect(Cesium3DTilesTerrainProvider).toConformToInterface(TerrainProvider);
});
it("fromUrl throws if url is not provided", async function () {
await expectAsync(
Cesium3DTilesTerrainProvider.fromUrl(),
).toBeRejectedWithDeveloperError(
"url is required, actual value was undefined",
);
});
it("fromUrl rejects when url rejects", async function () {
const error = new Error();
await expectAsync(
Cesium3DTilesTerrainProvider.fromUrl(Promise.reject(error)),
).toBeRejectedWithError();
});
it("fromUrl rejects when url is invalid", async function () {
const path = "made/up/url";
await expectAsync(
Cesium3DTilesTerrainProvider.fromUrl(path),
).toBeRejectedWithError();
});
it("fromUrl works with path", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path);
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
});
it("fromUrl when url promise is used", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const provider = await Cesium3DTilesTerrainProvider.fromUrl(
Promise.resolve(path),
);
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
});
it("fromUrl works with Resource", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const resource = new Resource(path);
const provider = await Cesium3DTilesTerrainProvider.fromUrl(resource);
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
});
it("logo is undefined if credit is not provided", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path);
expect(provider.credit).toBeUndefined();
});
it("logo is defined if credit is provided", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const credit = "test";
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path, {
credit: credit,
});
expect(provider.credit).toBeDefined();
expect(provider.credit.html).toEqual("test");
});
it("has a water mask when requested", async function () {
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path, {
requestWaterMask: true,
});
expect(provider.hasWaterMask).toBe(true);
});
});

View File

@ -0,0 +1,6 @@
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>

View File

@ -0,0 +1,93 @@
import * as Cesium from "cesium";
const terrainProvider =
await Cesium.Cesium3DTilesTerrainProvider.fromIonAssetId(3923568, {
requestVertexNormals: true, // Needed for hillshade lighting
requestWaterMask: true, // Needed to distinguish land from water
});
const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: terrainProvider,
scene3DOnly: true,
sceneModePicker: false,
navigationHelpButton: false,
});
// Create a globe material for shading elevation only on land
const customElevationMaterial = new Cesium.Material({
fabric: {
type: "ElevationLand",
materials: {
waterMaskMaterial: {
type: "WaterMask",
},
elevationRampMaterial: {
type: "ElevationRamp",
},
},
components: {
diffuse: "elevationRampMaterial.diffuse",
alpha: "1.0 - waterMaskMaterial.alpha", // We'll need the inverse of the watermask to shade land
},
},
translucent: false,
});
const minHeight = -414.0; // approximate dead sea elevation
const maxHeight = 8777.0; // approximate everest elevation
const elevationRamp = [0.0, 0.045, 0.45, 0.5, 0.55, 1.0];
function getColorRamp() {
const ramp = document.createElement("canvas");
ramp.width = 100;
ramp.height = 1;
const ctx = ramp.getContext("2d");
const values = elevationRamp;
const grd = ctx.createLinearGradient(0, 0, 100, 0);
// See https://gis.stackexchange.com/questions/25099/choosing-colour-ramp-to-use-for-elevation
grd.addColorStop(values[0], "#344f31");
grd.addColorStop(values[1], "#5b8742");
grd.addColorStop(values[2], "#e6daa5");
grd.addColorStop(values[3], "#fdc771");
grd.addColorStop(values[4], "#b99d89");
grd.addColorStop(values[5], "#f0f0f0");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 100, 1);
return ramp;
}
const globe = viewer.scene.globe;
const material = customElevationMaterial;
const shadingUniforms = material.materials.elevationRampMaterial.uniforms;
globe.showWaterEffect = false;
globe.enableLighting = true;
shadingUniforms.minimumHeight = minHeight;
shadingUniforms.maximumHeight = maxHeight;
shadingUniforms.image = getColorRamp();
globe.material = material;
// Light the scene with a hillshade effect similar to https://pro.arcgis.com/en/pro-app/latest/tool-reference/3d-analyst/how-hillshade-works.htm
const scene = viewer.scene;
scene.light = new Cesium.DirectionalLight({
direction: new Cesium.Cartesian3(1, 0, 0), // Updated every frame
});
// Update the light position base on the camera
const scratchNormal = new Cesium.Cartesian3();
scene.preRender.addEventListener(function (scene, time) {
const surfaceNormal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(
scene.camera.positionWC,
scratchNormal,
);
const negativeNormal = Cesium.Cartesian3.negate(surfaceNormal, surfaceNormal);
scene.light.direction = Cesium.Cartesian3.normalize(
Cesium.Cartesian3.add(negativeNormal, scene.camera.rightWC, surfaceNormal),
scene.light.direction,
);
});

View File

@ -0,0 +1,8 @@
legacyId: Globe Materials 3D Tiles Terrain.html
title: Globe Materials 3D Tiles Terrain
description: Load a 3D Tiles tileset as terrain, and apply globe material properties to style the terrain.
labels:
- 3D Tiles
- Terrain
- Showcases
thumbnail: thumbnail.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -174,6 +174,7 @@ const throttle = (callback) => {
/\.glb/,
/\.geom/,
/\.vctr/,
/\.subtree/,
/tileset.*\.json$/,
];
app.get(knownTilesetFormats, checkGzipAndNext);