Merge branch 'main' into billboard-terrain-clip-fix
|
|
@ -1,8 +1,8 @@
|
|||
name: prod
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'cesium.com'
|
||||
branches:
|
||||
- "cesium.com"
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
- name: install node 22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: "22"
|
||||
- name: npm install
|
||||
run: npm install
|
||||
- name: lint *.js
|
||||
|
|
@ -36,13 +36,17 @@ jobs:
|
|||
- name: install node 22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: "22"
|
||||
- name: npm install
|
||||
run: npm install
|
||||
- name: build website release
|
||||
run: npm run website-release
|
||||
- name: build apps
|
||||
run: npm run build-apps
|
||||
- name: build types
|
||||
run: npm run build-ts
|
||||
- name: build prod sandcastle
|
||||
run: npm run build-prod -w packages/sandcastle -- -l warn
|
||||
- name: deploy to cesium.com
|
||||
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
||||
run: |
|
||||
|
|
@ -51,4 +55,4 @@ jobs:
|
|||
aws s3 sync Build/release/ s3://cesium-website/cesiumjs/releases/$(cat package.json | jq -r '.version' | sed 's/\.0$//')/ --cache-control "public, max-age=1800" --delete
|
||||
aws s3 sync Build/Documentation/ s3://cesium-website/cesiumjs/ref-doc/ --cache-control "public, max-age=1800" --delete
|
||||
aws s3 sync Build/CesiumViewer/ s3://cesium-website/cesiumjs/cesium-viewer/ --cache-control "public, max-age=1800" --delete
|
||||
aws s3 sync Build/Sandcastle/ s3://cesium-sandcastle-website/ --cache-control "public, max-age=1800" --delete
|
||||
aws s3 sync Build/Sandcastle2/ s3://cesium-sandcastle-website/ --cache-control "public, max-age=1800" --delete
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
<!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="Imagery tiles from Google Maps with additional parameters to create overlays and custom styles."
|
||||
/>
|
||||
<meta name="cesium-sandcastle-labels" content="Beginner, Showcases" />
|
||||
<title>Cesium Demo</title>
|
||||
<script type="text/javascript" src="../Sandcastle-header.js"></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>
|
||||
<script id="cesium_sandcastle_script">
|
||||
window.startup = async function (Cesium) {
|
||||
"use strict";
|
||||
//Sandcastle_Begin
|
||||
const assetId = 3830184;
|
||||
|
||||
const base = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
assetId,
|
||||
mapType: "satellite",
|
||||
}),
|
||||
);
|
||||
|
||||
const overlay = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
assetId,
|
||||
overlayLayerType: "layerRoadmap",
|
||||
styles: [
|
||||
{
|
||||
stylers: [{ hue: "#00ffe6" }, { saturation: -20 }],
|
||||
},
|
||||
{
|
||||
featureType: "road",
|
||||
elementType: "geometry",
|
||||
stylers: [{ lightness: 100 }, { visibility: "simplified" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(base);
|
||||
viewer.imageryLayers.add(overlay);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
});
|
||||
//Sandcastle_End
|
||||
Sandcastle.finishedLoading();
|
||||
};
|
||||
if (typeof Cesium !== "undefined") {
|
||||
window.startupCalled = true;
|
||||
window.startup(Cesium).catch((error) => {
|
||||
"use strict";
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -0,0 +1,69 @@
|
|||
<!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="Global imagery data from Google Maps." />
|
||||
<meta name="cesium-sandcastle-labels" content="Beginner, Showcases" />
|
||||
<title>Cesium Demo</title>
|
||||
<script type="text/javascript" src="../Sandcastle-header.js"></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>
|
||||
<script id="cesium_sandcastle_script">
|
||||
window.startup = async function (Cesium) {
|
||||
"use strict";
|
||||
//Sandcastle_Begin
|
||||
const assetId = 3830184;
|
||||
|
||||
const google = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(google);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
}); //Sandcastle_End
|
||||
Sandcastle.finishedLoading();
|
||||
};
|
||||
if (typeof Cesium !== "undefined") {
|
||||
window.startupCalled = true;
|
||||
window.startup(Cesium).catch((error) => {
|
||||
"use strict";
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,98 @@
|
|||
<!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="Global imagery assets available from Cesium ion." />
|
||||
<meta name="cesium-sandcastle-labels" content="Beginner, Showcases" />
|
||||
<title>Cesium Demo</title>
|
||||
<script type="text/javascript" src="../Sandcastle-header.js"></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>
|
||||
<script id="cesium_sandcastle_script">
|
||||
window.startup = async function (Cesium) {
|
||||
"use strict";
|
||||
//Sandcastle_Begin
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
const menuOptions = [];
|
||||
|
||||
const dropdownOptions = [
|
||||
{ label: "Google Maps 2D Contour", assetId: 3830186 },
|
||||
{ label: "Google Maps 2D Labels Only", assetId: 3830185 },
|
||||
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
|
||||
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
|
||||
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
|
||||
{ label: "Bing Maps Aerial", assetId: 2 },
|
||||
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
|
||||
{ label: "Bing Maps Road", assetId: 4 },
|
||||
{ label: "Bing Maps Labels Only", assetId: 2411391 },
|
||||
{ label: "Sentinel-2", assetId: 3954 },
|
||||
];
|
||||
|
||||
function showLayer(assetId) {
|
||||
viewer.imageryLayers.removeAll(true);
|
||||
const layer = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
viewer.imageryLayers.add(layer);
|
||||
}
|
||||
|
||||
dropdownOptions.forEach((opt) => {
|
||||
const option = {
|
||||
text: opt.label,
|
||||
onselect: function () {
|
||||
showLayer(opt.assetId);
|
||||
},
|
||||
};
|
||||
menuOptions.push(option);
|
||||
});
|
||||
|
||||
Sandcastle.addToolbarMenu(menuOptions);
|
||||
|
||||
showLayer(3830186);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
}); //Sandcastle_End
|
||||
Sandcastle.finishedLoading();
|
||||
};
|
||||
if (typeof Cesium !== "undefined") {
|
||||
window.startupCalled = true;
|
||||
window.startup(Cesium).catch((error) => {
|
||||
"use strict";
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
|
@ -80,15 +80,17 @@
|
|||
this.names = ["color"];
|
||||
this.types = [Cesium.MetadataType.VEC4];
|
||||
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
|
||||
this._levelCount = 3;
|
||||
this.availableLevels = 3;
|
||||
this.globalTransform = globalTransform;
|
||||
}
|
||||
|
||||
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
|
||||
const { tileLevel, tileX, tileY, tileZ } = options;
|
||||
|
||||
if (tileLevel >= this._levelCount) {
|
||||
return Promise.reject(`No tiles available beyond level ${this._levelCount}`);
|
||||
if (tileLevel >= this.availableLevels) {
|
||||
return Promise.reject(
|
||||
`No tiles available beyond level ${this.availableLevels - 1}`,
|
||||
);
|
||||
}
|
||||
|
||||
const dimensions = this.dimensions;
|
||||
|
|
@ -174,6 +176,7 @@
|
|||
customShader: customShader,
|
||||
});
|
||||
voxelPrimitive.nearestSampling = true;
|
||||
voxelPrimitive.stepSize = 0.7;
|
||||
|
||||
viewer.scene.primitives.add(voxelPrimitive);
|
||||
camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
|
||||
|
|
|
|||
|
|
@ -123,14 +123,14 @@
|
|||
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
|
||||
this.globalTransform = globalTransform;
|
||||
|
||||
this._levelCount = 2;
|
||||
this._allVoxelData = new Array(this._levelCount);
|
||||
this.availableLevels = 2;
|
||||
this._allVoxelData = new Array(this.availableLevels);
|
||||
|
||||
const allVoxelData = this._allVoxelData;
|
||||
const channelCount = Cesium.MetadataType.getComponentCount(this.types[0]);
|
||||
const { dimensions } = this;
|
||||
|
||||
for (let level = 0; level < this._levelCount; level++) {
|
||||
for (let level = 0; level < this.availableLevels; level++) {
|
||||
const dimAtLevel = Math.pow(2, level);
|
||||
const voxelCountX = dimensions.x * dimAtLevel;
|
||||
const voxelCountY = dimensions.y * dimAtLevel;
|
||||
|
|
@ -158,9 +158,9 @@
|
|||
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
|
||||
const { tileLevel, tileX, tileY, tileZ } = options;
|
||||
|
||||
if (tileLevel >= this._levelCount) {
|
||||
if (tileLevel >= this.availableLevels) {
|
||||
return Promise.reject(
|
||||
`No tiles available beyond level ${this._levelCount - 1}`,
|
||||
`No tiles available beyond level ${this.availableLevels - 1}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<!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="Global imagery data from Azure Maps." />
|
||||
<meta name="cesium-sandcastle-labels" content="Beginner, Showcases" />
|
||||
<title>Cesium Demo</title>
|
||||
<script type="text/javascript" src="../Sandcastle-header.js"></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>
|
||||
<script id="cesium_sandcastle_script">
|
||||
window.startup = async function (Cesium) {
|
||||
"use strict";
|
||||
//Sandcastle_Begin
|
||||
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
|
||||
Cesium.Ion.defaultAccessToken = "";
|
||||
|
||||
const assetId = 1683;
|
||||
|
||||
const azure = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(azure);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
}); //Sandcastle_End
|
||||
Sandcastle.finishedLoading();
|
||||
};
|
||||
if (typeof Cesium !== "undefined") {
|
||||
window.startupCalled = true;
|
||||
window.startup(Cesium).catch((error) => {
|
||||
"use strict";
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -81,7 +81,7 @@
|
|||
data-dojo-type="dijit.layout.ContentPane"
|
||||
data-dojo-props="region: 'top'"
|
||||
>
|
||||
<a href="https://dev-sandcastle.cesium.com"
|
||||
<a href="https://sandcastle.cesium.com"
|
||||
>Try the new version of Sandcastle today!</a
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
29
CHANGES.md
|
|
@ -2,20 +2,33 @@
|
|||
|
||||
## 1.134 - 2025-10-01
|
||||
|
||||
- [Sandcastle](https://sandcastle.cesium.com/) has been updated at `https://sandcastle.cesium.com`! The [legacy Sandcastle app](https://cesium.com/downloads/cesiumjs/releases/1.134/Apps/Sandcastle/index.html) will remain available through November 3, 2025.
|
||||
|
||||
### @cesium/engine
|
||||
|
||||
#### Fixes :wrench:
|
||||
#### Breaking Changes :mega:
|
||||
|
||||
- Materials loaded from type now respect submaterials present in the referenced material type. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
|
||||
- Reverts `createImageBitmap` options update to continue support for older browsers [#12846](https://github.com/CesiumGS/cesium/issues/12846)
|
||||
- Fix flickering artifact in Gaussian splat models caused by incorrect sorting results. [#12662](https://github.com/CesiumGS/cesium/issues/12662)
|
||||
- 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)
|
||||
- Voxel rendering now requires a WebGL2 context, which is [enabled by default since 1.101](https://github.com/CesiumGS/cesium/pull/10894). Make sure the `requestWebGl1` flag in `contextOptions` is NOT set to true.
|
||||
- The `defaultValue` function has been removed. Instead, use the [nullish coalescing (`??`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) operator. See the [Coding Guide](https://github.com/CesiumGS/cesium/tree/main/Documentation/Contributors/CodingGuide#default-parameter-values) for usage information and examples.
|
||||
- `defaultValue.EMPTY_OBJECT` has been removed. Instead, use `Frozen.EMPTY_OBJECT`. See the [Coding Guide](https://github.com/CesiumGS/cesium/tree/main/Documentation/Contributors/CodingGuide#default-parameter-values) for usage information and examples.
|
||||
|
||||
#### Additions :tada:
|
||||
|
||||
- Adds an async factory method for the Material class that allows callers to wait on resource loading. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
|
||||
- Added Google2DImageryProvider to load imagery from [Google Maps](https://developers.google.com/maps/documentation/tile/2d-tiles-overview) [#12913](https://github.com/CesiumGS/cesium/pull/12913)
|
||||
- Added an async factory method for the Material class that allows callers to wait on resource loading. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
|
||||
|
||||
#### Fixes :wrench:
|
||||
|
||||
- Fixed vertical misalignment of glyphs in labels with small fonts [#8474](https://github.com/CesiumGS/cesium/issues/8474)
|
||||
- Converted voxel raymarching to eye coordinates to fix precision issues in large datasets. [#12061](https://github.com/CesiumGS/cesium/issues/12061)
|
||||
- Fixed flickering artifact in Gaussian splat models caused by incorrect sorting results. [#12662](https://github.com/CesiumGS/cesium/issues/12662)
|
||||
- Fixed 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)
|
||||
- Fixed rendering for geometry entities when `requestRenderMode` is enabled. [#12841](https://github.com/CesiumGS/cesium/pull/12841)
|
||||
- Improved performance and reduced memory usage of `Event` class. [#12896](https://github.com/CesiumGS/cesium/pull/12896)
|
||||
- Improved performance of clamped labels. [#12905](https://github.com/CesiumGS/cesium/pull/12905)
|
||||
- Materials loaded from type now respect submaterials present in the referenced material type. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
|
||||
- Prevent runtime errors for certain forms of invalid PNTS files [#12872](https://github.com/CesiumGS/cesium/issues/12872)
|
||||
- Revert `createImageBitmap` options update to continue support for older browsers [#12846](https://github.com/CesiumGS/cesium/issues/12846)
|
||||
|
||||
## 1.133.1 - 2025-09-08
|
||||
|
||||
|
|
|
|||
|
|
@ -432,3 +432,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
|
|||
- [Pamela Augustine](https://github.com/pamelaAugustine)
|
||||
- [宋时旺](https://github.com/BlockCnFuture)
|
||||
- [Marco Zhan](https://github.com/marcoYxz)
|
||||
- [Mikhail Porotkin](https://github.com/porotkin)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"version": "2.7.73",
|
||||
"version": "2.8.4",
|
||||
"url": "https://www.npmjs.com/package/@zip.js/zip.js"
|
||||
},
|
||||
{
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"version": "3.2.6",
|
||||
"version": "3.2.7",
|
||||
"url": "https://www.npmjs.com/package/dompurify",
|
||||
"notes": "dompurify is available as both MPL-2.0 OR Apache-2.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -447,6 +447,13 @@ export const websiteRelease = gulp.series(
|
|||
node: false,
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return buildCesium({
|
||||
minify: true,
|
||||
removePragmas: true,
|
||||
node: false,
|
||||
});
|
||||
},
|
||||
combineForSandcastle,
|
||||
buildDocs,
|
||||
);
|
||||
|
|
@ -663,6 +670,7 @@ export const makeZip = gulp.series(release, async function createZipFile() {
|
|||
"!**/*.gitignore",
|
||||
"!Specs/e2e/*-snapshots/**",
|
||||
"!Apps/Sandcastle/gallery/development/**",
|
||||
"!Apps/Sandcastle2/**",
|
||||
],
|
||||
{
|
||||
encoding: false,
|
||||
|
|
|
|||
|
|
@ -32,12 +32,11 @@
|
|||
<a href="Apps/HelloWorld.html">Hello World</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="Apps/Sandcastle/index.html">Sandcastle</a>
|
||||
<a href="Apps/Sandcastle2/index.html">Sandcastle</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="Build/Apps/Sandcastle/index.html">Built Sandcastle</a>
|
||||
<a href="Apps/Sandcastle/index.html">Legacy Sandcastle</a>
|
||||
</li>
|
||||
<li><a href="Apps/Sandcastle2/index.html">Sandcastle v2</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cesium",
|
||||
"version": "1.133.1",
|
||||
"version": "1.134.0",
|
||||
"description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.",
|
||||
"homepage": "http://cesium.com/cesiumjs/",
|
||||
"license": "Apache-2.0",
|
||||
|
|
@ -51,8 +51,8 @@
|
|||
"./Specs/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@cesium/engine": "^20.0.1",
|
||||
"@cesium/widgets": "^13.1.1"
|
||||
"@cesium/engine": "^21.0.0",
|
||||
"@cesium/widgets": "^13.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cesium/eslint-config": "^12.0.0",
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ GoogleMaps.defaultApiKey = undefined;
|
|||
* Gets or sets the default Google Map Tiles API endpoint.
|
||||
*
|
||||
* @type {string|Resource}
|
||||
* @default https://tile.googleapis.com/v1/
|
||||
* @default https://tile.googleapis.com/
|
||||
*/
|
||||
GoogleMaps.mapTilesApiEndpoint = new Resource({
|
||||
url: "https://tile.googleapis.com/v1/",
|
||||
url: "https://tile.googleapis.com/",
|
||||
});
|
||||
|
||||
GoogleMaps.getDefaultCredit = function () {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Resource from "./Resource.js";
|
|||
|
||||
let defaultTokenCredit;
|
||||
const defaultAccessToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2NTEwZTU2Yi0wOGEyLTQyZjgtOTJjNi04Mzc2NGRlNzA4NTkiLCJpZCI6MjU5LCJpYXQiOjE3NTY4NDExOTJ9._Y3MIsYgGKTVTpkEpKPNT0cQSa_hUocY0DdH7h0U-xM";
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjNjI5ZTViNy0wY2FhLTQ0ZDUtYTIzMi0wMWEyMzZkYWYwYWYiLCJpZCI6MjU5LCJpYXQiOjE3NTkzNDcyNDZ9.xyOPig1igKFQvOTaXfTE0KQ7dU7jyn_c3OQPaQ1hEiI";
|
||||
/**
|
||||
* Default settings for accessing the Cesium ion API.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ function IonResource(endpoint, endpointResource) {
|
|||
retryAttempts: 1,
|
||||
retryCallback: retryCallback,
|
||||
};
|
||||
} else if (["GOOGLE_2D_MAPS", "AZURE_MAPS"].includes(externalType)) {
|
||||
options = {
|
||||
url: endpoint.options.url,
|
||||
retryAttempts: 1,
|
||||
retryCallback: retryCallback,
|
||||
};
|
||||
} else if (
|
||||
externalType === "3DTILES" ||
|
||||
externalType === "STK_TERRAIN_SERVER"
|
||||
|
|
@ -84,7 +90,7 @@ if (defined(Object.create)) {
|
|||
* @param {object} [options] An object with the following properties:
|
||||
* @param {string} [options.accessToken=Ion.defaultAccessToken] The access token to use.
|
||||
* @param {string|Resource} [options.server=Ion.defaultServer] The resource to the Cesium ion API server.
|
||||
* @returns {Promise<IonResource>} A Promise to am instance representing the Cesium ion Asset.
|
||||
* @returns {Promise<IonResource>} A Promise to an instance representing the Cesium ion Asset.
|
||||
*
|
||||
* @example
|
||||
* // Load a Cesium3DTileset with asset ID of 124624234
|
||||
|
|
@ -207,7 +213,7 @@ IonResource.prototype._makeRequest = function (options) {
|
|||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
**/
|
||||
IonResource._createEndpointResource = function (assetId, options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("assetId", assetId);
|
||||
|
|
@ -226,6 +232,13 @@ IonResource._createEndpointResource = function (assetId, options) {
|
|||
resourceOptions.queryParameters = { access_token: accessToken };
|
||||
}
|
||||
|
||||
if (defined(options.queryParameters)) {
|
||||
resourceOptions.queryParameters = {
|
||||
...resourceOptions.queryParameters,
|
||||
...options.queryParameters,
|
||||
};
|
||||
}
|
||||
|
||||
addClientHeaders(resourceOptions);
|
||||
|
||||
return server.getDerivedResource(resourceOptions);
|
||||
|
|
@ -267,9 +280,21 @@ function retryCallback(that, error) {
|
|||
ionRoot._pendingPromise = endpointResource
|
||||
.fetchJson()
|
||||
.then(function (newEndpoint) {
|
||||
//Set the token for root resource so new derived resources automatically pick it up
|
||||
// Set the token for root resource so new derived resources automatically pick it up
|
||||
ionRoot._ionEndpoint = newEndpoint;
|
||||
return newEndpoint;
|
||||
// Reset the session token for Google 2D imagery
|
||||
if (newEndpoint.externalType === "GOOGLE_2D_MAPS") {
|
||||
ionRoot.setQueryParameters({
|
||||
session: newEndpoint.options.session,
|
||||
key: newEndpoint.options.key,
|
||||
});
|
||||
}
|
||||
if (newEndpoint.externalType === "AZURE_MAPS") {
|
||||
ionRoot.setQueryParameters({
|
||||
"subscription-key": newEndpoint.options["subscription-key"],
|
||||
});
|
||||
}
|
||||
return ionRoot._ionEndpoint;
|
||||
})
|
||||
.finally(function (newEndpoint) {
|
||||
// Pass or fail, we're done with this promise, the next failure should use a new one.
|
||||
|
|
@ -284,4 +309,5 @@ function retryCallback(that, error) {
|
|||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export default IonResource;
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
import deprecationWarning from "./deprecationWarning.js";
|
||||
import Frozen from "./Frozen.js";
|
||||
|
||||
/**
|
||||
* Returns the first parameter if not undefined, otherwise the second parameter.
|
||||
* Useful for setting a default value for a parameter.
|
||||
*
|
||||
* @function
|
||||
*
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @returns {*} Returns the first parameter if not undefined, otherwise the second parameter.
|
||||
*
|
||||
* @example
|
||||
* param = Cesium.defaultValue(param, 'default');
|
||||
* @deprecated This function is deprecated and will be removed in Cesium 1.134. See <a href="https://github.com/CesiumGS/cesium/issues/11674">Issue 11674</a>.
|
||||
* Use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing">Nullish coalescing operator</a> instead
|
||||
*/
|
||||
function defaultValue(a, b) {
|
||||
deprecationWarning(
|
||||
"defaultValue",
|
||||
`defaultValue has been deprecated and will be removed in Cesium 1.134. Use the nullish coalescing operator instead: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing`,
|
||||
);
|
||||
if (a !== undefined && a !== null) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* A frozen empty object that can be used as the default value for options passed as
|
||||
* an object literal.
|
||||
* @type {object}
|
||||
* @memberof defaultValue
|
||||
* @deprecated This property has been deprecated and will be removed in Cesium 1.134. See <a href="https://github.com/CesiumGS/cesium/issues/11326">Issue 11326</a>.
|
||||
* Use `Frozen.EMPTY_OBJECT` instead
|
||||
*/
|
||||
Object.defineProperty(defaultValue, "EMPTY_OBJECT", {
|
||||
get: function () {
|
||||
deprecationWarning(
|
||||
"defaultValue.EMPTY_OBJECT",
|
||||
"defaultValue.EMPTY_OBJECT has been deprecated and will be removed in Cesium 1.134. Use Frozen.EMPTY_OBJECT instead",
|
||||
);
|
||||
return Frozen.EMPTY_OBJECT;
|
||||
},
|
||||
});
|
||||
|
||||
export default defaultValue;
|
||||
|
|
@ -4,7 +4,7 @@ import Resource from "../Core/Resource.js";
|
|||
|
||||
let defaultTokenCredit;
|
||||
const defaultAccessToken =
|
||||
"AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmRXcPUeuOd6ZTh-Z86vRv6teqZdBYLUWIxB6HTYajPhFtAlXhMdqTg07UwlBQ59KbiReC5wCeOF4LzLTW4oaSPp-t4pEcJHnFePlnFcIPXM7R565gTy6f6fMuZAXPvUEuZSMf8dvTqHyV9-Zd9eZGWokDzKC4uEtHME7OeAk3oyZ7vzkjEo8fpuIfFw0sH8vQU.AT1_PLgWE6Lc";
|
||||
"AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmQqEeLjCLZBBnNm_y_K-Cs-tuPcsgUmlxDWvPdTmGIEhrihfZMTG77j2nmCeqeYCPAenHGG1YfJqJaeRLuy5YjARJcFLP2I_judomiDS-8A_LVZxWUObwIQNE5wcsQKxJl7RHiKm-81XRDuUJZXGqy9B4PwPxWkS-N9PZ7NmTT-sP6BOGn5ouiAN8dkxwNx3tA.AT1_BU0Co4D8";
|
||||
/**
|
||||
* Default options for accessing the ArcGIS image tile service.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,308 @@
|
|||
import Check from "../Core/Check.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import IonResource from "../Core/IonResource.js";
|
||||
import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
|
||||
|
||||
const trailingSlashRegex = /\/$/;
|
||||
|
||||
/**
|
||||
* @typedef {object} Azure2DImageryProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the Azure2DImageryProvider constructor
|
||||
*
|
||||
* @property {object} options Object with the following properties:
|
||||
* @property {string} [options.url="https://atlas.microsoft.com/"] The Azure server url.
|
||||
* @property {string} [options.tilesetId="microsoft.imagery"] The Azure tileset ID. Valid options are {@link microsoft.imagery}, {@link microsoft.base.road}, and {@link microsoft.base.labels.road}
|
||||
* @property {string} options.subscriptionKey The public subscription key for the imagery.
|
||||
* @property {Ellipsoid} [options.ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @property {number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
|
||||
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
|
||||
* to result in rendering problems.
|
||||
* @property {number} [options.maximumLevel=22] The maximum level-of-detail supported by the imagery provider.
|
||||
* @property {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides 2D image tiles from Azure.
|
||||
*
|
||||
* @alias Azure2DImageryProvider
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||
*
|
||||
* @example
|
||||
* // Azure 2D imagery provider
|
||||
* const azureImageryProvider = new Cesium.Azure2DImageryProvider({
|
||||
* subscriptionKey: "subscription-key",
|
||||
* tilesetId: "microsoft.base.road"
|
||||
* });
|
||||
*/
|
||||
function Azure2DImageryProvider(options) {
|
||||
options = options ?? {};
|
||||
options.maximumLevel = options.maximumLevel ?? 22;
|
||||
options.minimumLevel = options.minimumLevel ?? 0;
|
||||
|
||||
const subscriptionKey =
|
||||
options.subscriptionKey ?? options["subscription-key"];
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("options.tilesetId", options.tilesetId);
|
||||
Check.defined("options.subscriptionKey", subscriptionKey);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const resource =
|
||||
options.url instanceof IonResource
|
||||
? options.url
|
||||
: Resource.createIfNeeded(options.url ?? "https://atlas.microsoft.com/");
|
||||
|
||||
let templateUrl = resource.getUrlComponent();
|
||||
if (!trailingSlashRegex.test(templateUrl)) {
|
||||
templateUrl += "/";
|
||||
}
|
||||
templateUrl += `map/tile`;
|
||||
|
||||
resource.url = templateUrl;
|
||||
|
||||
resource.setQueryParameters({
|
||||
"api-version": "2024-04-01",
|
||||
tilesetId: options.tilesetId,
|
||||
zoom: `{z}`,
|
||||
x: `{x}`,
|
||||
y: `{y}`,
|
||||
"subscription-key": subscriptionKey,
|
||||
});
|
||||
|
||||
let credit;
|
||||
if (defined(options.credit)) {
|
||||
credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
}
|
||||
|
||||
const provider = new UrlTemplateImageryProvider({
|
||||
...options,
|
||||
url: resource,
|
||||
credit: credit,
|
||||
});
|
||||
provider._resource = resource;
|
||||
this._imageryProvider = provider;
|
||||
|
||||
// This will be defined for ion resources
|
||||
this._tileCredits = resource.credits;
|
||||
}
|
||||
|
||||
Object.defineProperties(Azure2DImageryProvider.prototype, {
|
||||
/**
|
||||
* Gets the URL of the Azure 2D Imagery server.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
url: {
|
||||
get: function () {
|
||||
return this._imageryProvider.url;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the rectangle, in radians, of the imagery provided by the instance.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Rectangle}
|
||||
* @readonly
|
||||
*/
|
||||
rectangle: {
|
||||
get: function () {
|
||||
return this._imageryProvider.rectangle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width of each tile, in pixels.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileWidth: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileWidth;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the height of each tile, in pixels.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileHeight: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileHeight;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum level-of-detail that can be requested.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.maximumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum level-of-detail that can be requested. Generally,
|
||||
* a minimum level should only be used when the rectangle of the imagery is small
|
||||
* enough that the number of tiles at the minimum level is small. An imagery
|
||||
* provider with more than a few tiles at the minimum level will lead to
|
||||
* rendering problems.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
minimumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.minimumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tiling scheme used by the provider.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {TilingScheme}
|
||||
* @readonly
|
||||
*/
|
||||
tilingScheme: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tilingScheme;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tile discard policy. If not undefined, the discard policy is responsible
|
||||
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
|
||||
* returns undefined, no tiles are filtered.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {TileDiscardPolicy}
|
||||
* @readonly
|
||||
*/
|
||||
tileDiscardPolicy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileDiscardPolicy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 {@link TileProviderError}.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
errorEvent: {
|
||||
get: function () {
|
||||
return this._imageryProvider.errorEvent;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
|
||||
* the source of the imagery.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return this._imageryProvider.credit;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the proxy used by this provider.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Proxy}
|
||||
* @readonly
|
||||
*/
|
||||
proxy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.proxy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether or not the images provided by this imagery provider
|
||||
* include an alpha channel. If this property is false, an alpha channel, if present, will
|
||||
* be ignored. If this property is true, any images without an alpha channel will be treated
|
||||
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
|
||||
* and texture upload time are reduced.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
hasAlphaChannel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.hasAlphaChannel;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the credits to be displayed when a given tile is displayed.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level;
|
||||
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
||||
return this._imageryProvider.getTileCredits(x, y, level);
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the image for a given tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {Request} [request] The request object. Intended for internal use only.
|
||||
* @returns {Promise<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
|
||||
* undefined if there are too many active requests to the server, and the request should be retried later.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.requestImage = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
request,
|
||||
) {
|
||||
return this._imageryProvider.requestImage(x, y, level, request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Picking features is not currently supported by this imagery provider, so this function simply returns
|
||||
* undefined.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {number} longitude The longitude at which to pick features.
|
||||
* @param {number} latitude The latitude at which to pick features.
|
||||
* @return {undefined} Undefined since picking is not supported.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.pickFeatures = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
longitude,
|
||||
latitude,
|
||||
) {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Exposed for tests
|
||||
export default Azure2DImageryProvider;
|
||||
|
|
@ -227,6 +227,9 @@ function Billboard(options, billboardCollection) {
|
|||
this._updateClamping();
|
||||
|
||||
this._splitDirection = options.splitDirection ?? SplitDirection.NONE;
|
||||
// Primarily used by labels to indicate that the position is derived from the parent.
|
||||
// and expensive operations like clamping can be skipped.
|
||||
this._positionFromParent = false;
|
||||
}
|
||||
|
||||
const SHOW_INDEX = (Billboard.SHOW_INDEX = 0);
|
||||
|
|
@ -1147,6 +1150,7 @@ Billboard._updateClamping = function (collection, owner) {
|
|||
|
||||
if (
|
||||
owner._heightReference === HeightReference.NONE ||
|
||||
owner._positionFromParent ||
|
||||
!defined(owner._position)
|
||||
) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ function ClippingPlaneCollection(options) {
|
|||
* An event triggered when a new clipping plane is added to the collection. Event handlers
|
||||
* are passed the new plane and the index at which it was added.
|
||||
* @type {Event}
|
||||
* @default Event()
|
||||
* @readonly
|
||||
*/
|
||||
this.planeAdded = new Event();
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ function ClippingPlaneCollection(options) {
|
|||
* An event triggered when a new clipping plane is removed from the collection. Event handlers
|
||||
* are passed the new plane and the index from which it was removed.
|
||||
* @type {Event}
|
||||
* @default Event()
|
||||
* @readonly
|
||||
*/
|
||||
this.planeRemoved = new Event();
|
||||
|
||||
|
|
@ -472,7 +472,7 @@ ClippingPlaneCollection.prototype.update = function (frameState) {
|
|||
// Compute texture requirements for current planes
|
||||
// In RGBA FLOAT, A plane is 4 floats packed to a RGBA.
|
||||
// In RGBA UNSIGNED_BYTE, A plane is a float in [0, 1) packed to RGBA and an Oct32 quantized normal,
|
||||
// so 8 bits or 2 pixels in RGBA.
|
||||
// so 8 bytes or 2 pixels in RGBA.
|
||||
const pixelsNeeded = useFloatTexture ? this.length : this.length * 2;
|
||||
|
||||
if (defined(clippingPlanesTexture)) {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ function appendCss(container) {
|
|||
.cesium-credit-lightbox.cesium-credit-lightbox-expanded {
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
max-width: 370px;
|
||||
max-width: 470px;
|
||||
}
|
||||
.cesium-credit-lightbox.cesium-credit-lightbox-mobile {
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,613 @@
|
|||
import Credit from "../Core/Credit.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import IonResource from "../Core/IonResource.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
|
||||
import GoogleMaps from "../Core/GoogleMaps.js";
|
||||
|
||||
const trailingSlashRegex = /\/$/;
|
||||
|
||||
/**
|
||||
* @typedef {Object} Google2DImageryProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the Google2DImageryProvider constructor
|
||||
*
|
||||
* @property {string} key The Google api key to send with tile requests.
|
||||
* @property {string} session The Google session token that tracks the current state of your map and viewport.
|
||||
* @property {string|Resource|IonResource} url The Google 2D maps endpoint.
|
||||
* @property {string} tileWidth The width of each tile in pixels.
|
||||
* @property {string} tileHeight The height of each tile in pixels.
|
||||
* @property {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @property {number} [minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
|
||||
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
|
||||
* to result in rendering problems.
|
||||
* @property {number} [maximumLevel=22] The maximum level-of-detail supported by the imagery provider.
|
||||
* @property {Rectangle} [rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <div class="notice">
|
||||
* This object is normally not instantiated directly, use {@link Google2DImageryProvider.fromIonAssetId} or {@link Google2DImageryProvider.fromUrl}.
|
||||
* </div>
|
||||
*
|
||||
*
|
||||
* Provides 2D image tiles from {@link https://developers.google.com/maps/documentation/tile/2d-tiles-overview|Google 2D Tiles}.
|
||||
*
|
||||
* Google 2D Tiles can only be used with the Google geocoder.
|
||||
*
|
||||
* @alias Google2DImageryProvider
|
||||
* @constructor
|
||||
*
|
||||
* @param {Google2DImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||
*
|
||||
* @example
|
||||
* // Google 2D imagery provider
|
||||
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
* assetId: 3830184
|
||||
* });
|
||||
* @example
|
||||
* // Use your own Google api key
|
||||
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
|
||||
*
|
||||
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromUrl({
|
||||
* mapType: "SATELLITE"
|
||||
* });
|
||||
*
|
||||
|
||||
*
|
||||
* @see {@link https://developers.google.com/maps/documentation/tile/2d-tiles-overview}
|
||||
* @see {@link https://developers.google.com/maps/documentation/tile/session_tokens}
|
||||
* @see {@link https://en.wikipedia.org/wiki/IETF_language_tag|IETF Language Tags}
|
||||
* @see {@link https://cldr.unicode.org/|Common Locale Data Repository region identifiers}
|
||||
*/
|
||||
|
||||
function Google2DImageryProvider(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
this._maximumLevel = options.maximumLevel ?? 22;
|
||||
this._minimumLevel = options.minimumLevel ?? 0;
|
||||
|
||||
//>>includeStart("debug", pragmas.debug);
|
||||
Check.defined("options.session", options.session);
|
||||
Check.defined("options.tileWidth", options.tileWidth);
|
||||
Check.defined("options.tileHeight", options.tileHeight);
|
||||
Check.defined("options.key", options.key);
|
||||
//>>includeEnd("debug");
|
||||
|
||||
this._session = options.session;
|
||||
this._key = options.key;
|
||||
this._tileWidth = options.tileWidth;
|
||||
this._tileHeight = options.tileHeight;
|
||||
|
||||
const resource =
|
||||
options.url instanceof IonResource
|
||||
? options.url
|
||||
: Resource.createIfNeeded(options.url ?? GoogleMaps.mapTilesApiEndpoint);
|
||||
|
||||
let templateUrl = resource.getUrlComponent();
|
||||
if (!trailingSlashRegex.test(templateUrl)) {
|
||||
templateUrl += "/";
|
||||
}
|
||||
const tilesUrl = `${templateUrl}v1/2dtiles/{z}/{x}/{y}`;
|
||||
this._viewportUrl = `${templateUrl}tile/v1/viewport`;
|
||||
|
||||
resource.url = tilesUrl;
|
||||
|
||||
resource.setQueryParameters({
|
||||
session: encodeURIComponent(options.session),
|
||||
key: encodeURIComponent(options.key),
|
||||
});
|
||||
|
||||
let credit;
|
||||
if (defined(options.credit)) {
|
||||
credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
}
|
||||
|
||||
const provider = new UrlTemplateImageryProvider({
|
||||
url: resource,
|
||||
credit: credit,
|
||||
tileWidth: options.tileWidth,
|
||||
tileHeight: options.tileHeight,
|
||||
ellipsoid: options.ellipsoid,
|
||||
rectangle: options.rectangle,
|
||||
maximumLevel: this._maximumLevel,
|
||||
minimumLevel: this._minimumLevel,
|
||||
});
|
||||
provider._resource = resource;
|
||||
this._imageryProvider = provider;
|
||||
|
||||
// This will be defined for ion resources
|
||||
this._tileCredits = resource.credits;
|
||||
this._attributionsByLevel = undefined;
|
||||
// Asynchronously request and populate _attributionsByLevel
|
||||
this.getViewportCredits();
|
||||
}
|
||||
|
||||
Object.defineProperties(Google2DImageryProvider.prototype, {
|
||||
/**
|
||||
* Gets the URL of the Google 2D Imagery server.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
url: {
|
||||
get: function () {
|
||||
return this._imageryProvider.url;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the rectangle, in radians, of the imagery provided by the instance.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {Rectangle}
|
||||
* @readonly
|
||||
*/
|
||||
rectangle: {
|
||||
get: function () {
|
||||
return this._imageryProvider.rectangle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width of each tile, in pixels.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileWidth: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileWidth;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the height of each tile, in pixels.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileHeight: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileHeight;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum level-of-detail that can be requested.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.maximumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum level-of-detail that can be requested. Generally,
|
||||
* a minimum level should only be used when the rectangle of the imagery is small
|
||||
* enough that the number of tiles at the minimum level is small. An imagery
|
||||
* provider with more than a few tiles at the minimum level will lead to
|
||||
* rendering problems.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
minimumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.minimumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tiling scheme used by the provider.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {TilingScheme}
|
||||
* @readonly
|
||||
*/
|
||||
tilingScheme: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tilingScheme;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tile discard policy. If not undefined, the discard policy is responsible
|
||||
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
|
||||
* returns undefined, no tiles are filtered.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {TileDiscardPolicy}
|
||||
* @readonly
|
||||
*/
|
||||
tileDiscardPolicy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileDiscardPolicy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 {@link TileProviderError}.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
errorEvent: {
|
||||
get: function () {
|
||||
return this._imageryProvider.errorEvent;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
|
||||
* the source of the imagery.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return this._imageryProvider.credit;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the proxy used by this provider.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {Proxy}
|
||||
* @readonly
|
||||
*/
|
||||
proxy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.proxy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether or not the images provided by this imagery provider
|
||||
* include an alpha channel. If this property is false, an alpha channel, if present, will
|
||||
* be ignored. If this property is true, any images without an alpha channel will be treated
|
||||
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
|
||||
* and texture upload time are reduced.
|
||||
* @memberof Google2DImageryProvider.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
hasAlphaChannel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.hasAlphaChannel;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an {@link ImageryProvider} which provides 2D global tiled imagery from {@link https://developers.google.com/maps/documentation/tile/2d-tiles-overview|Google 2D Tiles}, streamed using the Cesium ion REST API.
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {string} options.assetId The Cesium ion asset id.
|
||||
* @param {"satellite" | "terrain" | "roadmap"} [options.mapType="satellite"] The map type of the Google map imagery. Valid options are satellite, terrain, and roadmap. If overlayLayerType is set, mapType is ignored and a transparent overlay is returned. If overlayMapType is undefined, then a basemap of mapType is returned. layerRoadmap overlayLayerType is included in terrain and roadmap mapTypes.
|
||||
* @param {string} [options.language="en_US"] an IETF language tag that specifies the language used to display information on the tiles
|
||||
* @param {string} [options.region="US"] A Common Locale Data Repository region identifier (two uppercase letters) that represents the physical location of the user.
|
||||
* @param {"layerRoadmap" | "layerStreetview" | "layerTraffic"} [options.overlayLayerType] Returns a transparent overlay map with the specified layerType. If no value is provided, a basemap of mapType is returned. Use multiple instances of Google2DImageryProvider to add multiple Google Maps overlays to a scene. layerRoadmap is included in terrain and roadmap mapTypes, so adding as overlay to terrain or roadmap has no effect.
|
||||
* @param {Object} [options.styles] An array of JSON style objects that specify the appearance and detail level of map features such as roads, parks, and built-up areas. Styling is used to customize the standard Google base map. The styles parameter is valid only if the mapType is roadmap. For the complete style syntax, see the ({@link https://developers.google.com/maps/documentation/tile/style-reference|Google Style Reference}).
|
||||
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @param {number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
|
||||
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
|
||||
* to result in rendering problems.
|
||||
* @param {number} [options.maximumLevel=22] The maximum level-of-detail supported by the imagery provider.
|
||||
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
|
||||
* @param {Credit|string} [options.credit] A credit for the data source, which is displayed on the canvas.
|
||||
*
|
||||
* @returns {Promise<Google2DImageryProvider>} A promise that resolves to the created Google2DImageryProvider.
|
||||
*
|
||||
* @example
|
||||
* // Google 2D imagery provider
|
||||
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
* assetId: 3830184
|
||||
* });
|
||||
* @example
|
||||
* // Google 2D roadmap overlay with custom styles
|
||||
* const googleTileProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
* assetId: 3830184,
|
||||
* overlayLayerType: "layerRoadmap",
|
||||
* styles: [
|
||||
* {
|
||||
* stylers: [{ hue: "#00ffe6" }, { saturation: -20 }],
|
||||
* },
|
||||
* {
|
||||
* featureType: "road",
|
||||
* elementType: "geometry",
|
||||
* stylers: [{ lightness: 100 }, { visibility: "simplified" }],
|
||||
* },
|
||||
* ],
|
||||
* });
|
||||
*/
|
||||
Google2DImageryProvider.fromIonAssetId = async function (options) {
|
||||
options = options ?? {};
|
||||
options.mapType = options.mapType ?? "satellite";
|
||||
options.language = options.language ?? "en_US";
|
||||
options.region = options.region ?? "US";
|
||||
|
||||
const overlayLayerType = options.overlayLayerType;
|
||||
//>>includeStart("debug", pragmas.debug);
|
||||
if (defined(overlayLayerType)) {
|
||||
Check.typeOf.string("options.overlayLayerType", overlayLayerType);
|
||||
}
|
||||
Check.defined("options.assetId", options.assetId);
|
||||
//>>includeEnd("debug");
|
||||
|
||||
const queryOptions = buildQueryOptions(options);
|
||||
|
||||
const endpointResource = IonResource._createEndpointResource(
|
||||
options.assetId,
|
||||
{
|
||||
queryParameters: {
|
||||
options: JSON.stringify(queryOptions),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const endpoint = await endpointResource.fetchJson();
|
||||
const endpointOptions = { ...endpoint.options };
|
||||
delete endpointOptions.url;
|
||||
|
||||
const providerOptions = {
|
||||
language: options.language,
|
||||
region: options.region,
|
||||
ellipsoid: options.ellipsoid,
|
||||
minimumLevel: options.minimumLevel,
|
||||
maximumLevel: options.maximumLevel,
|
||||
rectangle: options.rectangle,
|
||||
credit: options.credit,
|
||||
};
|
||||
|
||||
return new Google2DImageryProvider({
|
||||
...endpointOptions,
|
||||
...providerOptions,
|
||||
url: new IonResource(endpoint, endpointResource),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an {@link ImageryProvider} which provides 2D global tiled imagery from {@link https://developers.google.com/maps/documentation/tile/2d-tiles-overview|Google 2D Tiles}.
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {string} [options.key=GoogleMaps.defaultApiKey] Your API key to access Google 2D Tiles. See {@link https://developers.google.com/maps/documentation/javascript/get-api-key} for instructions on how to create your own key.
|
||||
* @param {"satellite" | "terrain" | "roadmap"} [options.mapType="satellite"] The map type of the Google map imagery. Valid options are satellite, terrain, and roadmap. If overlayLayerType is set, mapType is ignored and a transparent overlay is returned. If overlayMapType is undefined, then a basemap of mapType is returned. layerRoadmap overlayLayerType is included in terrain and roadmap mapTypes.
|
||||
* @param {string} [options.language="en_US"] an IETF language tag that specifies the language used to display information on the tiles
|
||||
* @param {string} [options.region="US"] A Common Locale Data Repository region identifier (two uppercase letters) that represents the physical location of the user.
|
||||
* @param {"layerRoadmap" | "layerStreetview" | "layerTraffic"} [options.overlayLayerType] Returns a transparent overlay map with the specified layerType. If no value is provided, a basemap of mapType is returned. Use multiple instances of Google2DImageryProvider to add multiple Google Maps overlays to a scene. layerRoadmap is included in terrain and roadmap mapTypes, so adding as overlay to terrain or roadmap has no effect.
|
||||
* @param {Object} [options.styles] An array of JSON style objects that specify the appearance and detail level of map features such as roads, parks, and built-up areas. Styling is used to customize the standard Google base map. The styles parameter is valid only if the mapType is roadmap. For the complete style syntax, see the ({@link https://developers.google.com/maps/documentation/tile/style-reference|Google Style Reference}).
|
||||
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @param {number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
|
||||
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
|
||||
* to result in rendering problems.
|
||||
* @param {number} [options.maximumLevel=22] The maximum level-of-detail supported by the imagery provider.
|
||||
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
|
||||
* @param {Credit|string} [options.credit] A credit for the data source, which is displayed on the canvas.
|
||||
*
|
||||
* @returns {Promise<Google2DImageryProvider>} A promise that resolves to the created Google2DImageryProvider.
|
||||
*
|
||||
* @example
|
||||
* // Use your own Google api key
|
||||
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
|
||||
*
|
||||
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromUrl({
|
||||
* mapType: "satellite"
|
||||
* });
|
||||
* @example
|
||||
* // Google 2D roadmap overlay with custom styles
|
||||
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
|
||||
*
|
||||
* const googleTileProvider = Cesium.Google2DImageryProvider.fromUrl({
|
||||
* overlayLayerType: "layerRoadmap",
|
||||
* styles: [
|
||||
* {
|
||||
* stylers: [{ hue: "#00ffe6" }, { saturation: -20 }],
|
||||
* },
|
||||
* {
|
||||
* featureType: "road",
|
||||
* elementType: "geometry",
|
||||
* stylers: [{ lightness: 100 }, { visibility: "simplified" }],
|
||||
* },
|
||||
* ],
|
||||
* });
|
||||
*/
|
||||
Google2DImageryProvider.fromUrl = async function (options) {
|
||||
options = options ?? {};
|
||||
options.mapType = options.mapType ?? "satellite";
|
||||
options.language = options.language ?? "en_US";
|
||||
options.region = options.region ?? "US";
|
||||
options.url = options.url ?? GoogleMaps.mapTilesApiEndpoint;
|
||||
options.key = options.key ?? GoogleMaps.defaultApiKey;
|
||||
|
||||
const overlayLayerType = options.overlayLayerType;
|
||||
//>>includeStart("debug", pragmas.debug);
|
||||
if (defined(overlayLayerType)) {
|
||||
Check.typeOf.string("overlayLayerType", overlayLayerType);
|
||||
}
|
||||
if (!defined(options.key) && !defined(GoogleMaps.defaultApiKey)) {
|
||||
throw new DeveloperError(
|
||||
"options.key or GoogleMaps.defaultApiKey is required.",
|
||||
);
|
||||
}
|
||||
//>>includeEnd("debug");
|
||||
|
||||
const sessionJson = await createGoogleImagerySession(options);
|
||||
return new Google2DImageryProvider({
|
||||
...sessionJson,
|
||||
...options,
|
||||
credit: options.credit ?? GoogleMaps.getDefaultCredit(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the credits to be displayed when a given tile is displayed.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level;
|
||||
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
|
||||
*/
|
||||
Google2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
||||
const hasAttributions = defined(this._attributionsByLevel);
|
||||
|
||||
if (!hasAttributions || !defined(this._tileCredits)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const innerCredits = this._attributionsByLevel.get(level);
|
||||
if (!defined(this._tileCredits)) {
|
||||
return innerCredits;
|
||||
}
|
||||
|
||||
return this._tileCredits.concat(innerCredits);
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the image for a given tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {Request} [request] The request object. Intended for internal use only.
|
||||
* @returns {Promise<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
|
||||
* undefined if there are too many active requests to the server, and the request should be retried later.
|
||||
*/
|
||||
Google2DImageryProvider.prototype.requestImage = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
request,
|
||||
) {
|
||||
return this._imageryProvider.requestImage(x, y, level, request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Picking features is not currently supported by this imagery provider, so this function simply returns
|
||||
* undefined.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {number} longitude The longitude at which to pick features.
|
||||
* @param {number} latitude The latitude at which to pick features.
|
||||
* @return {undefined} Undefined since picking is not supported.
|
||||
*/
|
||||
Google2DImageryProvider.prototype.pickFeatures = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
longitude,
|
||||
latitude,
|
||||
) {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get attribution for imagery from Google Maps to display in the credits
|
||||
* @private
|
||||
* @return {Promise<Map<Credit[]>>} The list of attribution sources to display in the credits.
|
||||
*/
|
||||
Google2DImageryProvider.prototype.getViewportCredits = async function () {
|
||||
const maximumLevel = this._maximumLevel;
|
||||
|
||||
const promises = [];
|
||||
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||
promises.push(
|
||||
fetchViewportAttribution(
|
||||
this._viewportUrl,
|
||||
this._key,
|
||||
this._session,
|
||||
level,
|
||||
),
|
||||
);
|
||||
}
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
const attributionsByLevel = new Map();
|
||||
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||
const credits = [];
|
||||
const attributions = results[level];
|
||||
if (attributions) {
|
||||
const levelCredits = new Credit(attributions);
|
||||
credits.push(levelCredits);
|
||||
}
|
||||
attributionsByLevel.set(level, credits);
|
||||
}
|
||||
|
||||
this._attributionsByLevel = attributionsByLevel;
|
||||
|
||||
return attributionsByLevel;
|
||||
};
|
||||
|
||||
async function fetchViewportAttribution(url, key, session, level) {
|
||||
const viewport = await Resource.fetch({
|
||||
url: url,
|
||||
queryParameters: {
|
||||
key,
|
||||
session,
|
||||
zoom: level,
|
||||
north: 90,
|
||||
south: -90,
|
||||
east: 180,
|
||||
west: -180,
|
||||
},
|
||||
data: JSON.stringify(Frozen.EMPTY_OBJECT),
|
||||
});
|
||||
const viewportJson = JSON.parse(viewport);
|
||||
return viewportJson.copyright;
|
||||
}
|
||||
|
||||
function buildQueryOptions(options) {
|
||||
const { mapType, overlayLayerType, styles } = options;
|
||||
|
||||
const queryOptions = {
|
||||
mapType,
|
||||
overlay: false,
|
||||
};
|
||||
|
||||
if (mapType === "terrain" && !defined(overlayLayerType)) {
|
||||
queryOptions.layerTypes = ["layerRoadmap"];
|
||||
}
|
||||
|
||||
if (defined(overlayLayerType)) {
|
||||
queryOptions.mapType = "satellite";
|
||||
queryOptions.overlay = true;
|
||||
queryOptions.layerTypes = [overlayLayerType];
|
||||
}
|
||||
if (defined(styles)) {
|
||||
queryOptions.styles = styles;
|
||||
}
|
||||
return queryOptions;
|
||||
}
|
||||
|
||||
async function createGoogleImagerySession(options) {
|
||||
const { language, region, key, url } = options;
|
||||
|
||||
const queryOptions = buildQueryOptions(options);
|
||||
|
||||
let baseUrl = url.url ?? url;
|
||||
if (!trailingSlashRegex.test(baseUrl)) {
|
||||
baseUrl += "/";
|
||||
}
|
||||
|
||||
const response = await Resource.post({
|
||||
url: `${baseUrl}v1/createSession`,
|
||||
queryParameters: { key: key },
|
||||
data: JSON.stringify({
|
||||
...queryOptions,
|
||||
language,
|
||||
region,
|
||||
}),
|
||||
});
|
||||
const responseJson = JSON.parse(response);
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
export default Google2DImageryProvider;
|
||||
|
|
@ -13,6 +13,8 @@ import SingleTileImageryProvider from "./SingleTileImageryProvider.js";
|
|||
import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
|
||||
import WebMapServiceImageryProvider from "./WebMapServiceImageryProvider.js";
|
||||
import WebMapTileServiceImageryProvider from "./WebMapTileServiceImageryProvider.js";
|
||||
import Google2DImageryProvider from "./Google2DImageryProvider.js";
|
||||
import Azure2DImageryProvider from "./Azure2DImageryProvider.js";
|
||||
|
||||
// These values are the list of supported external imagery
|
||||
// assets in the Cesium ion beta. They are subject to change.
|
||||
|
|
@ -52,6 +54,18 @@ const ImageryProviderAsyncMapping = {
|
|||
...options,
|
||||
});
|
||||
},
|
||||
GOOGLE_2D_MAPS: (ionResource, options) => {
|
||||
return new Google2DImageryProvider({
|
||||
...options,
|
||||
url: ionResource,
|
||||
});
|
||||
},
|
||||
AZURE_MAPS: (ionResource, options) => {
|
||||
return new Azure2DImageryProvider({
|
||||
...options,
|
||||
url: ionResource,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -308,7 +322,14 @@ IonImageryProvider.fromAssetId = async function (assetId, options) {
|
|||
const options = { ...endpoint.options };
|
||||
const url = options.url;
|
||||
delete options.url;
|
||||
imageryProvider = await factory(url, options);
|
||||
if (["GOOGLE_2D_MAPS", "AZURE_MAPS"].includes(endpoint.externalType)) {
|
||||
imageryProvider = await factory(
|
||||
new IonResource(endpoint, endpointResource),
|
||||
options,
|
||||
);
|
||||
} else {
|
||||
imageryProvider = await factory(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
const provider = new IonImageryProvider(options);
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ function rebindAllGlyphs(labelCollection, label) {
|
|||
});
|
||||
billboard._labelDimensions = new Cartesian2();
|
||||
billboard._labelTranslate = new Cartesian2();
|
||||
billboard._positionFromParent = true;
|
||||
}
|
||||
glyph.billboard = billboard;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2489,10 +2489,16 @@ Primitive.prototype.destroy = function () {
|
|||
function setReady(primitive, frameState, state, error) {
|
||||
primitive._error = error;
|
||||
primitive._state = state;
|
||||
|
||||
frameState.afterRender.push(function () {
|
||||
primitive._ready =
|
||||
primitive._state === PrimitiveState.COMPLETE ||
|
||||
primitive._state === PrimitiveState.FAILED;
|
||||
|
||||
// Returning 'true' here will ensure that another rendering pass is
|
||||
// triggered after the primitive actually became ready, to make sure
|
||||
// that it is in fact rendered even in "request render mode"
|
||||
return true;
|
||||
});
|
||||
}
|
||||
export default Primitive;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,136 @@
|
|||
/**
|
||||
* The states that describe the lifecycle of a <code>Primitive</code>, as
|
||||
* represented by the <code>primitive._state</code>.
|
||||
*
|
||||
* The state transitions are triggered by calls to the <code>update</code>
|
||||
* function, but the actual state changes may happen asynchronously if the
|
||||
* <code>asynchronous</code> flag of the primitive was set to
|
||||
* <code>true</code>.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const PrimitiveState = {
|
||||
/**
|
||||
* The initial state of a primitive.
|
||||
*
|
||||
* Note that this does NOT mean that the primitive is "ready", as indicated
|
||||
* by the <code>_ready</code> property. It means the opposite: Nothing was
|
||||
* done with the primitive at all.
|
||||
*
|
||||
* For primitives that are created with the <code>asynchronous:true</code>
|
||||
* setting and that are in this state, the <code>update</code> call starts
|
||||
* the creation of the geometry using web workers, and the primitive goes
|
||||
* into the <code>CREATING</code> state.
|
||||
*
|
||||
* For synchronously created primitives, this state never matters. They will
|
||||
* go into the COMBINED (or FAILED) state directly due to a call to the
|
||||
* <code>update</code> function, if they are not yet FAILED, COMBINED,
|
||||
* or COMPLETE.
|
||||
*/
|
||||
READY: 0,
|
||||
|
||||
/**
|
||||
* The process of creating the primitive geometry is ongoing.
|
||||
*
|
||||
* A primitive can only ever be in this state when it was created
|
||||
* with the <code>asynchronous:true</code> setting.
|
||||
*
|
||||
* It means that web workers are currently creating the geometry
|
||||
* of the primitive.
|
||||
*
|
||||
* When the geometry creation succeeds, then the primitive will go
|
||||
* into the CREATED state. Otherwise, it will go into the FAILED
|
||||
* state. Both will happen asynchronously.
|
||||
*
|
||||
* The <code>update</code> function has to be called regularly
|
||||
* until either of these states is reached.
|
||||
*/
|
||||
CREATING: 1,
|
||||
|
||||
/**
|
||||
* The geometry for the primitive has been created.
|
||||
*
|
||||
* A primitive can only ever be in this state when it was created
|
||||
* with the <code>asynchronous:true</code> setting.
|
||||
*
|
||||
* It means that web workers have (asynchronously) finished the
|
||||
* creation of the geometry, but further (asynchronous) processing
|
||||
* is necessary: If a primitive is determined to be in this state
|
||||
* during a call to <code>update</code>, an asynchronous process
|
||||
* is triggered to "combine" the geometry, meaning that the primitive
|
||||
* will go into the COMBINING state.
|
||||
*/
|
||||
CREATED: 2,
|
||||
|
||||
/**
|
||||
* The asynchronous creation of the geometry has been finished, but the
|
||||
* asynchronous process of combining the geometry has not finished yet.
|
||||
*
|
||||
* A primitive can only ever be in this state when it was created
|
||||
* with the <code>asynchronous:true</code> setting.
|
||||
*
|
||||
* It means that whatever is done with
|
||||
* <code>PrimitivePipeline.packCombineGeometryParameters</code> has
|
||||
* not finished yet. When combining the geometry succeeds, the
|
||||
* primitive will go into the COMBINED state. Otherwise, it will
|
||||
* go into the FAILED state.
|
||||
*/
|
||||
COMBINING: 3,
|
||||
|
||||
/**
|
||||
* The geometry data is in a form that can be uploaded to the GPU.
|
||||
*
|
||||
* For <i>synchronous</i> primitives, this means that the geometry
|
||||
* has been created (synchronously) due to the first call to the
|
||||
* <code>update</code> function.
|
||||
*
|
||||
* For <i>asynchronous</i> primitives, this means that the asynchronous
|
||||
* creation of the geometry and the asynchronous combination of the
|
||||
* geometry have both finished.
|
||||
*
|
||||
* The <code>update</code> function has to be called regularly until
|
||||
* this state is reached. When it is reached, the <code>update</code>
|
||||
* call will cause the transition into the COMPLETE state.
|
||||
*/
|
||||
COMBINED: 4,
|
||||
|
||||
/**
|
||||
* The geometry has been created and uploaded to the GPU.
|
||||
*
|
||||
* When this state is reached, it eventually causes the <code>_ready</code>
|
||||
* flag of the primitive to become <code>true</code>.
|
||||
*
|
||||
* Note: Setting the <code>ready</code> flag does NOT happen in the
|
||||
* <code>update</code> call: It only happens after rendering the next
|
||||
* frame!
|
||||
*
|
||||
* Note: This state does not mean that nothing has to be done
|
||||
* anymore (so the work is not "complete"). When the primitive is in
|
||||
* this state, the <code>update</code> function still has to be
|
||||
* called regularly.
|
||||
*/
|
||||
COMPLETE: 5,
|
||||
|
||||
/**
|
||||
* The creation of the primitive failed.
|
||||
*
|
||||
* When this state is reached, it eventually causes the <code>_ready</code>
|
||||
* flag of the primitive to become <code>true</code>.
|
||||
*
|
||||
* Note: Setting the <code>ready</code> flag does NOT happen in the
|
||||
* <code>update</code> call: It only happens after rendering the next
|
||||
* frame!
|
||||
*
|
||||
* This state can be reached when the (synchronous or asynchronous)
|
||||
* creation of the geometry, or the (asynchronous) combination of
|
||||
* the geometry caused any form of error.
|
||||
*
|
||||
* It may or may not imply the presence of the <code>_error</code> property.
|
||||
* When the <code>_error</code> property is present on a FAILED primitive,
|
||||
* this error will be thrown during the <code>update</code> call. When it
|
||||
* is not present for a FAILED primitive, then the <code>update</code> call
|
||||
* will do nothing.
|
||||
*/
|
||||
FAILED: 6,
|
||||
};
|
||||
export default Object.freeze(PrimitiveState);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,494 @@
|
|||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import ClippingPlane from "./ClippingPlane.js";
|
||||
import ContextLimits from "../Renderer/ContextLimits.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import Event from "../Core/Event.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import Intersect from "../Core/Intersect.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import PixelFormat from "../Core/PixelFormat.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
import Plane from "../Core/Plane.js";
|
||||
import Sampler from "../Renderer/Sampler.js";
|
||||
import Texture from "../Renderer/Texture.js";
|
||||
|
||||
/**
|
||||
* Specifies a set of clipping planes defining rendering bounds for a {@link VoxelPrimitive}.
|
||||
*
|
||||
* @alias VoxelBoundsCollection
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} [options] Object with the following properties:
|
||||
* @param {ClippingPlane[]} [options.planes=[]] An array of {@link ClippingPlane} objects used to selectively disable rendering on the outside of each plane.
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system.
|
||||
* @param {boolean} [options.unionClippingRegions=false] If true, a region will be clipped if it is on the outside of any plane in the collection. Otherwise, a region will only be clipped if it is on the outside of every plane.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function VoxelBoundsCollection(options) {
|
||||
const {
|
||||
planes,
|
||||
modelMatrix = Matrix4.IDENTITY,
|
||||
unionClippingRegions = false,
|
||||
} = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
this._planes = [];
|
||||
|
||||
/**
|
||||
* The 4x4 transformation matrix specifying an additional transform relative to the clipping planes
|
||||
* original coordinate system.
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
*/
|
||||
this.modelMatrix = Matrix4.clone(modelMatrix);
|
||||
|
||||
/**
|
||||
* An event triggered when a new clipping plane is added to the collection. Event handlers
|
||||
* are passed the new plane and the index at which it was added.
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
this.planeAdded = new Event();
|
||||
|
||||
/**
|
||||
* An event triggered when a new clipping plane is removed from the collection. Event handlers
|
||||
* are passed the new plane and the index from which it was removed.
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
this.planeRemoved = new Event();
|
||||
|
||||
this._unionClippingRegions = unionClippingRegions;
|
||||
this._testIntersection = unionClippingRegions
|
||||
? unionIntersectFunction
|
||||
: defaultIntersectFunction;
|
||||
|
||||
this._float32View = undefined;
|
||||
|
||||
this._clippingPlanesTexture = undefined;
|
||||
|
||||
// Add each ClippingPlane object.
|
||||
if (defined(planes)) {
|
||||
for (let i = 0; i < planes.length; ++i) {
|
||||
this.add(planes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unionIntersectFunction(value) {
|
||||
return value === Intersect.OUTSIDE;
|
||||
}
|
||||
|
||||
function defaultIntersectFunction(value) {
|
||||
return value === Intersect.INSIDE;
|
||||
}
|
||||
|
||||
Object.defineProperties(VoxelBoundsCollection.prototype, {
|
||||
/**
|
||||
* Returns the number of planes in this collection. This is commonly used with
|
||||
* {@link VoxelBoundsCollection#get} to iterate over all the planes
|
||||
* in the collection.
|
||||
*
|
||||
* @memberof VoxelBoundsCollection.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
length: {
|
||||
get: function () {
|
||||
return this._planes.length;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, a region will be clipped if it is on the outside of any plane in the
|
||||
* collection. Otherwise, a region will only be clipped if it is on the
|
||||
* outside of every plane.
|
||||
*
|
||||
* @memberof VoxelBoundsCollection.prototype
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
unionClippingRegions: {
|
||||
get: function () {
|
||||
return this._unionClippingRegions;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.bool("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (this._unionClippingRegions === value) {
|
||||
return;
|
||||
}
|
||||
this._unionClippingRegions = value;
|
||||
this._testIntersection = value
|
||||
? unionIntersectFunction
|
||||
: defaultIntersectFunction;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a texture containing packed, untransformed clipping planes.
|
||||
*
|
||||
* @memberof VoxelBoundsCollection.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
texture: {
|
||||
get: function () {
|
||||
return this._clippingPlanesTexture;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Number encapsulating the state for this VoxelBoundsCollection.
|
||||
*
|
||||
* Clipping mode is encoded in the sign of the number, which is just the plane count.
|
||||
* If this value changes, then shader regeneration is necessary.
|
||||
*
|
||||
* @memberof VoxelBoundsCollection.prototype
|
||||
* @returns {number} A Number that describes the VoxelBoundsCollection's state.
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
clippingPlanesState: {
|
||||
get: function () {
|
||||
return this._unionClippingRegions
|
||||
? this._planes.length
|
||||
: -this._planes.length;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Adds the specified {@link ClippingPlane} to the collection to be used to selectively disable rendering
|
||||
* on the outside of each plane. Use {@link VoxelBoundsCollection#unionClippingRegions} to modify
|
||||
* how modify the clipping behavior of multiple planes.
|
||||
*
|
||||
* @param {ClippingPlane} plane The ClippingPlane to add to the collection.
|
||||
*
|
||||
* @see VoxelBoundsCollection#unionClippingRegions
|
||||
* @see VoxelBoundsCollection#remove
|
||||
* @see VoxelBoundsCollection#removeAll
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.add = function (plane) {
|
||||
const newPlaneIndex = this._planes.length;
|
||||
plane.index = newPlaneIndex;
|
||||
this._planes.push(plane);
|
||||
this.planeAdded.raiseEvent(plane, newPlaneIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the plane in the collection at the specified index. Indices are zero-based
|
||||
* and increase as planes are added. Removing a plane shifts all planes after
|
||||
* it to the left, changing their indices. This function is commonly used with
|
||||
* {@link VoxelBoundsCollection#length} to iterate over all the planes
|
||||
* in the collection.
|
||||
*
|
||||
* @param {number} index The zero-based index of the plane.
|
||||
* @returns {ClippingPlane} The ClippingPlane at the specified index.
|
||||
*
|
||||
* @see VoxelBoundsCollection#length
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.get = function (index) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("index", index);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
return this._planes[index];
|
||||
};
|
||||
|
||||
function indexOf(planes, plane) {
|
||||
for (let i = 0; i < planes.length; ++i) {
|
||||
if (Plane.equals(planes[i], plane)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this collection contains a ClippingPlane equal to the given ClippingPlane.
|
||||
*
|
||||
* @param {ClippingPlane} [clippingPlane] The ClippingPlane to check for.
|
||||
* @returns {boolean} true if this collection contains the ClippingPlane, false otherwise.
|
||||
*
|
||||
* @see VoxelBoundsCollection#get
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.contains = function (clippingPlane) {
|
||||
return indexOf(this._planes, clippingPlane) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the first occurrence of the given ClippingPlane from the collection.
|
||||
*
|
||||
* @param {ClippingPlane} clippingPlane
|
||||
* @returns {boolean} <code>true</code> if the plane was removed; <code>false</code> if the plane was not found in the collection.
|
||||
*
|
||||
* @see VoxelBoundsCollection#add
|
||||
* @see VoxelBoundsCollection#contains
|
||||
* @see VoxelBoundsCollection#removeAll
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.remove = function (clippingPlane) {
|
||||
const planes = this._planes;
|
||||
const index = indexOf(planes, clippingPlane);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unlink this VoxelBoundsCollection from the ClippingPlane
|
||||
if (clippingPlane instanceof ClippingPlane) {
|
||||
clippingPlane.onChangeCallback = undefined;
|
||||
clippingPlane.index = -1;
|
||||
}
|
||||
|
||||
// Shift and update indices
|
||||
const length = planes.length - 1;
|
||||
for (let i = index; i < length; ++i) {
|
||||
const planeToKeep = planes[i + 1];
|
||||
planes[i] = planeToKeep;
|
||||
if (planeToKeep instanceof ClippingPlane) {
|
||||
planeToKeep.index = i;
|
||||
}
|
||||
}
|
||||
|
||||
planes.length = length;
|
||||
|
||||
this.planeRemoved.raiseEvent(clippingPlane, index);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all planes from the collection.
|
||||
*
|
||||
* @see VoxelBoundsCollection#add
|
||||
* @see VoxelBoundsCollection#remove
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.removeAll = function () {
|
||||
// Dereference this VoxelBoundsCollection from all ClippingPlanes
|
||||
const planes = this._planes;
|
||||
for (let i = 0; i < planes.length; ++i) {
|
||||
const plane = planes[i];
|
||||
if (plane instanceof ClippingPlane) {
|
||||
plane.onChangeCallback = undefined;
|
||||
plane.index = -1;
|
||||
}
|
||||
this.planeRemoved.raiseEvent(plane, i);
|
||||
}
|
||||
this._planes = [];
|
||||
};
|
||||
|
||||
const scratchPlane = new Plane(Cartesian3.fromElements(1.0, 0.0, 0.0), 0.0);
|
||||
|
||||
// Pack starting at the beginning of the buffer to allow partial update
|
||||
function transformAndPackPlanes(clippingPlaneCollection, transform) {
|
||||
const float32View = clippingPlaneCollection._float32View;
|
||||
const planes = clippingPlaneCollection._planes;
|
||||
|
||||
let floatIndex = 0;
|
||||
for (let i = 0; i < planes.length; ++i) {
|
||||
const { normal, distance } = transformPlane(
|
||||
planes[i],
|
||||
transform,
|
||||
scratchPlane,
|
||||
);
|
||||
|
||||
float32View[floatIndex] = normal.x;
|
||||
float32View[floatIndex + 1] = normal.y;
|
||||
float32View[floatIndex + 2] = normal.z;
|
||||
float32View[floatIndex + 3] = distance;
|
||||
|
||||
floatIndex += 4; // each plane is 4 floats
|
||||
}
|
||||
}
|
||||
|
||||
const scratchPlaneCartesian4 = new Cartesian4();
|
||||
const scratchTransformedNormal = new Cartesian3();
|
||||
|
||||
function transformPlane(plane, transform, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("plane", plane);
|
||||
Check.typeOf.object("transform", transform);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const { normal, distance } = plane;
|
||||
const planeAsCartesian4 = Cartesian4.fromElements(
|
||||
normal.x,
|
||||
normal.y,
|
||||
normal.z,
|
||||
distance,
|
||||
scratchPlaneCartesian4,
|
||||
);
|
||||
let transformedPlane = Matrix4.multiplyByVector(
|
||||
transform,
|
||||
planeAsCartesian4,
|
||||
scratchPlaneCartesian4,
|
||||
);
|
||||
|
||||
// Convert the transformed plane to Hessian Normal Form
|
||||
const transformedNormal = Cartesian3.fromCartesian4(
|
||||
transformedPlane,
|
||||
scratchTransformedNormal,
|
||||
);
|
||||
transformedPlane = Cartesian4.divideByScalar(
|
||||
transformedPlane,
|
||||
Cartesian3.magnitude(transformedNormal),
|
||||
scratchPlaneCartesian4,
|
||||
);
|
||||
|
||||
return Plane.fromCartesian4(transformedPlane, result);
|
||||
}
|
||||
|
||||
function computeTextureResolution(pixelsNeeded, result) {
|
||||
result.x = Math.min(pixelsNeeded, ContextLimits.maximumTextureSize);
|
||||
result.y = Math.ceil(pixelsNeeded / result.x);
|
||||
return result;
|
||||
}
|
||||
|
||||
const textureResolutionScratch = new Cartesian2();
|
||||
/**
|
||||
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
|
||||
* build the resources for clipping planes.
|
||||
* <p>
|
||||
* Do not call this function directly.
|
||||
* </p>
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.update = function (frameState, transform) {
|
||||
let clippingPlanesTexture = this._clippingPlanesTexture;
|
||||
|
||||
// Compute texture requirements for current planes
|
||||
// In RGBA FLOAT, a plane is 4 floats packed to a single RGBA pixel.
|
||||
const pixelsNeeded = this.length;
|
||||
|
||||
if (defined(clippingPlanesTexture)) {
|
||||
const currentPixelCount =
|
||||
clippingPlanesTexture.width * clippingPlanesTexture.height;
|
||||
// Recreate the texture to double current requirement if it isn't big enough or is 4 times larger than it needs to be.
|
||||
// Optimization note: this isn't exactly the classic resizeable array algorithm
|
||||
// * not necessarily checking for resize after each add/remove operation
|
||||
// * random-access deletes instead of just pops
|
||||
// * alloc ops likely more expensive than demonstrable via big-O analysis
|
||||
if (
|
||||
currentPixelCount < pixelsNeeded ||
|
||||
pixelsNeeded < 0.25 * currentPixelCount
|
||||
) {
|
||||
clippingPlanesTexture.destroy();
|
||||
clippingPlanesTexture = undefined;
|
||||
this._clippingPlanesTexture = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no bound planes, there's nothing to update.
|
||||
if (this.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined(clippingPlanesTexture)) {
|
||||
const requiredResolution = computeTextureResolution(
|
||||
pixelsNeeded,
|
||||
textureResolutionScratch,
|
||||
);
|
||||
// Allocate twice as much space as needed to avoid frequent texture reallocation.
|
||||
// Allocate in the Y direction, since texture may be as wide as context texture support.
|
||||
requiredResolution.y *= 2;
|
||||
|
||||
clippingPlanesTexture = new Texture({
|
||||
context: frameState.context,
|
||||
width: requiredResolution.x,
|
||||
height: requiredResolution.y,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: PixelDatatype.FLOAT,
|
||||
sampler: Sampler.NEAREST,
|
||||
flipY: false,
|
||||
});
|
||||
this._float32View = new Float32Array(
|
||||
requiredResolution.x * requiredResolution.y * 4,
|
||||
);
|
||||
|
||||
this._clippingPlanesTexture = clippingPlanesTexture;
|
||||
}
|
||||
|
||||
const { width, height } = clippingPlanesTexture;
|
||||
transformAndPackPlanes(this, transform);
|
||||
clippingPlanesTexture.copyFrom({
|
||||
source: {
|
||||
width: width,
|
||||
height: height,
|
||||
arrayBufferView: this._float32View,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for getting the clipping plane collection's texture resolution.
|
||||
* If the VoxelBoundsCollection hasn't been updated, returns the resolution that will be
|
||||
* allocated based on the current plane count.
|
||||
*
|
||||
* @param {VoxelBoundsCollection} clippingPlaneCollection The clipping plane collection
|
||||
* @param {Context} context The rendering context
|
||||
* @param {Cartesian2} result A Cartesian2 for the result.
|
||||
* @returns {Cartesian2} The required resolution.
|
||||
* @private
|
||||
*/
|
||||
VoxelBoundsCollection.getTextureResolution = function (
|
||||
clippingPlaneCollection,
|
||||
context,
|
||||
result,
|
||||
) {
|
||||
const texture = clippingPlaneCollection.texture;
|
||||
if (defined(texture)) {
|
||||
result.x = texture.width;
|
||||
result.y = texture.height;
|
||||
return result;
|
||||
}
|
||||
|
||||
const pixelsNeeded = clippingPlaneCollection.length;
|
||||
const requiredResolution = computeTextureResolution(pixelsNeeded, result);
|
||||
|
||||
// Allocate twice as much space as needed to avoid frequent texture reallocation.
|
||||
requiredResolution.y *= 2;
|
||||
return requiredResolution;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if this object was destroyed; otherwise, false.
|
||||
* <br /><br />
|
||||
* If this object was destroyed, it should not be used; calling any function other than
|
||||
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
||||
*
|
||||
* @see VoxelBoundsCollection#destroy
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
|
||||
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
|
||||
* <br />
|
||||
* Once an object is destroyed, it should not be used; calling any function other than
|
||||
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
|
||||
* assign the return value (<code>undefined</code>) to the object as done in the example.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
* @example
|
||||
* voxelBounds = voxelBounds && voxelBounds.destroy();
|
||||
*
|
||||
* @see VoxelBoundsCollection#isDestroyed
|
||||
*/
|
||||
VoxelBoundsCollection.prototype.destroy = function () {
|
||||
this._clippingPlanesTexture =
|
||||
this._clippingPlanesTexture && this._clippingPlanesTexture.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default VoxelBoundsCollection;
|
||||
|
|
@ -5,6 +5,8 @@ import Check from "../Core/Check.js";
|
|||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
||||
import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
|
||||
import ClippingPlane from "./ClippingPlane.js";
|
||||
|
||||
/**
|
||||
* A box {@link VoxelShape}.
|
||||
|
|
@ -20,41 +22,10 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
|||
* @private
|
||||
*/
|
||||
function VoxelBoxShape() {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
*/
|
||||
this.orientedBoundingBox = new OrientedBoundingBox();
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
*/
|
||||
this.boundingSphere = new BoundingSphere();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
*/
|
||||
this.boundTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
*/
|
||||
this.shapeTransform = new Matrix4();
|
||||
this._orientedBoundingBox = new OrientedBoundingBox();
|
||||
this._boundingSphere = new BoundingSphere();
|
||||
this._boundTransform = new Matrix4();
|
||||
this._shapeTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* The minimum bounds of the shape.
|
||||
|
|
@ -71,49 +42,166 @@ function VoxelBoxShape() {
|
|||
this._maxBounds = VoxelBoxShape.DefaultMaxBounds.clone();
|
||||
|
||||
/**
|
||||
* The minimum render bounds of the shape.
|
||||
* @type {Cartesian3}
|
||||
* @private
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
*/
|
||||
this.shaderUniforms = {
|
||||
renderMinBounds: new Cartesian3(),
|
||||
renderMaxBounds: new Cartesian3(),
|
||||
boxUvToShapeUvScale: new Cartesian3(),
|
||||
boxUvToShapeUvTranslate: new Cartesian3(),
|
||||
};
|
||||
this._renderMinBounds = VoxelBoxShape.DefaultMinBounds.clone();
|
||||
|
||||
/**
|
||||
* The maximum render bounds of the shape.
|
||||
* @type {Cartesian3}
|
||||
* @private
|
||||
*/
|
||||
this._renderMaxBounds = VoxelBoxShape.DefaultMaxBounds.clone();
|
||||
|
||||
const { DefaultMinBounds, DefaultMaxBounds } = VoxelBoxShape;
|
||||
const boundPlanes = [
|
||||
new ClippingPlane(
|
||||
Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()),
|
||||
DefaultMinBounds.x,
|
||||
),
|
||||
new ClippingPlane(
|
||||
Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
|
||||
DefaultMinBounds.y,
|
||||
),
|
||||
new ClippingPlane(
|
||||
Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
|
||||
DefaultMinBounds.z,
|
||||
),
|
||||
new ClippingPlane(Cartesian3.UNIT_X, -DefaultMaxBounds.x),
|
||||
new ClippingPlane(Cartesian3.UNIT_Y, -DefaultMaxBounds.y),
|
||||
new ClippingPlane(Cartesian3.UNIT_Z, -DefaultMaxBounds.z),
|
||||
];
|
||||
|
||||
this._renderBoundPlanes = new VoxelBoundsCollection({ planes: boundPlanes });
|
||||
|
||||
this._shaderUniforms = {
|
||||
boxEcToXyz: new Matrix3(),
|
||||
boxLocalToShapeUvScale: new Cartesian3(),
|
||||
boxLocalToShapeUvTranslate: new Cartesian3(),
|
||||
};
|
||||
|
||||
this._shaderDefines = {
|
||||
BOX_INTERSECTION_INDEX: undefined,
|
||||
};
|
||||
|
||||
this._shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
|
||||
Object.defineProperties(VoxelBoxShape.prototype, {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
orientedBoundingBox: {
|
||||
get: function () {
|
||||
return this._orientedBoundingBox;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A collection of planes used for the render bounds
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {VoxelBoundsCollection}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
renderBoundPlanes: {
|
||||
get: function () {
|
||||
return this._renderBoundPlanes;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundingSphere: {
|
||||
get: function () {
|
||||
return this._boundingSphere;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundTransform: {
|
||||
get: function () {
|
||||
return this._boundTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
*
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shapeTransform: {
|
||||
get: function () {
|
||||
return this._shapeTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderDefines = {
|
||||
BOX_INTERSECTION_INDEX: undefined,
|
||||
BOX_HAS_SHAPE_BOUNDS: undefined,
|
||||
};
|
||||
shaderUniforms: {
|
||||
get: function () {
|
||||
return this._shaderUniforms;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shaderDefines: {
|
||||
get: function () {
|
||||
return this._shaderDefines;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum number of intersections against the shape for any ray direction.
|
||||
* @private
|
||||
* @memberof VoxelBoxShape.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
shaderMaximumIntersectionsLength: {
|
||||
get: function () {
|
||||
return this._shaderMaximumIntersectionsLength;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const scratchCenter = new Cartesian3();
|
||||
const scratchScale = new Cartesian3();
|
||||
const scratchRotation = new Matrix3();
|
||||
const scratchClipMinBounds = new Cartesian3();
|
||||
const scratchClipMaxBounds = new Cartesian3();
|
||||
const scratchRenderMinBounds = new Cartesian3();
|
||||
const scratchRenderMaxBounds = new Cartesian3();
|
||||
|
||||
const transformLocalToUv = Matrix4.fromRotationTranslation(
|
||||
Matrix3.fromUniformScale(0.5, new Matrix3()),
|
||||
new Cartesian3(0.5, 0.5, 0.5),
|
||||
new Matrix4(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Update the shape's state.
|
||||
|
|
@ -148,13 +236,13 @@ VoxelBoxShape.prototype.update = function (
|
|||
minBounds,
|
||||
clipMinBounds,
|
||||
clipMaxBounds,
|
||||
scratchRenderMinBounds,
|
||||
this._renderMinBounds,
|
||||
);
|
||||
const renderMaxBounds = Cartesian3.clamp(
|
||||
maxBounds,
|
||||
clipMinBounds,
|
||||
clipMaxBounds,
|
||||
scratchRenderMaxBounds,
|
||||
this._renderMaxBounds,
|
||||
);
|
||||
|
||||
// Box is not visible if:
|
||||
|
|
@ -177,29 +265,39 @@ VoxelBoxShape.prototype.update = function (
|
|||
return false;
|
||||
}
|
||||
|
||||
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
|
||||
// Update the render bounds planes
|
||||
const renderBoundPlanes = this._renderBoundPlanes;
|
||||
renderBoundPlanes.get(0).distance = renderMinBounds.x;
|
||||
renderBoundPlanes.get(1).distance = renderMinBounds.y;
|
||||
renderBoundPlanes.get(2).distance = renderMinBounds.z;
|
||||
renderBoundPlanes.get(3).distance = -renderMaxBounds.x;
|
||||
renderBoundPlanes.get(4).distance = -renderMaxBounds.y;
|
||||
renderBoundPlanes.get(5).distance = -renderMaxBounds.z;
|
||||
|
||||
this.orientedBoundingBox = getBoxChunkObb(
|
||||
this._shapeTransform = Matrix4.clone(modelMatrix, this._shapeTransform);
|
||||
|
||||
this._orientedBoundingBox = getBoxChunkObb(
|
||||
renderMinBounds,
|
||||
renderMaxBounds,
|
||||
this.shapeTransform,
|
||||
this.orientedBoundingBox,
|
||||
this._shapeTransform,
|
||||
this._orientedBoundingBox,
|
||||
);
|
||||
|
||||
// All of the box bounds go from -1 to +1, so the model matrix scale can be
|
||||
// used as the oriented bounding box half axes.
|
||||
this.boundTransform = Matrix4.fromRotationTranslation(
|
||||
this.orientedBoundingBox.halfAxes,
|
||||
this.orientedBoundingBox.center,
|
||||
this.boundTransform,
|
||||
this._boundTransform = Matrix4.fromRotationTranslation(
|
||||
this._orientedBoundingBox.halfAxes,
|
||||
this._orientedBoundingBox.center,
|
||||
this._boundTransform,
|
||||
);
|
||||
|
||||
this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this.orientedBoundingBox,
|
||||
this.boundingSphere,
|
||||
this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this._orientedBoundingBox,
|
||||
this._boundingSphere,
|
||||
);
|
||||
|
||||
const { shaderUniforms, shaderDefines } = this;
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
const shaderDefines = this._shaderDefines;
|
||||
|
||||
// To keep things simple, clear the defines every time
|
||||
for (const key in shaderDefines) {
|
||||
|
|
@ -214,57 +312,94 @@ VoxelBoxShape.prototype.update = function (
|
|||
shaderDefines["BOX_INTERSECTION_INDEX"] = intersectionCount;
|
||||
intersectionCount += 1;
|
||||
|
||||
shaderUniforms.renderMinBounds = Matrix4.multiplyByPoint(
|
||||
transformLocalToUv,
|
||||
renderMinBounds,
|
||||
shaderUniforms.renderMinBounds,
|
||||
);
|
||||
shaderUniforms.renderMaxBounds = Matrix4.multiplyByPoint(
|
||||
transformLocalToUv,
|
||||
renderMaxBounds,
|
||||
shaderUniforms.renderMaxBounds,
|
||||
);
|
||||
|
||||
shaderDefines["BOX_HAS_SHAPE_BOUNDS"] = true;
|
||||
|
||||
// Compute scale and translation to transform from UV space to bounded UV space
|
||||
const min = minBounds;
|
||||
const max = maxBounds;
|
||||
|
||||
// Go from UV space to bounded UV space:
|
||||
// delerp(posUv, minBoundsUv, maxBoundsUv)
|
||||
// (posUv - minBoundsUv) / (maxBoundsUv - minBoundsUv)
|
||||
// posUv / (maxBoundsUv - minBoundsUv) - minBoundsUv / (maxBoundsUv - minBoundsUv)
|
||||
// scale = 1.0 / (maxBoundsUv - minBoundsUv)
|
||||
// scale = 1.0 / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
|
||||
// scale = 2.0 / (maxBounds - minBounds)
|
||||
// offset = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
|
||||
// offset = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
|
||||
// offset = -scale * (minBounds * 0.5 + 0.5)
|
||||
shaderUniforms.boxUvToShapeUvScale = Cartesian3.fromElements(
|
||||
2.0 / (min.x === max.x ? 1.0 : max.x - min.x),
|
||||
2.0 / (min.y === max.y ? 1.0 : max.y - min.y),
|
||||
2.0 / (min.z === max.z ? 1.0 : max.z - min.z),
|
||||
shaderUniforms.boxUvToShapeUvScale,
|
||||
const boxLocalToShapeUvScale = Cartesian3.fromElements(
|
||||
boundScale(min.x, max.x),
|
||||
boundScale(min.y, max.y),
|
||||
boundScale(min.z, max.z),
|
||||
shaderUniforms.boxLocalToShapeUvScale,
|
||||
);
|
||||
shaderUniforms.boxLocalToShapeUvTranslate = Cartesian3.negate(
|
||||
Cartesian3.multiplyComponents(
|
||||
boxLocalToShapeUvScale,
|
||||
min,
|
||||
shaderUniforms.boxLocalToShapeUvTranslate,
|
||||
),
|
||||
shaderUniforms.boxLocalToShapeUvTranslate,
|
||||
);
|
||||
|
||||
shaderUniforms.boxUvToShapeUvTranslate = Cartesian3.fromElements(
|
||||
-shaderUniforms.boxUvToShapeUvScale.x * (min.x * 0.5 + 0.5),
|
||||
-shaderUniforms.boxUvToShapeUvScale.y * (min.y * 0.5 + 0.5),
|
||||
-shaderUniforms.boxUvToShapeUvScale.z * (min.z * 0.5 + 0.5),
|
||||
shaderUniforms.boxUvToShapeUvTranslate,
|
||||
);
|
||||
|
||||
this.shaderMaximumIntersectionsLength = intersectionCount;
|
||||
this._shaderMaximumIntersectionsLength = intersectionCount;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function boundScale(minBound, maxBound) {
|
||||
return CesiumMath.equalsEpsilon(minBound, maxBound, CesiumMath.EPSILON7)
|
||||
? 1.0
|
||||
: 1.0 / (maxBound - minBound);
|
||||
}
|
||||
|
||||
const scratchTransformPositionWorldToLocal = new Matrix4();
|
||||
/**
|
||||
* Update any view-dependent transforms.
|
||||
* @private
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*/
|
||||
VoxelBoxShape.prototype.updateViewTransforms = function (frameState) {
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
const transformPositionWorldToLocal = Matrix4.inverse(
|
||||
this._shapeTransform,
|
||||
scratchTransformPositionWorldToLocal,
|
||||
);
|
||||
const transformDirectionWorldToLocal = Matrix4.getMatrix3(
|
||||
transformPositionWorldToLocal,
|
||||
shaderUniforms.boxEcToXyz,
|
||||
);
|
||||
const rotateViewToWorld = frameState.context.uniformState.inverseViewRotation;
|
||||
Matrix3.multiply(
|
||||
transformDirectionWorldToLocal,
|
||||
rotateViewToWorld,
|
||||
shaderUniforms.boxEcToXyz,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a local coordinate to the shape's UV space.
|
||||
* @private
|
||||
* @param {Cartesian3} positionLocal The local coordinate to convert.
|
||||
* @param {Cartesian3} result The Cartesian3 to store the result in.
|
||||
* @returns {Cartesian3} The converted UV coordinate.
|
||||
*/
|
||||
VoxelBoxShape.prototype.convertLocalToShapeUvSpace = function (
|
||||
positionLocal,
|
||||
result,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("positionLocal", positionLocal);
|
||||
Check.typeOf.object("result", result);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const { boxLocalToShapeUvScale, boxLocalToShapeUvTranslate } =
|
||||
this._shaderUniforms;
|
||||
|
||||
return Cartesian3.add(
|
||||
Cartesian3.multiplyComponents(
|
||||
positionLocal,
|
||||
boxLocalToShapeUvScale,
|
||||
result,
|
||||
),
|
||||
boxLocalToShapeUvTranslate,
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
||||
const scratchTileMinBounds = new Cartesian3();
|
||||
const scratchTileMaxBounds = new Cartesian3();
|
||||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {number} tileLevel The tile's level.
|
||||
* @param {number} tileX The tile's x coordinate.
|
||||
|
|
@ -309,7 +444,7 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
|
|||
return getBoxChunkObb(
|
||||
tileMinBounds,
|
||||
tileMaxBounds,
|
||||
this.shapeTransform,
|
||||
this._shapeTransform,
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
|
@ -318,7 +453,6 @@ const sampleSizeScratch = new Cartesian3();
|
|||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified sample within a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {SpatialNode} spatialNode The spatial node containing the sample
|
||||
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
|
||||
|
|
@ -385,7 +519,7 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForSample = function (
|
|||
return getBoxChunkObb(
|
||||
sampleMinBounds,
|
||||
sampleMaxBounds,
|
||||
this.shapeTransform,
|
||||
this._shapeTransform,
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
|
@ -412,6 +546,7 @@ VoxelBoxShape.DefaultMaxBounds = Object.freeze(
|
|||
new Cartesian3(+1.0, +1.0, +1.0),
|
||||
);
|
||||
|
||||
const scratchBoxScale = new Cartesian3();
|
||||
/**
|
||||
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
|
||||
*
|
||||
|
|
@ -437,7 +572,7 @@ function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
|
|||
result.center = Matrix4.getTranslation(matrix, result.center);
|
||||
result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
|
||||
} else {
|
||||
let scale = Matrix4.getScale(matrix, scratchScale);
|
||||
let scale = Matrix4.getScale(matrix, scratchBoxScale);
|
||||
const localCenter = Cartesian3.midpoint(
|
||||
minimumBounds,
|
||||
maximumBounds,
|
||||
|
|
@ -448,7 +583,7 @@ function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
|
|||
scale.x * 0.5 * (maximumBounds.x - minimumBounds.x),
|
||||
scale.y * 0.5 * (maximumBounds.y - minimumBounds.y),
|
||||
scale.z * 0.5 * (maximumBounds.z - minimumBounds.z),
|
||||
scratchScale,
|
||||
scratchBoxScale,
|
||||
);
|
||||
const rotation = Matrix4.getRotation(matrix, scratchRotation);
|
||||
result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import Cartesian3 from "../Core/Cartesian3.js";
|
|||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import ClippingPlane from "./ClippingPlane.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
||||
import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
|
||||
|
||||
/**
|
||||
* A cylinder {@link VoxelShape}.
|
||||
|
|
@ -22,41 +24,10 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
|||
* @private
|
||||
*/
|
||||
function VoxelCylinderShape() {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
*/
|
||||
this.orientedBoundingBox = new OrientedBoundingBox();
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
*/
|
||||
this.boundingSphere = new BoundingSphere();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
*/
|
||||
this.boundTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
* The update function must be called before accessing this value.
|
||||
* @private
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
*/
|
||||
this.shapeTransform = new Matrix4();
|
||||
this._orientedBoundingBox = new OrientedBoundingBox();
|
||||
this._boundingSphere = new BoundingSphere();
|
||||
this._boundTransform = new Matrix4();
|
||||
this._shapeTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* The minimum bounds of the shape, corresponding to minimum radius, angle, and height.
|
||||
|
|
@ -72,61 +43,160 @@ function VoxelCylinderShape() {
|
|||
*/
|
||||
this._maxBounds = VoxelCylinderShape.DefaultMaxBounds.clone();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
*/
|
||||
this.shaderUniforms = {
|
||||
const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
|
||||
const boundPlanes = [
|
||||
new ClippingPlane(
|
||||
Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
|
||||
DefaultMinBounds.z,
|
||||
),
|
||||
new ClippingPlane(Cartesian3.UNIT_Z, -DefaultMaxBounds.z),
|
||||
];
|
||||
|
||||
this._renderBoundPlanes = new VoxelBoundsCollection({ planes: boundPlanes });
|
||||
|
||||
this._shaderUniforms = {
|
||||
cameraShapePosition: new Cartesian3(),
|
||||
cylinderEcToRadialTangentUp: new Matrix3(),
|
||||
cylinderRenderRadiusMinMax: new Cartesian2(),
|
||||
cylinderRenderAngleMinMax: new Cartesian2(),
|
||||
cylinderRenderHeightMinMax: new Cartesian2(),
|
||||
cylinderUvToShapeUvRadius: new Cartesian2(),
|
||||
cylinderUvToShapeUvAngle: new Cartesian2(),
|
||||
cylinderUvToShapeUvHeight: new Cartesian2(),
|
||||
cylinderShapeUvAngleMinMax: new Cartesian2(),
|
||||
cylinderShapeUvAngleRangeZeroMid: 0.0,
|
||||
cylinderLocalToShapeUvRadius: new Cartesian2(),
|
||||
cylinderLocalToShapeUvAngle: new Cartesian2(),
|
||||
cylinderLocalToShapeUvHeight: new Cartesian2(),
|
||||
cylinderShapeUvAngleRangeOrigin: 0.0,
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
*/
|
||||
this.shaderDefines = {
|
||||
this._shaderDefines = {
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
|
||||
CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
|
||||
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_RADIUS: undefined,
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT: undefined,
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY: undefined,
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY: undefined,
|
||||
CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED: undefined,
|
||||
|
||||
CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
|
||||
CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
|
||||
CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
|
||||
};
|
||||
|
||||
this._shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
|
||||
Object.defineProperties(VoxelCylinderShape.prototype, {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
orientedBoundingBox: {
|
||||
get: function () {
|
||||
return this._orientedBoundingBox;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A collection of planes used for the render bounds
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {VoxelBoundsCollection}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
renderBoundPlanes: {
|
||||
get: function () {
|
||||
return this._renderBoundPlanes;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundingSphere: {
|
||||
get: function () {
|
||||
return this._boundingSphere;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundTransform: {
|
||||
get: function () {
|
||||
return this._boundTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
*
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shapeTransform: {
|
||||
get: function () {
|
||||
return this._shapeTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shaderUniforms: {
|
||||
get: function () {
|
||||
return this._shaderUniforms;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shaderDefines: {
|
||||
get: function () {
|
||||
return this._shaderDefines;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum number of intersections against the shape for any ray direction.
|
||||
* @private
|
||||
* @memberof VoxelCylinderShape.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
shaderMaximumIntersectionsLength: {
|
||||
get: function () {
|
||||
return this._shaderMaximumIntersectionsLength;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const scratchScale = new Cartesian3();
|
||||
const scratchClipMinBounds = new Cartesian3();
|
||||
const scratchClipMaxBounds = new Cartesian3();
|
||||
const scratchRenderMinBounds = new Cartesian3();
|
||||
const scratchRenderMaxBounds = new Cartesian3();
|
||||
const scratchTransformPositionWorldToLocal = new Matrix4();
|
||||
const scratchCameraPositionLocal = new Cartesian3();
|
||||
const scratchCameraRadialPosition = new Cartesian2();
|
||||
|
||||
/**
|
||||
* Update the shape's state.
|
||||
|
|
@ -158,15 +228,15 @@ VoxelCylinderShape.prototype.update = function (
|
|||
maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
|
||||
|
||||
const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
|
||||
const defaultAngleRange = DefaultMaxBounds.y - DefaultMinBounds.y;
|
||||
const defaultAngleRangeHalf = 0.5 * defaultAngleRange;
|
||||
const defaultAngleRange = DefaultMaxBounds.y - DefaultMinBounds.y; // == 2 * PI
|
||||
const defaultAngleRangeHalf = 0.5 * defaultAngleRange; // == PI
|
||||
|
||||
const epsilonZeroScale = CesiumMath.EPSILON10;
|
||||
const epsilonAngleDiscontinuity = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
|
||||
const epsilonAngle = CesiumMath.EPSILON10;
|
||||
|
||||
// Clamp the bounds to the valid range
|
||||
minBounds.x = Math.max(0.0, minBounds.x);
|
||||
// TODO: require maxBounds.x >= minBounds.x ?
|
||||
maxBounds.x = Math.max(0.0, maxBounds.x);
|
||||
minBounds.y = CesiumMath.negativePiToPi(minBounds.y);
|
||||
maxBounds.y = CesiumMath.negativePiToPi(maxBounds.y);
|
||||
|
|
@ -174,6 +244,10 @@ VoxelCylinderShape.prototype.update = function (
|
|||
clipMinBounds.y = CesiumMath.negativePiToPi(clipMinBounds.y);
|
||||
clipMaxBounds.y = CesiumMath.negativePiToPi(clipMaxBounds.y);
|
||||
|
||||
// TODO: what does this do with partial volumes crossing the antimeridian?
|
||||
// We could have minBounds.y = +PI/2 and maxBounds.y = -PI/2.
|
||||
// Then clipMinBounds.y = +PI/4 and clipMaxBounds.y = -PI/4.
|
||||
// This maximumByComponent would cancel the clipping.
|
||||
const renderMinBounds = Cartesian3.maximumByComponent(
|
||||
minBounds,
|
||||
clipMinBounds,
|
||||
|
|
@ -205,57 +279,35 @@ VoxelCylinderShape.prototype.update = function (
|
|||
return false;
|
||||
}
|
||||
|
||||
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
|
||||
// Update the render bounds planes
|
||||
const renderBoundPlanes = this._renderBoundPlanes;
|
||||
renderBoundPlanes.get(0).distance = renderMinBounds.z;
|
||||
renderBoundPlanes.get(1).distance = -renderMaxBounds.z;
|
||||
|
||||
this.orientedBoundingBox = getCylinderChunkObb(
|
||||
this._shapeTransform = Matrix4.clone(modelMatrix, this._shapeTransform);
|
||||
|
||||
this._orientedBoundingBox = getCylinderChunkObb(
|
||||
renderMinBounds,
|
||||
renderMaxBounds,
|
||||
this.shapeTransform,
|
||||
this.orientedBoundingBox,
|
||||
this._shapeTransform,
|
||||
this._orientedBoundingBox,
|
||||
);
|
||||
|
||||
this.boundTransform = Matrix4.fromRotationTranslation(
|
||||
this.orientedBoundingBox.halfAxes,
|
||||
this.orientedBoundingBox.center,
|
||||
this.boundTransform,
|
||||
this._boundTransform = Matrix4.fromRotationTranslation(
|
||||
this._orientedBoundingBox.halfAxes,
|
||||
this._orientedBoundingBox.center,
|
||||
this._boundTransform,
|
||||
);
|
||||
|
||||
this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this.orientedBoundingBox,
|
||||
this.boundingSphere,
|
||||
this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this._orientedBoundingBox,
|
||||
this._boundingSphere,
|
||||
);
|
||||
|
||||
const shapeIsDefaultRadius =
|
||||
minBounds.x === DefaultMinBounds.x && maxBounds.x === DefaultMaxBounds.x;
|
||||
const shapeIsAngleReversed = maxBounds.y < minBounds.y;
|
||||
const shapeAngleRange =
|
||||
maxBounds.y - minBounds.y + shapeIsAngleReversed * defaultAngleRange;
|
||||
const shapeIsAngleRegular =
|
||||
shapeAngleRange > defaultAngleRangeHalf + epsilonAngle &&
|
||||
shapeAngleRange < defaultAngleRange - epsilonAngle;
|
||||
const shapeIsAngleFlipped =
|
||||
shapeAngleRange < defaultAngleRangeHalf - epsilonAngle;
|
||||
const shapeIsAngleRangeHalf =
|
||||
shapeAngleRange >= defaultAngleRangeHalf - epsilonAngle &&
|
||||
shapeAngleRange <= defaultAngleRangeHalf + epsilonAngle;
|
||||
const shapeHasAngle =
|
||||
shapeIsAngleRegular || shapeIsAngleFlipped || shapeIsAngleRangeHalf;
|
||||
const shapeIsMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
|
||||
minBounds.y,
|
||||
DefaultMinBounds.y,
|
||||
undefined,
|
||||
epsilonAngleDiscontinuity,
|
||||
);
|
||||
const shapeIsMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
|
||||
maxBounds.y,
|
||||
DefaultMaxBounds.y,
|
||||
undefined,
|
||||
epsilonAngleDiscontinuity,
|
||||
);
|
||||
const shapeIsDefaultHeight =
|
||||
minBounds.z === DefaultMinBounds.z && maxBounds.z === DefaultMaxBounds.z;
|
||||
|
||||
const renderIsDefaultMinRadius = renderMinBounds.x === DefaultMinBounds.x;
|
||||
const renderIsAngleReversed = renderMaxBounds.y < renderMinBounds.y;
|
||||
const renderAngleRange =
|
||||
renderMaxBounds.y -
|
||||
|
|
@ -271,7 +323,8 @@ VoxelCylinderShape.prototype.update = function (
|
|||
const renderHasAngle =
|
||||
renderIsAngleRegular || renderIsAngleFlipped || renderIsAngleRangeZero;
|
||||
|
||||
const { shaderUniforms, shaderDefines } = this;
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
const shaderDefines = this._shaderDefines;
|
||||
|
||||
// To keep things simple, clear the defines every time
|
||||
for (const key in shaderDefines) {
|
||||
|
|
@ -286,7 +339,11 @@ VoxelCylinderShape.prototype.update = function (
|
|||
shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MAX"] = intersectionCount;
|
||||
intersectionCount += 1;
|
||||
|
||||
if (!renderIsDefaultMinRadius) {
|
||||
if (shapeAngleRange < defaultAngleRange - epsilonAngle) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
|
||||
}
|
||||
|
||||
if (renderMinBounds.x !== DefaultMinBounds.x) {
|
||||
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true;
|
||||
shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
|
||||
intersectionCount += 1;
|
||||
|
|
@ -300,65 +357,32 @@ VoxelCylinderShape.prototype.update = function (
|
|||
if (renderMinBounds.x === renderMaxBounds.x) {
|
||||
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true;
|
||||
}
|
||||
if (!shapeIsDefaultRadius) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS"] = true;
|
||||
|
||||
// delerp(radius, minRadius, maxRadius)
|
||||
// (radius - minRadius) / (maxRadius - minRadius)
|
||||
// radius / (maxRadius - minRadius) - minRadius / (maxRadius - minRadius)
|
||||
// scale = 1.0 / (maxRadius - minRadius)
|
||||
// offset = -minRadius / (maxRadius - minRadius)
|
||||
// offset = minRadius / (minRadius - maxRadius)
|
||||
const radiusRange = maxBounds.x - minBounds.x;
|
||||
let scale = 0.0;
|
||||
let offset = 1.0;
|
||||
if (radiusRange !== 0.0) {
|
||||
scale = 1.0 / radiusRange;
|
||||
offset = -minBounds.x / radiusRange;
|
||||
}
|
||||
shaderUniforms.cylinderUvToShapeUvRadius = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.cylinderUvToShapeUvRadius,
|
||||
);
|
||||
const radiusRange = maxBounds.x - minBounds.x;
|
||||
let radialScale = 0.0;
|
||||
let radialOffset = 1.0;
|
||||
if (radiusRange !== 0.0) {
|
||||
radialScale = 1.0 / radiusRange;
|
||||
radialOffset = -minBounds.x * radialScale;
|
||||
}
|
||||
|
||||
if (!shapeIsDefaultHeight) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT"] = true;
|
||||
|
||||
// delerp(heightUv, minHeightUv, maxHeightUv)
|
||||
// (heightUv - minHeightUv) / (maxHeightUv - minHeightUv)
|
||||
// heightUv / (maxHeightUv - minHeightUv) - minHeightUv / (maxHeightUv - minHeightUv)
|
||||
// scale = 1.0 / (maxHeightUv - minHeightUv)
|
||||
// scale = 1.0 / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
|
||||
// scale = 2.0 / (maxHeight - minHeight)
|
||||
// offset = -minHeightUv / (maxHeightUv - minHeightUv)
|
||||
// offset = -minHeightUv / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
|
||||
// offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
|
||||
// offset = -(minHeight + 1.0) / (maxHeight - minHeight)
|
||||
// offset = (minHeight + 1.0) / (minHeight - maxHeight)
|
||||
const heightRange = maxBounds.z - minBounds.z;
|
||||
let scale = 0.0;
|
||||
let offset = 1.0;
|
||||
if (heightRange !== 0.0) {
|
||||
scale = 2.0 / heightRange;
|
||||
offset = -(minBounds.z + 1.0) / heightRange;
|
||||
}
|
||||
shaderUniforms.cylinderUvToShapeUvHeight = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.cylinderUvToShapeUvHeight,
|
||||
);
|
||||
}
|
||||
shaderUniforms.cylinderRenderHeightMinMax = Cartesian2.fromElements(
|
||||
renderMinBounds.z,
|
||||
renderMaxBounds.z,
|
||||
shaderUniforms.cylinderRenderHeightMinMax,
|
||||
shaderUniforms.cylinderLocalToShapeUvRadius = Cartesian2.fromElements(
|
||||
radialScale,
|
||||
radialOffset,
|
||||
shaderUniforms.cylinderLocalToShapeUvRadius,
|
||||
);
|
||||
|
||||
if (shapeIsAngleReversed) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED"] = true;
|
||||
const heightRange = maxBounds.z - minBounds.z; // Default 2.0
|
||||
let heightScale = 0.0;
|
||||
let heightOffset = 1.0;
|
||||
if (heightRange !== 0.0) {
|
||||
heightScale = 1.0 / heightRange;
|
||||
heightOffset = -minBounds.z * heightScale;
|
||||
}
|
||||
shaderUniforms.cylinderLocalToShapeUvHeight = Cartesian2.fromElements(
|
||||
heightScale,
|
||||
heightOffset,
|
||||
shaderUniforms.cylinderLocalToShapeUvHeight,
|
||||
);
|
||||
|
||||
if (renderHasAngle) {
|
||||
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE"] = true;
|
||||
|
|
@ -382,64 +406,151 @@ VoxelCylinderShape.prototype.update = function (
|
|||
);
|
||||
}
|
||||
|
||||
if (shapeHasAngle) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
|
||||
if (shapeIsMinAngleDiscontinuity) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY"] = true;
|
||||
}
|
||||
if (shapeIsMaxAngleDiscontinuity) {
|
||||
shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY"] = true;
|
||||
}
|
||||
const uvMinAngle = (minBounds.y - DefaultMinBounds.y) / defaultAngleRange;
|
||||
const uvMaxAngle = (maxBounds.y - DefaultMinBounds.y) / defaultAngleRange;
|
||||
const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
|
||||
|
||||
const uvMinAngle = (minBounds.y - DefaultMinBounds.y) / defaultAngleRange;
|
||||
const uvMaxAngle = (maxBounds.y - DefaultMinBounds.y) / defaultAngleRange;
|
||||
const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
|
||||
// Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
|
||||
const uvAngleRangeOrigin = (uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
|
||||
shaderUniforms.cylinderShapeUvAngleRangeOrigin = uvAngleRangeOrigin;
|
||||
|
||||
shaderUniforms.cylinderShapeUvAngleMinMax = Cartesian2.fromElements(
|
||||
uvMinAngle,
|
||||
uvMaxAngle,
|
||||
shaderUniforms.cylinderShapeUvAngleMinMax,
|
||||
if (shapeAngleRange <= epsilonAngle) {
|
||||
shaderUniforms.cylinderLocalToShapeUvAngle = Cartesian2.fromElements(
|
||||
0.0,
|
||||
1.0,
|
||||
shaderUniforms.cylinderLocalToShapeUvAngle,
|
||||
);
|
||||
} else {
|
||||
const scale = defaultAngleRange / shapeAngleRange;
|
||||
const shiftedMinAngle = uvMinAngle - uvAngleRangeOrigin;
|
||||
const offset = -scale * (shiftedMinAngle - Math.floor(shiftedMinAngle));
|
||||
shaderUniforms.cylinderLocalToShapeUvAngle = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.cylinderLocalToShapeUvAngle,
|
||||
);
|
||||
shaderUniforms.cylinderShapeUvAngleRangeZeroMid =
|
||||
(uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
|
||||
|
||||
// delerp(angleUv, uvMinAngle, uvMaxAngle)
|
||||
// (angelUv - uvMinAngle) / (uvMaxAngle - uvMinAngle)
|
||||
// angleUv / (uvMaxAngle - uvMinAngle) - uvMinAngle / (uvMaxAngle - uvMinAngle)
|
||||
// scale = 1.0 / (uvMaxAngle - uvMinAngle)
|
||||
// scale = 1.0 / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
|
||||
// scale = 2.0 * pi / (maxAngle - minAngle)
|
||||
// offset = -uvMinAngle / (uvMaxAngle - uvMinAngle)
|
||||
// offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
|
||||
// offset = -(minAngle - pi) / (maxAngle - minAngle)
|
||||
if (shapeAngleRange <= epsilonAngle) {
|
||||
shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
|
||||
0.0,
|
||||
1.0,
|
||||
shaderUniforms.cylinderUvToShapeUvAngle,
|
||||
);
|
||||
} else {
|
||||
const scale = defaultAngleRange / shapeAngleRange;
|
||||
const offset = -(minBounds.y - DefaultMinBounds.y) / shapeAngleRange;
|
||||
shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.cylinderUvToShapeUvAngle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.shaderMaximumIntersectionsLength = intersectionCount;
|
||||
this._shaderMaximumIntersectionsLength = intersectionCount;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const scratchRotateRtuToLocal = new Matrix3();
|
||||
const scratchRtuRotation = new Matrix3();
|
||||
const scratchTransformPositionViewToLocal = new Matrix4();
|
||||
|
||||
/**
|
||||
* Update any view-dependent transforms.
|
||||
* @private
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*/
|
||||
VoxelCylinderShape.prototype.updateViewTransforms = function (frameState) {
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
// 1. Update camera position in cylindrical coordinates
|
||||
const transformPositionWorldToLocal = Matrix4.inverse(
|
||||
this._shapeTransform,
|
||||
scratchTransformPositionWorldToLocal,
|
||||
);
|
||||
const cameraPositionLocal = Matrix4.multiplyByPoint(
|
||||
transformPositionWorldToLocal,
|
||||
frameState.camera.positionWC,
|
||||
scratchCameraPositionLocal,
|
||||
);
|
||||
shaderUniforms.cameraShapePosition = Cartesian3.fromElements(
|
||||
Cartesian2.magnitude(cameraPositionLocal),
|
||||
Math.atan2(cameraPositionLocal.y, cameraPositionLocal.x),
|
||||
cameraPositionLocal.z,
|
||||
shaderUniforms.cameraShapePosition,
|
||||
);
|
||||
// 2. Find radial, tangent, and up components at camera position
|
||||
const cameraRadialDirection = Cartesian2.normalize(
|
||||
Cartesian2.fromCartesian3(cameraPositionLocal, scratchCameraRadialPosition),
|
||||
scratchCameraRadialPosition,
|
||||
);
|
||||
// As row vectors, the radial, tangent, and up vectors constitute a rotation matrix from local to RTU.
|
||||
const rotateLocalToRtu = Matrix3.fromRowMajorArray(
|
||||
[
|
||||
cameraRadialDirection.x,
|
||||
cameraRadialDirection.y,
|
||||
0.0,
|
||||
-cameraRadialDirection.y,
|
||||
cameraRadialDirection.x,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
],
|
||||
scratchRotateRtuToLocal,
|
||||
);
|
||||
// 3. Get rotation from eye to local coordinates
|
||||
const transformPositionViewToWorld =
|
||||
frameState.context.uniformState.inverseView;
|
||||
const transformPositionViewToLocal = Matrix4.multiplyTransformation(
|
||||
transformPositionWorldToLocal,
|
||||
transformPositionViewToWorld,
|
||||
scratchTransformPositionViewToLocal,
|
||||
);
|
||||
const transformDirectionViewToLocal = Matrix4.getMatrix3(
|
||||
transformPositionViewToLocal,
|
||||
scratchRtuRotation,
|
||||
);
|
||||
// 4. Multiply to get rotation from eye to RTU coordinates
|
||||
shaderUniforms.cylinderEcToRadialTangentUp = Matrix3.multiply(
|
||||
rotateLocalToRtu,
|
||||
transformDirectionViewToLocal,
|
||||
shaderUniforms.cylinderEcToRadialTangentUp,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a UV coordinate to the shape's UV space.
|
||||
* @private
|
||||
* @param {Cartesian3} positionLocal The local coordinate to convert.
|
||||
* @param {Cartesian3} result The Cartesian3 to store the result in.
|
||||
* @returns {Cartesian3} The converted UV coordinate.
|
||||
*/
|
||||
VoxelCylinderShape.prototype.convertLocalToShapeUvSpace = function (
|
||||
positionLocal,
|
||||
result,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("positionLocal", positionLocal);
|
||||
Check.typeOf.object("result", result);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
let radius = Math.hypot(positionLocal.x, positionLocal.y);
|
||||
let angle = Math.atan2(positionLocal.y, positionLocal.x);
|
||||
let height = positionLocal.z;
|
||||
|
||||
const {
|
||||
cylinderLocalToShapeUvRadius,
|
||||
cylinderLocalToShapeUvAngle,
|
||||
cylinderShapeUvAngleRangeOrigin,
|
||||
cylinderLocalToShapeUvHeight,
|
||||
} = this._shaderUniforms;
|
||||
|
||||
radius =
|
||||
radius * cylinderLocalToShapeUvRadius.x + cylinderLocalToShapeUvRadius.y;
|
||||
|
||||
// Convert angle to a "UV" in [0,1] with 0 defined at the center of the unoccupied space.
|
||||
angle = (angle + Math.PI) / (2.0 * Math.PI);
|
||||
angle -= cylinderShapeUvAngleRangeOrigin;
|
||||
angle = angle - Math.floor(angle);
|
||||
// Scale and shift so [0,1] covers the occupied space.
|
||||
angle = angle * cylinderLocalToShapeUvAngle.x + cylinderLocalToShapeUvAngle.y;
|
||||
|
||||
height =
|
||||
height * cylinderLocalToShapeUvHeight.x + cylinderLocalToShapeUvHeight.y;
|
||||
|
||||
return Cartesian3.fromElements(radius, angle, height, result);
|
||||
};
|
||||
|
||||
const scratchMinBounds = new Cartesian3();
|
||||
const scratchMaxBounds = new Cartesian3();
|
||||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {number} tileLevel The tile's level.
|
||||
* @param {number} tileX The tile's x coordinate.
|
||||
|
|
@ -484,7 +595,7 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
|
|||
return getCylinderChunkObb(
|
||||
tileMinBounds,
|
||||
tileMaxBounds,
|
||||
this.shapeTransform,
|
||||
this._shapeTransform,
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
|
@ -493,7 +604,6 @@ const sampleSizeScratch = new Cartesian3();
|
|||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified sample within a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {SpatialNode} spatialNode The spatial node containing the sample
|
||||
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
|
||||
|
|
@ -557,7 +667,7 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function (
|
|||
return getCylinderChunkObb(
|
||||
sampleMinBounds,
|
||||
sampleMaxBounds,
|
||||
this.shapeTransform,
|
||||
this._shapeTransform,
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
|
@ -639,6 +749,7 @@ function computeLooseOrientedBoundingBox(matrix, result) {
|
|||
return OrientedBoundingBox.fromPoints(corners, result);
|
||||
}
|
||||
|
||||
const scratchBoxScale = new Cartesian3();
|
||||
/**
|
||||
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
|
||||
*
|
||||
|
|
@ -720,7 +831,7 @@ function getCylinderChunkObb(chunkMinBounds, chunkMaxBounds, matrix, result) {
|
|||
extentX,
|
||||
extentY,
|
||||
extentZ,
|
||||
scratchScale,
|
||||
scratchBoxScale,
|
||||
);
|
||||
|
||||
const scaleMatrix = Matrix4.fromScale(scale, scratchScaleMatrix);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import defined from "../Core/defined.js";
|
||||
import BoundingSphere from "../Core/BoundingSphere.js";
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
|
@ -8,6 +10,7 @@ import Matrix3 from "../Core/Matrix3.js";
|
|||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
import Transforms from "../Core/Transforms.js";
|
||||
|
||||
/**
|
||||
* An ellipsoid {@link VoxelShape}.
|
||||
|
|
@ -23,41 +26,10 @@ import Rectangle from "../Core/Rectangle.js";
|
|||
* @private
|
||||
*/
|
||||
function VoxelEllipsoidShape() {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.orientedBoundingBox = new OrientedBoundingBox();
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.boundingSphere = new BoundingSphere();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.boundTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
* The update function must be called before accessing this value.
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shapeTransform = new Matrix4();
|
||||
this._orientedBoundingBox = new OrientedBoundingBox();
|
||||
this._boundingSphere = new BoundingSphere();
|
||||
this._boundTransform = new Matrix4();
|
||||
this._shapeTransform = new Matrix4();
|
||||
|
||||
/**
|
||||
* @type {Rectangle}
|
||||
|
|
@ -95,31 +67,25 @@ function VoxelEllipsoidShape() {
|
|||
*/
|
||||
this._rotation = new Matrix3();
|
||||
|
||||
/**
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderUniforms = {
|
||||
ellipsoidRadiiUv: new Cartesian3(),
|
||||
this._shaderUniforms = {
|
||||
cameraPositionCartographic: new Cartesian3(),
|
||||
ellipsoidEcToEastNorthUp: new Matrix3(),
|
||||
ellipsoidRadii: new Cartesian3(),
|
||||
eccentricitySquared: 0.0,
|
||||
evoluteScale: new Cartesian2(),
|
||||
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
|
||||
ellipsoidCurvatureAtLatitude: new Cartesian2(),
|
||||
ellipsoidInverseRadiiSquared: new Cartesian3(),
|
||||
ellipsoidRenderLongitudeMinMax: new Cartesian2(),
|
||||
ellipsoidShapeUvLongitudeRangeOrigin: 0.0,
|
||||
ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(),
|
||||
ellipsoidUvToShapeUvLongitude: new Cartesian2(),
|
||||
ellipsoidUvToShapeUvLatitude: new Cartesian2(),
|
||||
ellipsoidLocalToShapeUvLongitude: new Cartesian2(),
|
||||
ellipsoidLocalToShapeUvLatitude: new Cartesian2(),
|
||||
ellipsoidRenderLatitudeSinMinMax: new Cartesian2(),
|
||||
ellipsoidInverseHeightDifferenceUv: 0.0,
|
||||
ellipsoidInverseHeightDifference: 0.0,
|
||||
clipMinMaxHeight: new Cartesian2(),
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderDefines = {
|
||||
this._shaderDefines = {
|
||||
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE: undefined,
|
||||
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO: undefined,
|
||||
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF: undefined,
|
||||
|
|
@ -142,14 +108,103 @@ function VoxelEllipsoidShape() {
|
|||
ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN: undefined,
|
||||
};
|
||||
|
||||
this._shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
|
||||
Object.defineProperties(VoxelEllipsoidShape.prototype, {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {OrientedBoundingBox}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
orientedBoundingBox: {
|
||||
get: function () {
|
||||
return this._orientedBoundingBox;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundingSphere: {
|
||||
get: function () {
|
||||
return this._boundingSphere;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
*
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
boundTransform: {
|
||||
get: function () {
|
||||
return this._boundTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
*
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {Matrix4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shapeTransform: {
|
||||
get: function () {
|
||||
return this._shapeTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shaderUniforms: {
|
||||
get: function () {
|
||||
return this._shaderUniforms;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
shaderDefines: {
|
||||
get: function () {
|
||||
return this._shaderDefines;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum number of intersections against the shape for any ray direction.
|
||||
* @memberof VoxelEllipsoidShape.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
this.shaderMaximumIntersectionsLength = 0; // not known until update
|
||||
}
|
||||
shaderMaximumIntersectionsLength: {
|
||||
get: function () {
|
||||
return this._shaderMaximumIntersectionsLength;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const scratchActualMinBounds = new Cartesian3();
|
||||
const scratchShapeMinBounds = new Cartesian3();
|
||||
|
|
@ -159,7 +214,6 @@ const scratchClipMaxBounds = new Cartesian3();
|
|||
const scratchRenderMinBounds = new Cartesian3();
|
||||
const scratchRenderMaxBounds = new Cartesian3();
|
||||
const scratchScale = new Cartesian3();
|
||||
const scratchRotationScale = new Matrix3();
|
||||
const scratchShapeOuterExtent = new Cartesian3();
|
||||
const scratchRenderOuterExtent = new Cartesian3();
|
||||
const scratchRenderRectangle = new Rectangle();
|
||||
|
|
@ -250,7 +304,6 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
),
|
||||
scratchShapeOuterExtent,
|
||||
);
|
||||
const shapeMaxExtent = Cartesian3.maximumComponent(shapeOuterExtent);
|
||||
|
||||
const renderOuterExtent = Cartesian3.add(
|
||||
radii,
|
||||
|
|
@ -300,31 +353,31 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
scratchRenderRectangle,
|
||||
);
|
||||
|
||||
this.orientedBoundingBox = getEllipsoidChunkObb(
|
||||
this._orientedBoundingBox = getEllipsoidChunkObb(
|
||||
renderRectangle,
|
||||
renderMinBounds.z,
|
||||
renderMaxBounds.z,
|
||||
this._ellipsoid,
|
||||
this._translation,
|
||||
this._rotation,
|
||||
this.orientedBoundingBox,
|
||||
this._orientedBoundingBox,
|
||||
);
|
||||
|
||||
this.shapeTransform = Matrix4.fromRotationTranslation(
|
||||
Matrix3.setScale(this._rotation, shapeOuterExtent, scratchRotationScale),
|
||||
this._shapeTransform = Matrix4.fromRotationTranslation(
|
||||
this._rotation,
|
||||
this._translation,
|
||||
this.shapeTransform,
|
||||
this._shapeTransform,
|
||||
);
|
||||
|
||||
this.boundTransform = Matrix4.fromRotationTranslation(
|
||||
this.orientedBoundingBox.halfAxes,
|
||||
this.orientedBoundingBox.center,
|
||||
this.boundTransform,
|
||||
this._boundTransform = Matrix4.fromRotationTranslation(
|
||||
this._orientedBoundingBox.halfAxes,
|
||||
this._orientedBoundingBox.center,
|
||||
this._boundTransform,
|
||||
);
|
||||
|
||||
this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this.orientedBoundingBox,
|
||||
this.boundingSphere,
|
||||
this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
|
||||
this._orientedBoundingBox,
|
||||
this._boundingSphere,
|
||||
);
|
||||
|
||||
// Longitude
|
||||
|
|
@ -415,7 +468,8 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
shapeIsLatitudeMinOverHalf;
|
||||
const shapeHasLatitude = shapeHasLatitudeMax || shapeHasLatitudeMin;
|
||||
|
||||
const { shaderUniforms, shaderDefines } = this;
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
const shaderDefines = this._shaderDefines;
|
||||
|
||||
// To keep things simple, clear the defines every time
|
||||
for (const key in shaderDefines) {
|
||||
|
|
@ -424,30 +478,28 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
}
|
||||
}
|
||||
|
||||
// The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
|
||||
shaderUniforms.ellipsoidRadiiUv = Cartesian3.divideByScalar(
|
||||
shaderUniforms.ellipsoidRadii = Cartesian3.clone(
|
||||
shapeOuterExtent,
|
||||
shapeMaxExtent,
|
||||
shaderUniforms.ellipsoidRadiiUv,
|
||||
shaderUniforms.ellipsoidRadii,
|
||||
);
|
||||
const { x: radiiUvX, z: radiiUvZ } = shaderUniforms.ellipsoidRadiiUv;
|
||||
const axisRatio = radiiUvZ / radiiUvX;
|
||||
const { x: radiiX, z: radiiZ } = shaderUniforms.ellipsoidRadii;
|
||||
const axisRatio = radiiZ / radiiX;
|
||||
shaderUniforms.eccentricitySquared = 1.0 - axisRatio * axisRatio;
|
||||
shaderUniforms.evoluteScale = Cartesian2.fromElements(
|
||||
(radiiUvX * radiiUvX - radiiUvZ * radiiUvZ) / radiiUvX,
|
||||
(radiiUvZ * radiiUvZ - radiiUvX * radiiUvX) / radiiUvZ,
|
||||
(radiiX * radiiX - radiiZ * radiiZ) / radiiX,
|
||||
(radiiZ * radiiZ - radiiX * radiiX) / radiiZ,
|
||||
shaderUniforms.evoluteScale,
|
||||
);
|
||||
|
||||
// Used to compute geodetic surface normal.
|
||||
shaderUniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
|
||||
shaderUniforms.ellipsoidInverseRadiiSquared = Cartesian3.divideComponents(
|
||||
Cartesian3.ONE,
|
||||
Cartesian3.multiplyComponents(
|
||||
shaderUniforms.ellipsoidRadiiUv,
|
||||
shaderUniforms.ellipsoidRadiiUv,
|
||||
shaderUniforms.ellipsoidInverseRadiiSquaredUv,
|
||||
shaderUniforms.ellipsoidRadii,
|
||||
shaderUniforms.ellipsoidRadii,
|
||||
shaderUniforms.ellipsoidInverseRadiiSquared,
|
||||
),
|
||||
shaderUniforms.ellipsoidInverseRadiiSquaredUv,
|
||||
shaderUniforms.ellipsoidInverseRadiiSquared,
|
||||
);
|
||||
|
||||
// Keep track of how many intersections there are going to be.
|
||||
|
|
@ -460,16 +512,16 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
intersectionCount += 1;
|
||||
|
||||
shaderUniforms.clipMinMaxHeight = Cartesian2.fromElements(
|
||||
(renderMinBounds.z - shapeMaxBounds.z) / shapeMaxExtent,
|
||||
(renderMaxBounds.z - shapeMaxBounds.z) / shapeMaxExtent,
|
||||
renderMinBounds.z - shapeMaxBounds.z,
|
||||
renderMaxBounds.z - shapeMaxBounds.z,
|
||||
shaderUniforms.clipMinMaxHeight,
|
||||
);
|
||||
|
||||
// The percent of space that is between the inner and outer ellipsoid.
|
||||
const thickness = (shapeMaxBounds.z - shapeMinBounds.z) / shapeMaxExtent;
|
||||
shaderUniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
|
||||
const thickness = shapeMaxBounds.z - shapeMinBounds.z;
|
||||
shaderUniforms.ellipsoidInverseHeightDifference = 1.0 / thickness;
|
||||
if (shapeMinBounds.z === shapeMaxBounds.z) {
|
||||
shaderUniforms.ellipsoidInverseHeightDifferenceUv = 0.0;
|
||||
shaderUniforms.ellipsoidInverseHeightDifference = 0.0;
|
||||
}
|
||||
|
||||
// Intersects a wedge for the min and max longitude.
|
||||
|
|
@ -501,36 +553,39 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
if (shapeHasLongitude) {
|
||||
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE"] = true;
|
||||
|
||||
const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
|
||||
const uvShapeMinLongitude =
|
||||
(shapeMinBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
|
||||
const uvShapeMaxLongitude =
|
||||
(shapeMaxBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
|
||||
const uvLongitudeRangeZero =
|
||||
1.0 - shapeLongitudeRange / defaultLongitudeRange;
|
||||
// Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
|
||||
const uvLongitudeRangeOrigin =
|
||||
(uvShapeMaxLongitude + 0.5 * uvLongitudeRangeZero) % 1.0;
|
||||
shaderUniforms.ellipsoidShapeUvLongitudeRangeOrigin =
|
||||
uvLongitudeRangeOrigin;
|
||||
|
||||
const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
|
||||
if (shapeIsLongitudeReversed) {
|
||||
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED"] =
|
||||
true;
|
||||
}
|
||||
|
||||
// delerp(longitudeUv, minLongitudeUv, maxLongitudeUv)
|
||||
// (longitudeUv - minLongitudeUv) / (maxLongitudeUv - minLongitudeUv)
|
||||
// longitudeUv / (maxLongitudeUv - minLongitudeUv) - minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
|
||||
// scale = 1.0 / (maxLongitudeUv - minLongitudeUv)
|
||||
// scale = 1.0 / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
|
||||
// scale = 2.0 * pi / (maxLongitude - minLongitude)
|
||||
// offset = -minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
|
||||
// offset = -((minLongitude - pi) / (2.0 * pi)) / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
|
||||
// offset = -(minLongitude - pi) / (maxLongitude - minLongitude)
|
||||
if (shapeLongitudeRange <= epsilonLongitude) {
|
||||
shaderUniforms.ellipsoidUvToShapeUvLongitude = Cartesian2.fromElements(
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
|
||||
0.0,
|
||||
1.0,
|
||||
shaderUniforms.ellipsoidUvToShapeUvLongitude,
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLongitude,
|
||||
);
|
||||
} else {
|
||||
const scale = defaultLongitudeRange / shapeLongitudeRange;
|
||||
const shiftedMinLongitude = uvShapeMinLongitude - uvLongitudeRangeOrigin;
|
||||
const offset =
|
||||
-(shapeMinBounds.x - DefaultMinBounds.x) / shapeLongitudeRange;
|
||||
shaderUniforms.ellipsoidUvToShapeUvLongitude = Cartesian2.fromElements(
|
||||
-scale * (shiftedMinLongitude - Math.floor(shiftedMinLongitude));
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.ellipsoidUvToShapeUvLongitude,
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLongitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -630,45 +685,92 @@ VoxelEllipsoidShape.prototype.update = function (
|
|||
if (shapeHasLatitude) {
|
||||
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE"] = true;
|
||||
|
||||
// delerp(latitudeUv, minLatitudeUv, maxLatitudeUv)
|
||||
// (latitudeUv - minLatitudeUv) / (maxLatitudeUv - minLatitudeUv)
|
||||
// latitudeUv / (maxLatitudeUv - minLatitudeUv) - minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
|
||||
// scale = 1.0 / (maxLatitudeUv - minLatitudeUv)
|
||||
// scale = 1.0 / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
|
||||
// scale = 2.0 * pi / (maxLatitude - minLatitude)
|
||||
// offset = -minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
|
||||
// offset = -((minLatitude - -pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
|
||||
// offset = -(minLatitude - -pi) / (maxLatitude - minLatitude)
|
||||
// offset = (-pi - minLatitude) / (maxLatitude - minLatitude)
|
||||
if (shapeLatitudeRange < epsilonLatitude) {
|
||||
shaderUniforms.ellipsoidUvToShapeUvLatitude = Cartesian2.fromElements(
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
|
||||
0.0,
|
||||
1.0,
|
||||
shaderUniforms.ellipsoidUvToShapeUvLatitude,
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLatitude,
|
||||
);
|
||||
} else {
|
||||
const defaultLatitudeRange = DefaultMaxBounds.y - DefaultMinBounds.y;
|
||||
const scale = defaultLatitudeRange / shapeLatitudeRange;
|
||||
const offset =
|
||||
(DefaultMinBounds.y - shapeMinBounds.y) / shapeLatitudeRange;
|
||||
shaderUniforms.ellipsoidUvToShapeUvLatitude = Cartesian2.fromElements(
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
|
||||
scale,
|
||||
offset,
|
||||
shaderUniforms.ellipsoidUvToShapeUvLatitude,
|
||||
shaderUniforms.ellipsoidLocalToShapeUvLatitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.shaderMaximumIntersectionsLength = intersectionCount;
|
||||
this._shaderMaximumIntersectionsLength = intersectionCount;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const scratchCameraPositionCartographic = new Cartographic();
|
||||
const surfacePositionScratch = new Cartesian3();
|
||||
const enuTransformScratch = new Matrix4();
|
||||
const enuRotationScratch = new Matrix3();
|
||||
/**
|
||||
* Update any view-dependent transforms.
|
||||
* @private
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*/
|
||||
VoxelEllipsoidShape.prototype.updateViewTransforms = function (frameState) {
|
||||
const shaderUniforms = this._shaderUniforms;
|
||||
const ellipsoid = this._ellipsoid;
|
||||
// TODO: incorporate modelMatrix or shapeTransform here?
|
||||
const cameraWC = frameState.camera.positionWC;
|
||||
const cameraPositionCartographic = ellipsoid.cartesianToCartographic(
|
||||
cameraWC,
|
||||
scratchCameraPositionCartographic,
|
||||
);
|
||||
Cartesian3.fromElements(
|
||||
cameraPositionCartographic.longitude,
|
||||
cameraPositionCartographic.latitude,
|
||||
cameraPositionCartographic.height,
|
||||
shaderUniforms.cameraPositionCartographic,
|
||||
);
|
||||
|
||||
// TODO: incorporate modelMatrix here?
|
||||
const surfacePosition = Cartesian3.fromRadians(
|
||||
cameraPositionCartographic.longitude,
|
||||
cameraPositionCartographic.latitude,
|
||||
0.0,
|
||||
ellipsoid,
|
||||
surfacePositionScratch,
|
||||
);
|
||||
|
||||
shaderUniforms.ellipsoidCurvatureAtLatitude = ellipsoid.getLocalCurvature(
|
||||
surfacePosition,
|
||||
shaderUniforms.ellipsoidCurvatureAtLatitude,
|
||||
);
|
||||
|
||||
const enuToWorld = Transforms.eastNorthUpToFixedFrame(
|
||||
surfacePosition,
|
||||
ellipsoid,
|
||||
enuTransformScratch,
|
||||
);
|
||||
const rotateEnuToWorld = Matrix4.getRotation(enuToWorld, enuRotationScratch);
|
||||
const rotateWorldToView = frameState.context.uniformState.viewRotation;
|
||||
const rotateEnuToView = Matrix3.multiply(
|
||||
rotateWorldToView,
|
||||
rotateEnuToWorld,
|
||||
enuRotationScratch,
|
||||
);
|
||||
// Inverse is the transpose since it's a pure rotation.
|
||||
shaderUniforms.ellipsoidEcToEastNorthUp = Matrix3.transpose(
|
||||
rotateEnuToView,
|
||||
shaderUniforms.ellipsoidEcToEastNorthUp,
|
||||
);
|
||||
};
|
||||
|
||||
const scratchRectangle = new Rectangle();
|
||||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {number} tileLevel The tile's level.
|
||||
* @param {number} tileX The tile's x coordinate.
|
||||
|
|
@ -732,13 +834,194 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
|
|||
);
|
||||
};
|
||||
|
||||
const scratchQuadrantPosition = new Cartesian2();
|
||||
const scratchInverseRadii = new Cartesian2();
|
||||
const scratchEllipseTrigs = new Cartesian2();
|
||||
const scratchEllipseGuess = new Cartesian2();
|
||||
const scratchEvolute = new Cartesian2();
|
||||
const scratchQ = new Cartesian2();
|
||||
|
||||
/**
|
||||
* Find the nearest point on an ellipse and its radius.
|
||||
* @param {Cartesian2} position
|
||||
* @param {Cartesian2} radii
|
||||
* @param {Cartesian2} evoluteScale
|
||||
* @param {Cartesian3} result The Cartesian3 to store the result in. .x and .y components contain the nearest point on the ellipse, .z contains the local radius of curvature.
|
||||
* @returns {Cartesian3} The nearest point on the ellipse and its radius.
|
||||
* @private
|
||||
*/
|
||||
function nearestPointAndRadiusOnEllipse(position, radii, evoluteScale, result) {
|
||||
// Map to the first quadrant
|
||||
const p = Cartesian2.abs(position, scratchQuadrantPosition);
|
||||
const inverseRadii = Cartesian2.fromElements(
|
||||
1.0 / radii.x,
|
||||
1.0 / radii.y,
|
||||
scratchInverseRadii,
|
||||
);
|
||||
// We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
|
||||
// but store the cos and sin of t in a vec2 for efficiency.
|
||||
// Initial guess: t = pi/4
|
||||
let tTrigs = Cartesian2.fromElements(
|
||||
Math.SQRT1_2,
|
||||
Math.SQRT1_2,
|
||||
scratchEllipseTrigs,
|
||||
);
|
||||
// TODO: too much duplication. Move v and evolute declarations inside loop?
|
||||
// Initial guess of point on ellipsoid
|
||||
let v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
|
||||
// Center of curvature of the ellipse at v
|
||||
let evolute = Cartesian2.fromElements(
|
||||
evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
|
||||
evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
|
||||
scratchEvolute,
|
||||
);
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
// Find the (approximate) intersection of p - evolute with the ellipsoid.
|
||||
const distance = Cartesian2.magnitude(
|
||||
Cartesian2.subtract(v, evolute, scratchQ),
|
||||
);
|
||||
const direction = Cartesian2.normalize(
|
||||
Cartesian2.subtract(p, evolute, scratchQ),
|
||||
scratchQ,
|
||||
);
|
||||
const q = Cartesian2.multiplyByScalar(direction, distance, scratchQ);
|
||||
// Update the estimate of t
|
||||
tTrigs = Cartesian2.multiplyComponents(
|
||||
Cartesian2.add(q, evolute, scratchEllipseTrigs),
|
||||
inverseRadii,
|
||||
scratchEllipseTrigs,
|
||||
);
|
||||
tTrigs = Cartesian2.normalize(
|
||||
Cartesian2.clamp(
|
||||
tTrigs,
|
||||
Cartesian2.ZERO,
|
||||
Cartesian2.ONE,
|
||||
scratchEllipseTrigs,
|
||||
),
|
||||
scratchEllipseTrigs,
|
||||
);
|
||||
v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
|
||||
evolute = Cartesian2.fromElements(
|
||||
evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
|
||||
evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
|
||||
scratchEvolute,
|
||||
);
|
||||
}
|
||||
|
||||
// Map back to the original quadrant
|
||||
return Cartesian3.fromElements(
|
||||
Math.sign(position.x) * v.x,
|
||||
Math.sign(position.y) * v.y,
|
||||
Cartesian2.magnitude(Cartesian2.subtract(v, evolute, scratchQ)),
|
||||
result,
|
||||
);
|
||||
}
|
||||
|
||||
const scratchEllipseRadii = new Cartesian2();
|
||||
const scratchEllipsePosition = new Cartesian2();
|
||||
const scratchSurfacePointAndRadius = new Cartesian3();
|
||||
const scratchNormal2d = new Cartesian2();
|
||||
/**
|
||||
* Convert a UV coordinate to the shape's UV space.
|
||||
* @private
|
||||
* @param {Cartesian3} positionLocal The local position to convert.
|
||||
* @param {Cartesian3} result The Cartesian3 to store the result in.
|
||||
* @returns {Cartesian3} The converted UV coordinate.
|
||||
*/
|
||||
VoxelEllipsoidShape.prototype.convertLocalToShapeUvSpace = function (
|
||||
positionLocal,
|
||||
result,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("positionLocal", positionLocal);
|
||||
Check.typeOf.object("result", result);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
let longitude = Math.atan2(positionLocal.y, positionLocal.x);
|
||||
|
||||
const {
|
||||
ellipsoidRadii,
|
||||
evoluteScale,
|
||||
ellipsoidInverseRadiiSquared,
|
||||
ellipsoidInverseHeightDifference,
|
||||
ellipsoidShapeUvLongitudeRangeOrigin,
|
||||
ellipsoidLocalToShapeUvLongitude,
|
||||
ellipsoidLocalToShapeUvLatitude,
|
||||
} = this._shaderUniforms;
|
||||
|
||||
const distanceFromZAxis = Math.hypot(positionLocal.x, positionLocal.y);
|
||||
const posEllipse = Cartesian2.fromElements(
|
||||
distanceFromZAxis,
|
||||
positionLocal.z,
|
||||
scratchEllipsePosition,
|
||||
);
|
||||
const surfacePointAndRadius = nearestPointAndRadiusOnEllipse(
|
||||
posEllipse,
|
||||
Cartesian2.fromElements(
|
||||
ellipsoidRadii.x,
|
||||
ellipsoidRadii.z,
|
||||
scratchEllipseRadii,
|
||||
),
|
||||
evoluteScale,
|
||||
scratchSurfacePointAndRadius,
|
||||
);
|
||||
|
||||
const normal2d = Cartesian2.normalize(
|
||||
Cartesian2.fromElements(
|
||||
surfacePointAndRadius.x * ellipsoidInverseRadiiSquared.x,
|
||||
surfacePointAndRadius.y * ellipsoidInverseRadiiSquared.z,
|
||||
scratchNormal2d,
|
||||
),
|
||||
scratchNormal2d,
|
||||
);
|
||||
let latitude = Math.atan2(normal2d.y, normal2d.x);
|
||||
|
||||
const heightSign =
|
||||
Cartesian2.magnitude(posEllipse) <
|
||||
Cartesian2.magnitude(surfacePointAndRadius)
|
||||
? -1.0
|
||||
: 1.0;
|
||||
const heightVector = Cartesian2.subtract(
|
||||
posEllipse,
|
||||
surfacePointAndRadius,
|
||||
scratchEllipsePosition,
|
||||
);
|
||||
let height = heightSign * Cartesian2.magnitude(heightVector);
|
||||
|
||||
const {
|
||||
ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE,
|
||||
ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE,
|
||||
} = this._shaderDefines;
|
||||
|
||||
longitude = (longitude + Math.PI) / (2.0 * Math.PI);
|
||||
if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)) {
|
||||
longitude -= ellipsoidShapeUvLongitudeRangeOrigin;
|
||||
longitude = longitude - Math.floor(longitude);
|
||||
// Scale and shift so [0, 1] covers the occupied space.
|
||||
longitude =
|
||||
longitude * ellipsoidLocalToShapeUvLongitude.x +
|
||||
ellipsoidLocalToShapeUvLongitude.y;
|
||||
}
|
||||
|
||||
latitude = (latitude + Math.PI / 2.0) / Math.PI;
|
||||
if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)) {
|
||||
// Scale and shift so [0, 1] covers the occupied space.
|
||||
latitude =
|
||||
latitude * ellipsoidLocalToShapeUvLatitude.x +
|
||||
ellipsoidLocalToShapeUvLatitude.y;
|
||||
}
|
||||
|
||||
height = 1.0 + height * ellipsoidInverseHeightDifference;
|
||||
|
||||
return Cartesian3.fromElements(longitude, latitude, height, result);
|
||||
};
|
||||
|
||||
const sampleSizeScratch = new Cartesian3();
|
||||
const scratchTileMinBounds = new Cartesian3();
|
||||
const scratchTileMaxBounds = new Cartesian3();
|
||||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified sample within a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {SpatialNode} spatialNode The spatial node containing the sample
|
||||
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
|
||||
|
|
|
|||
|
|
@ -130,6 +130,14 @@ function VoxelPrimitive(options) {
|
|||
*/
|
||||
this._paddingAfter = new Cartesian3();
|
||||
|
||||
/**
|
||||
* This member is not known until the provider is ready.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this._availableLevels = 1;
|
||||
|
||||
/**
|
||||
* This member is not known until the provider is ready.
|
||||
*
|
||||
|
|
@ -332,24 +340,24 @@ function VoxelPrimitive(options) {
|
|||
this._clock = options.clock;
|
||||
|
||||
// Transforms and other values that are computed when the shape changes
|
||||
/**
|
||||
* @type {Matrix4}
|
||||
* @private
|
||||
*/
|
||||
this._transformPositionLocalToWorld = new Matrix4();
|
||||
|
||||
/**
|
||||
* @type {Matrix4}
|
||||
* @private
|
||||
*/
|
||||
this._transformPositionWorldToUv = new Matrix4();
|
||||
|
||||
/**
|
||||
* @type {Matrix3}
|
||||
* @private
|
||||
*/
|
||||
this._transformDirectionWorldToUv = new Matrix3();
|
||||
this._transformPositionWorldToLocal = new Matrix4();
|
||||
|
||||
/**
|
||||
* Transforms a plane in Hessian normal form from local space to view space.
|
||||
* @type {Matrix4}
|
||||
* @private
|
||||
*/
|
||||
this._transformPositionUvToWorld = new Matrix4();
|
||||
this._transformPlaneLocalToView = new Matrix4();
|
||||
|
||||
/**
|
||||
* @type {Matrix3}
|
||||
|
|
@ -440,14 +448,16 @@ function VoxelPrimitive(options) {
|
|||
inputDimensions: new Cartesian3(),
|
||||
paddingBefore: new Cartesian3(),
|
||||
paddingAfter: new Cartesian3(),
|
||||
transformPositionViewToUv: new Matrix4(),
|
||||
transformPositionUvToView: new Matrix4(),
|
||||
transformPositionViewToLocal: new Matrix4(),
|
||||
transformDirectionViewToLocal: new Matrix3(),
|
||||
cameraPositionUv: new Cartesian3(),
|
||||
cameraDirectionUv: new Cartesian3(),
|
||||
cameraPositionLocal: new Cartesian3(),
|
||||
cameraDirectionLocal: new Cartesian3(),
|
||||
cameraTileCoordinates: new Cartesian4(),
|
||||
cameraTileUv: new Cartesian3(),
|
||||
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
|
||||
clippingPlanesTexture: undefined,
|
||||
clippingPlanesMatrix: new Matrix4(),
|
||||
renderBoundPlanesTexture: undefined,
|
||||
stepSize: 0,
|
||||
pickColor: new Color(),
|
||||
};
|
||||
|
|
@ -631,11 +641,7 @@ function initialize(primitive, provider) {
|
|||
// Create the shape object, and update it so it is valid for VoxelTraversal
|
||||
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
|
||||
primitive._shape = new ShapeConstructor();
|
||||
primitive._shapeVisible = updateShapeAndTransforms(
|
||||
primitive,
|
||||
primitive._shape,
|
||||
provider,
|
||||
);
|
||||
primitive._shapeVisible = updateShapeAndTransforms(primitive);
|
||||
}
|
||||
|
||||
Object.defineProperties(VoxelPrimitive.prototype, {
|
||||
|
|
@ -1137,20 +1143,10 @@ Object.defineProperties(VoxelPrimitive.prototype, {
|
|||
|
||||
const scratchIntersect = new Cartesian4();
|
||||
const scratchNdcAabb = new Cartesian4();
|
||||
const scratchTransformPositionWorldToLocal = new Matrix4();
|
||||
const scratchTransformPositionLocalToWorld = new Matrix4();
|
||||
const scratchTransformPositionLocalToProjection = new Matrix4();
|
||||
|
||||
const transformPositionLocalToUv = Matrix4.fromRotationTranslation(
|
||||
Matrix3.fromUniformScale(0.5, new Matrix3()),
|
||||
new Cartesian3(0.5, 0.5, 0.5),
|
||||
new Matrix4(),
|
||||
);
|
||||
const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
|
||||
Matrix3.fromUniformScale(2.0, new Matrix3()),
|
||||
new Cartesian3(-1.0, -1.0, -1.0),
|
||||
new Matrix4(),
|
||||
);
|
||||
const scratchCameraPositionShapeUv = new Cartesian3();
|
||||
const scratchCameraTileCoordinates = new Cartesian4();
|
||||
|
||||
/**
|
||||
* Updates the voxel primitive.
|
||||
|
|
@ -1160,6 +1156,7 @@ const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
|
|||
*/
|
||||
VoxelPrimitive.prototype.update = function (frameState) {
|
||||
const provider = this._provider;
|
||||
const uniforms = this._uniforms;
|
||||
|
||||
// Update the custom shader in case it has texture uniforms.
|
||||
this._customShader.update(frameState);
|
||||
|
|
@ -1185,10 +1182,9 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
// frame because the member variables can be modified externally via the
|
||||
// getters.
|
||||
const shapeDirty = checkTransformAndBounds(this, provider);
|
||||
const shape = this._shape;
|
||||
if (shapeDirty) {
|
||||
this._shapeVisible = updateShapeAndTransforms(this, shape, provider);
|
||||
if (checkShapeDefines(this, shape)) {
|
||||
this._shapeVisible = updateShapeAndTransforms(this);
|
||||
if (checkShapeDefines(this)) {
|
||||
this._shaderDirty = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1196,6 +1192,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
return;
|
||||
}
|
||||
|
||||
this._shape.updateViewTransforms(frameState);
|
||||
|
||||
// Update the traversal and prepare for rendering.
|
||||
const keyframeLocation = getKeyframeLocation(
|
||||
provider.timeIntervalCollection,
|
||||
|
|
@ -1243,7 +1241,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
}
|
||||
|
||||
const leafNodeTexture = traversal.leafNodeTexture;
|
||||
const uniforms = this._uniforms;
|
||||
if (defined(leafNodeTexture)) {
|
||||
uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
|
||||
uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
|
||||
|
|
@ -1262,7 +1259,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
// Calculate the NDC-space AABB to "scissor" the fullscreen quad
|
||||
const transformPositionWorldToProjection =
|
||||
context.uniformState.viewProjection;
|
||||
const orientedBoundingBox = shape.orientedBoundingBox;
|
||||
const { orientedBoundingBox } = this._shape;
|
||||
const ndcAabb = orientedBoundingBoxToNdcAabb(
|
||||
orientedBoundingBox,
|
||||
transformPositionWorldToProjection,
|
||||
|
|
@ -1286,17 +1283,17 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
uniforms.ndcSpaceAxisAlignedBoundingBox,
|
||||
);
|
||||
const transformPositionViewToWorld = context.uniformState.inverseView;
|
||||
uniforms.transformPositionViewToUv = Matrix4.multiplyTransformation(
|
||||
this._transformPositionWorldToUv,
|
||||
const transformPositionViewToLocal = Matrix4.multiplyTransformation(
|
||||
this._transformPositionWorldToLocal,
|
||||
transformPositionViewToWorld,
|
||||
uniforms.transformPositionViewToUv,
|
||||
uniforms.transformPositionViewToLocal,
|
||||
);
|
||||
const transformPositionWorldToView = context.uniformState.view;
|
||||
uniforms.transformPositionUvToView = Matrix4.multiplyTransformation(
|
||||
transformPositionWorldToView,
|
||||
this._transformPositionUvToWorld,
|
||||
uniforms.transformPositionUvToView,
|
||||
|
||||
this._transformPlaneLocalToView = Matrix4.transpose(
|
||||
transformPositionViewToLocal,
|
||||
this._transformPlaneLocalToView,
|
||||
);
|
||||
|
||||
const transformDirectionViewToWorld =
|
||||
context.uniformState.inverseViewRotation;
|
||||
uniforms.transformDirectionViewToLocal = Matrix3.multiply(
|
||||
|
|
@ -1304,32 +1301,85 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
|||
transformDirectionViewToWorld,
|
||||
uniforms.transformDirectionViewToLocal,
|
||||
);
|
||||
uniforms.cameraPositionUv = Matrix4.multiplyByPoint(
|
||||
this._transformPositionWorldToUv,
|
||||
uniforms.cameraPositionLocal = Matrix4.multiplyByPoint(
|
||||
this._transformPositionWorldToLocal,
|
||||
frameState.camera.positionWC,
|
||||
uniforms.cameraPositionUv,
|
||||
uniforms.cameraPositionLocal,
|
||||
);
|
||||
uniforms.cameraDirectionUv = Matrix3.multiplyByVector(
|
||||
this._transformDirectionWorldToUv,
|
||||
uniforms.cameraDirectionLocal = Matrix3.multiplyByVector(
|
||||
this._transformDirectionWorldToLocal,
|
||||
frameState.camera.directionWC,
|
||||
uniforms.cameraDirectionUv,
|
||||
uniforms.cameraDirectionLocal,
|
||||
);
|
||||
uniforms.cameraDirectionUv = Cartesian3.normalize(
|
||||
uniforms.cameraDirectionUv,
|
||||
uniforms.cameraDirectionUv,
|
||||
const cameraTileCoordinates = getTileCoordinates(
|
||||
this,
|
||||
uniforms.cameraPositionLocal,
|
||||
scratchCameraTileCoordinates,
|
||||
);
|
||||
uniforms.cameraTileCoordinates = Cartesian4.fromElements(
|
||||
Math.floor(cameraTileCoordinates.x),
|
||||
Math.floor(cameraTileCoordinates.y),
|
||||
Math.floor(cameraTileCoordinates.z),
|
||||
cameraTileCoordinates.w,
|
||||
uniforms.cameraTileCoordinates,
|
||||
);
|
||||
uniforms.cameraTileUv = Cartesian3.fromElements(
|
||||
cameraTileCoordinates.x - Math.floor(cameraTileCoordinates.x),
|
||||
cameraTileCoordinates.y - Math.floor(cameraTileCoordinates.y),
|
||||
cameraTileCoordinates.z - Math.floor(cameraTileCoordinates.z),
|
||||
uniforms.cameraTileUv,
|
||||
);
|
||||
uniforms.stepSize = this._stepSizeMultiplier;
|
||||
|
||||
updateRenderBoundPlanes(this, frameState);
|
||||
|
||||
// Render the primitive
|
||||
const command = frameState.passes.pick
|
||||
? this._drawCommandPick
|
||||
: frameState.passes.pickVoxel
|
||||
? this._drawCommandPickVoxel
|
||||
: this._drawCommand;
|
||||
command.boundingVolume = shape.boundingSphere;
|
||||
command.boundingVolume = this._shape.boundingSphere;
|
||||
frameState.commandList.push(command);
|
||||
};
|
||||
|
||||
function updateRenderBoundPlanes(primitive, frameState) {
|
||||
const uniforms = primitive._uniforms;
|
||||
const { renderBoundPlanes } = primitive._shape;
|
||||
if (!defined(renderBoundPlanes)) {
|
||||
return;
|
||||
}
|
||||
renderBoundPlanes.update(frameState, primitive._transformPlaneLocalToView);
|
||||
uniforms.renderBoundPlanesTexture = renderBoundPlanes.texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a position in local space to tile coordinates.
|
||||
*
|
||||
* @param {VoxelPrimitive} primitive The primitive to get the tile coordinates for.
|
||||
* @param {Cartesian3} positionLocal The position in local space to convert to tile coordinates.
|
||||
* @param {Cartesian4} result The result object to store the tile coordinates.
|
||||
* @returns {Cartesian4} The tile coordinates of the supplied position.
|
||||
* @private
|
||||
*/
|
||||
function getTileCoordinates(primitive, positionLocal, result) {
|
||||
const shapeUv = primitive._shape.convertLocalToShapeUvSpace(
|
||||
positionLocal,
|
||||
scratchCameraPositionShapeUv,
|
||||
);
|
||||
|
||||
const availableLevels = primitive._availableLevels;
|
||||
const numTiles = 2 ** (availableLevels - 1);
|
||||
|
||||
return Cartesian4.fromElements(
|
||||
shapeUv.x * numTiles,
|
||||
shapeUv.y * numTiles,
|
||||
shapeUv.z * numTiles,
|
||||
availableLevels - 1,
|
||||
result,
|
||||
);
|
||||
}
|
||||
|
||||
const scratchExaggerationScale = new Cartesian3();
|
||||
const scratchExaggerationCenter = new Cartesian3();
|
||||
const scratchCartographicCenter = new Cartographic();
|
||||
|
|
@ -1337,7 +1387,6 @@ const scratchExaggerationTranslation = new Cartesian3();
|
|||
|
||||
/**
|
||||
* Update the exaggerated bounds of a primitive to account for vertical exaggeration
|
||||
* Currently only applies to Ellipsoid shape type
|
||||
* @param {VoxelPrimitive} primitive
|
||||
* @param {FrameState} frameState
|
||||
* @private
|
||||
|
|
@ -1513,6 +1562,7 @@ function initFromProvider(primitive, provider, context) {
|
|||
primitive._inputDimensions,
|
||||
uniforms.inputDimensions,
|
||||
);
|
||||
primitive._availableLevels = provider.availableLevels ?? 1;
|
||||
|
||||
// Create the VoxelTraversal, and set related uniforms
|
||||
const keyframeCount = provider.keyframeCount ?? 1;
|
||||
|
|
@ -1586,12 +1636,11 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
|
|||
/**
|
||||
* Update the shape and related transforms
|
||||
* @param {VoxelPrimitive} primitive
|
||||
* @param {VoxelShape} shape
|
||||
* @param {VoxelProvider} provider
|
||||
* @returns {boolean} True if the shape is visible
|
||||
* @private
|
||||
*/
|
||||
function updateShapeAndTransforms(primitive, shape, provider) {
|
||||
function updateShapeAndTransforms(primitive) {
|
||||
const shape = primitive._shape;
|
||||
const visible = shape.update(
|
||||
primitive._compoundModelMatrix,
|
||||
primitive._exaggeratedMinBounds,
|
||||
|
|
@ -1603,29 +1652,16 @@ function updateShapeAndTransforms(primitive, shape, provider) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const transformPositionLocalToWorld = shape.shapeTransform;
|
||||
const transformPositionWorldToLocal = Matrix4.inverse(
|
||||
transformPositionLocalToWorld,
|
||||
scratchTransformPositionWorldToLocal,
|
||||
primitive._transformPositionLocalToWorld = Matrix4.clone(
|
||||
shape.shapeTransform,
|
||||
primitive._transformPositionLocalToWorld,
|
||||
);
|
||||
|
||||
// Set member variables when the shape is dirty
|
||||
primitive._transformPositionWorldToUv = Matrix4.multiplyTransformation(
|
||||
transformPositionLocalToUv,
|
||||
transformPositionWorldToLocal,
|
||||
primitive._transformPositionWorldToUv,
|
||||
);
|
||||
primitive._transformDirectionWorldToUv = Matrix4.getMatrix3(
|
||||
primitive._transformPositionWorldToUv,
|
||||
primitive._transformDirectionWorldToUv,
|
||||
);
|
||||
primitive._transformPositionUvToWorld = Matrix4.multiplyTransformation(
|
||||
transformPositionLocalToWorld,
|
||||
transformPositionUvToLocal,
|
||||
primitive._transformPositionUvToWorld,
|
||||
primitive._transformPositionWorldToLocal = Matrix4.inverse(
|
||||
primitive._transformPositionLocalToWorld,
|
||||
primitive._transformPositionWorldToLocal,
|
||||
);
|
||||
primitive._transformDirectionWorldToLocal = Matrix4.getMatrix3(
|
||||
transformPositionWorldToLocal,
|
||||
primitive._transformPositionWorldToLocal,
|
||||
primitive._transformDirectionWorldToLocal,
|
||||
);
|
||||
|
||||
|
|
@ -1679,17 +1715,16 @@ function setTraversalUniforms(traversal, uniforms) {
|
|||
/**
|
||||
* Track changes in shape-related shader defines
|
||||
* @param {VoxelPrimitive} primitive
|
||||
* @param {VoxelShape} shape
|
||||
* @returns {boolean} True if any of the shape defines changed, requiring a shader rebuild
|
||||
* @private
|
||||
*/
|
||||
function checkShapeDefines(primitive, shape) {
|
||||
const shapeDefines = shape.shaderDefines;
|
||||
const shapeDefinesChanged = Object.keys(shapeDefines).some(
|
||||
(key) => shapeDefines[key] !== primitive._shapeDefinesOld[key],
|
||||
function checkShapeDefines(primitive) {
|
||||
const { shaderDefines } = primitive._shape;
|
||||
const shapeDefinesChanged = Object.keys(shaderDefines).some(
|
||||
(key) => shaderDefines[key] !== primitive._shapeDefinesOld[key],
|
||||
);
|
||||
if (shapeDefinesChanged) {
|
||||
primitive._shapeDefinesOld = clone(shapeDefines, true);
|
||||
primitive._shapeDefinesOld = clone(shaderDefines, true);
|
||||
}
|
||||
return shapeDefinesChanged;
|
||||
}
|
||||
|
|
@ -1761,12 +1796,12 @@ function updateClippingPlanes(primitive, frameState) {
|
|||
const uniforms = primitive._uniforms;
|
||||
uniforms.clippingPlanesTexture = clippingPlanes.texture;
|
||||
|
||||
// Compute the clipping plane's transformation to uv space and then take the inverse
|
||||
// Compute the clipping plane's transformation to local space and then take the inverse
|
||||
// transpose to properly transform the hessian normal form of the plane.
|
||||
|
||||
// transpose(inverse(worldToUv * clippingPlaneLocalToWorld))
|
||||
// transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToUv))
|
||||
// transpose(inverse(clippingPlaneLocalToWorld) * uvToWorld)
|
||||
// transpose(inverse(worldToLocal * clippingPlaneLocalToWorld))
|
||||
// transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToLocal))
|
||||
// transpose(inverse(clippingPlaneLocalToWorld) * localToWorld)
|
||||
|
||||
uniforms.clippingPlanesMatrix = Matrix4.transpose(
|
||||
Matrix4.multiplyTransformation(
|
||||
|
|
@ -1774,7 +1809,7 @@ function updateClippingPlanes(primitive, frameState) {
|
|||
clippingPlanes.modelMatrix,
|
||||
uniforms.clippingPlanesMatrix,
|
||||
),
|
||||
primitive._transformPositionUvToWorld,
|
||||
primitive._transformPositionLocalToWorld,
|
||||
uniforms.clippingPlanesMatrix,
|
||||
),
|
||||
uniforms.clippingPlanesMatrix,
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ import VoxelFS from "../Shaders/Voxels/VoxelFS.js";
|
|||
import VoxelVS from "../Shaders/Voxels/VoxelVS.js";
|
||||
import IntersectionUtils from "../Shaders/Voxels/IntersectionUtils.js";
|
||||
import IntersectDepth from "../Shaders/Voxels/IntersectDepth.js";
|
||||
import IntersectClippingPlanes from "../Shaders/Voxels/IntersectClippingPlanes.js";
|
||||
import IntersectPlane from "../Shaders/Voxels/IntersectPlane.js";
|
||||
import IntersectLongitude from "../Shaders/Voxels/IntersectLongitude.js";
|
||||
import IntersectBox from "../Shaders/Voxels/IntersectBox.js";
|
||||
import IntersectCylinder from "../Shaders/Voxels/IntersectCylinder.js";
|
||||
import IntersectEllipsoid from "../Shaders/Voxels/IntersectEllipsoid.js";
|
||||
import Intersection from "../Shaders/Voxels/Intersection.js";
|
||||
import convertUvToBox from "../Shaders/Voxels/convertUvToBox.js";
|
||||
import convertUvToCylinder from "../Shaders/Voxels/convertUvToCylinder.js";
|
||||
import convertUvToEllipsoid from "../Shaders/Voxels/convertUvToEllipsoid.js";
|
||||
import convertLocalToBoxUv from "../Shaders/Voxels/convertLocalToBoxUv.js";
|
||||
import convertLocalToCylinderUv from "../Shaders/Voxels/convertLocalToCylinderUv.js";
|
||||
import convertLocalToEllipsoidUv from "../Shaders/Voxels/convertLocalToEllipsoidUv.js";
|
||||
import Octree from "../Shaders/Voxels/Octree.js";
|
||||
import Megatexture from "../Shaders/Voxels/Megatexture.js";
|
||||
import VoxelMetadataOrder from "./VoxelMetadataOrder.js";
|
||||
|
|
@ -84,6 +84,12 @@ function VoxelRenderResources(primitive) {
|
|||
this.clippingPlanes = clippingPlanes;
|
||||
this.clippingPlanesLength = clippingPlanesLength;
|
||||
|
||||
const renderBoundPlanes = primitive._shape.renderBoundPlanes;
|
||||
const renderBoundPlanesLength = renderBoundPlanes?.length ?? 0;
|
||||
|
||||
this.renderBoundPlanes = renderBoundPlanes;
|
||||
this.renderBoundPlanesLength = renderBoundPlanesLength;
|
||||
|
||||
// Build shader
|
||||
shaderBuilder.addVertexLines([VoxelVS]);
|
||||
|
||||
|
|
@ -116,8 +122,10 @@ function VoxelRenderResources(primitive) {
|
|||
"#line 0",
|
||||
Octree,
|
||||
VoxelUtils,
|
||||
IntersectionUtils,
|
||||
Megatexture,
|
||||
IntersectionUtils,
|
||||
IntersectPlane,
|
||||
IntersectDepth,
|
||||
]);
|
||||
|
||||
if (clippingPlanesLength > 0) {
|
||||
|
|
@ -138,9 +146,8 @@ function VoxelRenderResources(primitive) {
|
|||
ShaderDestination.FRAGMENT,
|
||||
);
|
||||
}
|
||||
shaderBuilder.addFragmentLines([IntersectClippingPlanes]);
|
||||
}
|
||||
shaderBuilder.addFragmentLines([IntersectDepth]);
|
||||
|
||||
if (primitive._depthTest) {
|
||||
shaderBuilder.addDefine(
|
||||
"DEPTH_TEST",
|
||||
|
|
@ -151,20 +158,20 @@ function VoxelRenderResources(primitive) {
|
|||
|
||||
if (shapeType === "BOX") {
|
||||
shaderBuilder.addFragmentLines([
|
||||
convertUvToBox,
|
||||
convertLocalToBoxUv,
|
||||
IntersectBox,
|
||||
Intersection,
|
||||
]);
|
||||
} else if (shapeType === "CYLINDER") {
|
||||
shaderBuilder.addFragmentLines([
|
||||
convertUvToCylinder,
|
||||
convertLocalToCylinderUv,
|
||||
IntersectLongitude,
|
||||
IntersectCylinder,
|
||||
Intersection,
|
||||
]);
|
||||
} else if (shapeType === "ELLIPSOID") {
|
||||
shaderBuilder.addFragmentLines([
|
||||
convertUvToEllipsoid,
|
||||
convertLocalToEllipsoidUv,
|
||||
IntersectLongitude,
|
||||
IntersectEllipsoid,
|
||||
Intersection,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ function VoxelShape() {
|
|||
Object.defineProperties(VoxelShape.prototype, {
|
||||
/**
|
||||
* An oriented bounding box containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
*
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {OrientedBoundingBox}
|
||||
|
|
@ -34,7 +33,6 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
|
||||
/**
|
||||
* A bounding sphere containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
*
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {BoundingSphere}
|
||||
|
|
@ -47,7 +45,6 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
|
||||
/**
|
||||
* A transformation matrix containing the bounded shape.
|
||||
* The update function must be called before accessing this value.
|
||||
*
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {Matrix4}
|
||||
|
|
@ -60,7 +57,6 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
|
||||
/**
|
||||
* A transformation matrix containing the shape, ignoring the bounds.
|
||||
* The update function must be called before accessing this value.
|
||||
*
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {Matrix4}
|
||||
|
|
@ -72,6 +68,7 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
|
|
@ -81,6 +78,7 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
},
|
||||
|
||||
/**
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {Object<string, any>}
|
||||
* @readonly
|
||||
* @private
|
||||
|
|
@ -91,6 +89,7 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
|
||||
/**
|
||||
* The maximum number of intersections against the shape for any ray direction.
|
||||
* @memberof VoxelShape.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
|
|
@ -110,9 +109,26 @@ Object.defineProperties(VoxelShape.prototype, {
|
|||
*/
|
||||
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
|
||||
|
||||
/**
|
||||
* Update any view-dependent transforms.
|
||||
* @private
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*/
|
||||
VoxelShape.prototype.updateViewTransforms =
|
||||
DeveloperError.throwInstantiationError;
|
||||
|
||||
/**
|
||||
* Converts a local coordinate to the shape's UV space.
|
||||
* @private
|
||||
* @param {Cartesian3} positionLocal The local coordinate to convert.
|
||||
* @param {Cartesian3} result The Cartesian3 to store the result in.
|
||||
* @returns {Cartesian3} The converted UV coordinate.
|
||||
*/
|
||||
VoxelShape.prototype.convertLocalToShapeUvSpace =
|
||||
DeveloperError.throwInstantiationError;
|
||||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {number} tileLevel The tile's level.
|
||||
* @param {number} tileX The tile's x coordinate.
|
||||
|
|
@ -126,7 +142,6 @@ VoxelShape.prototype.computeOrientedBoundingBoxForTile =
|
|||
|
||||
/**
|
||||
* Computes an oriented bounding box for a specified sample within a specified tile.
|
||||
* The update function must be called before calling this function.
|
||||
* @private
|
||||
* @param {SpatialNode} spatialNode The spatial node containing the sample
|
||||
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import defined from "../Core/defined.js";
|
||||
import PrimitiveType from "../Core/PrimitiveType.js";
|
||||
import BlendingState from "./BlendingState.js";
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
|
||||
import CullFace from "./CullFace.js";
|
||||
import getClippingFunction from "./getClippingFunction.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DrawCommand from "../Renderer/DrawCommand.js";
|
||||
import Pass from "../Renderer/Pass.js";
|
||||
import PrimitiveType from "../Core/PrimitiveType.js";
|
||||
import processVoxelProperties from "./processVoxelProperties.js";
|
||||
import RenderState from "../Renderer/RenderState.js";
|
||||
import ShaderDestination from "../Renderer/ShaderDestination.js";
|
||||
import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
|
||||
import VoxelRenderResources from "./VoxelRenderResources.js";
|
||||
import processVoxelProperties from "./processVoxelProperties.js";
|
||||
|
||||
const textureResolutionScratch = new Cartesian2();
|
||||
|
||||
/**
|
||||
* @function
|
||||
|
|
@ -23,27 +27,40 @@ function buildVoxelDrawCommands(primitive, context) {
|
|||
|
||||
processVoxelProperties(renderResources, primitive);
|
||||
|
||||
const { shaderBuilder, clippingPlanes, clippingPlanesLength } =
|
||||
renderResources;
|
||||
const {
|
||||
shaderBuilder,
|
||||
clippingPlanes,
|
||||
clippingPlanesLength,
|
||||
renderBoundPlanes,
|
||||
renderBoundPlanesLength,
|
||||
} = renderResources;
|
||||
|
||||
if (clippingPlanesLength > 0) {
|
||||
// Extract the getClippingPlane function from the getClippingFunction string.
|
||||
// This is a bit of a hack.
|
||||
const functionId = "getClippingPlane";
|
||||
const entireFunction = getClippingFunction(clippingPlanes, context);
|
||||
const functionSignatureBegin = 0;
|
||||
const functionSignatureEnd = entireFunction.indexOf(")") + 1;
|
||||
const functionBodyBegin =
|
||||
entireFunction.indexOf("{", functionSignatureEnd) + 1;
|
||||
const functionBodyEnd = entireFunction.indexOf("}", functionBodyBegin);
|
||||
const functionSignature = entireFunction.slice(
|
||||
functionSignatureBegin,
|
||||
functionSignatureEnd,
|
||||
const functionSignature = `vec4 ${functionId}(highp sampler2D packedPlanes, int planeNumber)`;
|
||||
const textureResolution = ClippingPlaneCollection.getTextureResolution(
|
||||
clippingPlanes,
|
||||
context,
|
||||
textureResolutionScratch,
|
||||
);
|
||||
const functionBody = entireFunction.slice(
|
||||
functionBodyBegin,
|
||||
functionBodyEnd,
|
||||
const functionBody = getPlaneFunctionBody(textureResolution);
|
||||
shaderBuilder.addFunction(
|
||||
functionId,
|
||||
functionSignature,
|
||||
ShaderDestination.FRAGMENT,
|
||||
);
|
||||
shaderBuilder.addFunctionLines(functionId, [functionBody]);
|
||||
}
|
||||
|
||||
if (renderBoundPlanesLength > 0) {
|
||||
const functionId = "getBoundPlane";
|
||||
const functionSignature = `vec4 ${functionId}(highp sampler2D packedPlanes, int planeNumber)`;
|
||||
const textureResolution = VoxelBoundsCollection.getTextureResolution(
|
||||
renderBoundPlanes,
|
||||
context,
|
||||
textureResolutionScratch,
|
||||
);
|
||||
const functionBody = getPlaneFunctionBody(textureResolution);
|
||||
shaderBuilder.addFunction(
|
||||
functionId,
|
||||
functionSignature,
|
||||
|
|
@ -133,4 +150,28 @@ function buildVoxelDrawCommands(primitive, context) {
|
|||
primitive._drawCommandPickVoxel = drawCommandPickVoxel;
|
||||
}
|
||||
|
||||
function getPlaneFunctionBody(textureResolution) {
|
||||
const width = textureResolution.x;
|
||||
const height = textureResolution.y;
|
||||
|
||||
const pixelWidth = 1.0 / width;
|
||||
const pixelHeight = 1.0 / height;
|
||||
|
||||
let pixelWidthString = `${pixelWidth}`;
|
||||
if (pixelWidthString.indexOf(".") === -1) {
|
||||
pixelWidthString += ".0";
|
||||
}
|
||||
let pixelHeightString = `${pixelHeight}`;
|
||||
if (pixelHeightString.indexOf(".") === -1) {
|
||||
pixelHeightString += ".0";
|
||||
}
|
||||
|
||||
return `int pixY = planeNumber / ${width};
|
||||
int pixX = planeNumber - (pixY * ${width});
|
||||
// Sample from center of pixel
|
||||
float u = (float(pixX) + 0.5) * ${pixelWidthString};
|
||||
float v = (float(pixY) + 0.5) * ${pixelHeightString};
|
||||
return texture(packedPlanes, vec2(u, v));`;
|
||||
}
|
||||
|
||||
export default buildVoxelDrawCommands;
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ async function createGooglePhotorealistic3DTileset(apiOptions, tilesetOptions) {
|
|||
}
|
||||
|
||||
const resource = new Resource({
|
||||
url: `${GoogleMaps.mapTilesApiEndpoint}3dtiles/root.json`,
|
||||
url: `${GoogleMaps.mapTilesApiEndpoint}v1/3dtiles/root.json`,
|
||||
queryParameters: {
|
||||
key: key,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,66 +38,61 @@ function getClippingFunction(clippingPlaneCollection, context) {
|
|||
}
|
||||
|
||||
function clippingFunctionUnion(clippingPlanesLength) {
|
||||
const functionString =
|
||||
`${
|
||||
"float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n" +
|
||||
"{\n" +
|
||||
" vec4 position = czm_windowToEyeCoordinates(fragCoord);\n" +
|
||||
" vec3 clipNormal = vec3(0.0);\n" +
|
||||
" vec3 clipPosition = vec3(0.0);\n" +
|
||||
" float clipAmount;\n" + // For union planes, we want to get the min distance. So we set the initial value to the first plane distance in the loop below.
|
||||
" float pixelWidth = czm_metersPerPixel(position);\n" +
|
||||
" bool breakAndDiscard = false;\n" +
|
||||
" for (int i = 0; i < "
|
||||
}${clippingPlanesLength}; ++i)\n` +
|
||||
` {\n` +
|
||||
` vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n` +
|
||||
` clipNormal = clippingPlane.xyz;\n` +
|
||||
` clipPosition = -clippingPlane.w * clipNormal;\n` +
|
||||
` float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n` +
|
||||
` clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount));\n` +
|
||||
` if (amount <= 0.0)\n` +
|
||||
` {\n` +
|
||||
` breakAndDiscard = true;\n` +
|
||||
` break;\n` + // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6
|
||||
` }\n` +
|
||||
` }\n` +
|
||||
` if (breakAndDiscard) {\n` +
|
||||
` discard;\n` +
|
||||
` }\n` +
|
||||
` return clipAmount;\n` +
|
||||
`}\n`;
|
||||
return functionString;
|
||||
return `float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)
|
||||
{
|
||||
vec4 position = czm_windowToEyeCoordinates(fragCoord);
|
||||
vec3 clipNormal = vec3(0.0);
|
||||
vec3 clipPosition = vec3(0.0);
|
||||
float clipAmount;
|
||||
float pixelWidth = czm_metersPerPixel(position);
|
||||
bool breakAndDiscard = false;
|
||||
for (int i = 0; i < ${clippingPlanesLength}; ++i)
|
||||
{
|
||||
vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);
|
||||
clipNormal = clippingPlane.xyz;
|
||||
clipPosition = -clippingPlane.w * clipNormal;
|
||||
float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;
|
||||
clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount));
|
||||
if (amount <= 0.0)
|
||||
{
|
||||
breakAndDiscard = true;
|
||||
// HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (breakAndDiscard) {
|
||||
discard;
|
||||
}
|
||||
return clipAmount;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function clippingFunctionIntersect(clippingPlanesLength) {
|
||||
const functionString =
|
||||
`${
|
||||
"float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n" +
|
||||
"{\n" +
|
||||
" bool clipped = true;\n" +
|
||||
" vec4 position = czm_windowToEyeCoordinates(fragCoord);\n" +
|
||||
" vec3 clipNormal = vec3(0.0);\n" +
|
||||
" vec3 clipPosition = vec3(0.0);\n" +
|
||||
" float clipAmount = 0.0;\n" +
|
||||
" float pixelWidth = czm_metersPerPixel(position);\n" +
|
||||
" for (int i = 0; i < "
|
||||
}${clippingPlanesLength}; ++i)\n` +
|
||||
` {\n` +
|
||||
` vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n` +
|
||||
` clipNormal = clippingPlane.xyz;\n` +
|
||||
` clipPosition = -clippingPlane.w * clipNormal;\n` +
|
||||
` float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n` +
|
||||
` clipAmount = max(amount, clipAmount);\n` +
|
||||
` clipped = clipped && (amount <= 0.0);\n` +
|
||||
` }\n` +
|
||||
` if (clipped)\n` +
|
||||
` {\n` +
|
||||
` discard;\n` +
|
||||
` }\n` +
|
||||
` return clipAmount;\n` +
|
||||
`}\n`;
|
||||
return functionString;
|
||||
return `float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)
|
||||
{
|
||||
bool clipped = true;
|
||||
vec4 position = czm_windowToEyeCoordinates(fragCoord);
|
||||
vec3 clipNormal = vec3(0.0);
|
||||
vec3 clipPosition = vec3(0.0);
|
||||
float clipAmount = 0.0;
|
||||
float pixelWidth = czm_metersPerPixel(position);
|
||||
for (int i = 0; i < ${clippingPlanesLength}; ++i)
|
||||
{
|
||||
vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);
|
||||
clipNormal = clippingPlane.xyz;
|
||||
clipPosition = -clippingPlane.w * clipNormal;
|
||||
float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;
|
||||
clipAmount = max(amount, clipAmount);
|
||||
clipped = clipped && (amount <= 0.0);
|
||||
}
|
||||
if (clipped)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
return clipAmount;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function getClippingPlaneFloat(width, height) {
|
||||
|
|
@ -113,19 +108,17 @@ function getClippingPlaneFloat(width, height) {
|
|||
pixelHeightString += ".0";
|
||||
}
|
||||
|
||||
const functionString =
|
||||
`${
|
||||
"vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n" +
|
||||
"{\n" +
|
||||
" int pixY = clippingPlaneNumber / "
|
||||
}${width};\n` +
|
||||
` int pixX = clippingPlaneNumber - (pixY * ${width});\n` +
|
||||
` float u = (float(pixX) + 0.5) * ${pixelWidthString};\n` + // sample from center of pixel
|
||||
` float v = (float(pixY) + 0.5) * ${pixelHeightString};\n` +
|
||||
` vec4 plane = texture(packedClippingPlanes, vec2(u, v));\n` +
|
||||
` return czm_transformPlane(plane, transform);\n` +
|
||||
`}\n`;
|
||||
return functionString;
|
||||
return `vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)
|
||||
{
|
||||
int pixY = clippingPlaneNumber / ${width};
|
||||
int pixX = clippingPlaneNumber - (pixY * ${width});
|
||||
// Sample from center of pixel
|
||||
float u = (float(pixX) + 0.5) * ${pixelWidthString};
|
||||
float v = (float(pixY) + 0.5) * ${pixelHeightString};
|
||||
vec4 plane = texture(packedClippingPlanes, vec2(u, v));
|
||||
return czm_transformPlane(plane, transform);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function getClippingPlaneUint8(width, height) {
|
||||
|
|
@ -141,23 +134,21 @@ function getClippingPlaneUint8(width, height) {
|
|||
pixelHeightString += ".0";
|
||||
}
|
||||
|
||||
const functionString =
|
||||
`${
|
||||
"vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n" +
|
||||
"{\n" +
|
||||
" int clippingPlaneStartIndex = clippingPlaneNumber * 2;\n" + // clipping planes are two pixels each
|
||||
" int pixY = clippingPlaneStartIndex / "
|
||||
}${width};\n` +
|
||||
` int pixX = clippingPlaneStartIndex - (pixY * ${width});\n` +
|
||||
` float u = (float(pixX) + 0.5) * ${pixelWidthString};\n` + // sample from center of pixel
|
||||
` float v = (float(pixY) + 0.5) * ${pixelHeightString};\n` +
|
||||
` vec4 oct32 = texture(packedClippingPlanes, vec2(u, v)) * 255.0;\n` +
|
||||
` vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w);\n` +
|
||||
` vec4 plane;\n` +
|
||||
` plane.xyz = czm_octDecode(oct, 65535.0);\n` +
|
||||
` plane.w = czm_unpackFloat(texture(packedClippingPlanes, vec2(u + ${pixelWidthString}, v)));\n` +
|
||||
` return czm_transformPlane(plane, transform);\n` +
|
||||
`}\n`;
|
||||
return functionString;
|
||||
return `vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)
|
||||
{
|
||||
int clippingPlaneStartIndex = clippingPlaneNumber * 2;
|
||||
int pixY = clippingPlaneStartIndex / ${width};
|
||||
int pixX = clippingPlaneStartIndex - (pixY * ${width});
|
||||
// Sample from center of pixel
|
||||
float u = (float(pixX) + 0.5) * ${pixelWidthString};
|
||||
float v = (float(pixY) + 0.5) * ${pixelHeightString};
|
||||
vec4 oct32 = texture(packedClippingPlanes, vec2(u, v)) * 255.0;
|
||||
vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w);
|
||||
vec4 plane;
|
||||
plane.xyz = czm_octDecode(oct, 65535.0);
|
||||
plane.w = czm_unpackFloat(texture(packedClippingPlanes, vec2(u + ${pixelWidthString}, v)));
|
||||
return czm_transformPlane(plane, transform);
|
||||
}
|
||||
`;
|
||||
}
|
||||
export default getClippingFunction;
|
||||
|
|
|
|||
|
|
@ -5,43 +5,30 @@
|
|||
#define BOX_INTERSECTION_INDEX ### // always 0
|
||||
*/
|
||||
|
||||
uniform vec3 u_renderMinBounds;
|
||||
uniform vec3 u_renderMaxBounds;
|
||||
uniform sampler2D u_renderBoundPlanesTexture;
|
||||
|
||||
RayShapeIntersection intersectBox(in Ray ray, in vec3 minBound, in vec3 maxBound)
|
||||
{
|
||||
// Consider the box as the intersection of the space between 3 pairs of parallel planes
|
||||
// Compute the distance along the ray to each plane
|
||||
vec3 t0 = (minBound - ray.pos) / ray.dir;
|
||||
vec3 t1 = (maxBound - ray.pos) / ray.dir;
|
||||
|
||||
// Identify candidate entries/exits based on distance from ray.pos
|
||||
vec3 entries = min(t0, t1);
|
||||
vec3 exits = max(t0, t1);
|
||||
|
||||
vec3 directions = sign(ray.dir);
|
||||
|
||||
// The actual intersection points are the furthest entry and the closest exit
|
||||
float lastEntry = maxComponent(entries);
|
||||
bvec3 isLastEntry = equal(entries, vec3(lastEntry));
|
||||
vec3 entryNormal = -1.0 * vec3(isLastEntry) * directions;
|
||||
vec4 entry = vec4(entryNormal, lastEntry);
|
||||
|
||||
float firstExit = minComponent(exits);
|
||||
bvec3 isFirstExit = equal(exits, vec3(firstExit));
|
||||
vec3 exitNormal = vec3(isLastEntry) * directions;
|
||||
vec4 exit = vec4(exitNormal, firstExit);
|
||||
|
||||
if (entry.w > exit.w) {
|
||||
entry.w = NO_HIT;
|
||||
exit.w = NO_HIT;
|
||||
RayShapeIntersection intersectBoundPlanes(in Ray ray) {
|
||||
vec4 lastEntry = vec4(ray.dir, -INF_HIT);
|
||||
vec4 firstExit = vec4(-ray.dir, +INF_HIT);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
vec4 boundPlane = getBoundPlane(u_renderBoundPlanesTexture, i);
|
||||
vec4 intersection = intersectPlane(ray, boundPlane);
|
||||
if (dot(ray.dir, boundPlane.xyz) < 0.0) {
|
||||
lastEntry = intersection.w > lastEntry.w ? intersection : lastEntry;
|
||||
} else {
|
||||
firstExit = intersection.w < firstExit.w ? intersection: firstExit;
|
||||
}
|
||||
}
|
||||
|
||||
return RayShapeIntersection(entry, exit);
|
||||
if (lastEntry.w < firstExit.w) {
|
||||
return RayShapeIntersection(lastEntry, firstExit);
|
||||
} else {
|
||||
return RayShapeIntersection(vec4(-ray.dir, NO_HIT), vec4(ray.dir, NO_HIT));
|
||||
}
|
||||
}
|
||||
|
||||
void intersectShape(in Ray ray, inout Intersections ix)
|
||||
void intersectShape(in Ray rayUV, in Ray rayEC, inout Intersections ix)
|
||||
{
|
||||
RayShapeIntersection intersection = intersectBox(ray, u_renderMinBounds, u_renderMaxBounds);
|
||||
RayShapeIntersection intersection = intersectBoundPlanes(rayEC);
|
||||
setShapeIntersection(ix, BOX_INTERSECTION_INDEX, intersection);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,32 +19,30 @@
|
|||
|
||||
// Cylinder uniforms
|
||||
uniform vec2 u_cylinderRenderRadiusMinMax;
|
||||
uniform vec2 u_cylinderRenderHeightMinMax;
|
||||
#if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE)
|
||||
uniform vec2 u_cylinderRenderAngleMinMax;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Find the intersection of a ray with the volume defined by two planes of constant z
|
||||
*/
|
||||
RayShapeIntersection intersectHeightBounds(in Ray ray, in vec2 minMaxHeight, in bool convex)
|
||||
{
|
||||
float zPosition = ray.pos.z;
|
||||
float zDirection = ray.dir.z;
|
||||
uniform sampler2D u_renderBoundPlanesTexture;
|
||||
|
||||
float tmin = (minMaxHeight.x - zPosition) / zDirection;
|
||||
float tmax = (minMaxHeight.y - zPosition) / zDirection;
|
||||
RayShapeIntersection intersectBoundPlanes(in Ray ray) {
|
||||
vec4 lastEntry = vec4(ray.dir, -INF_HIT);
|
||||
vec4 firstExit = vec4(-ray.dir, +INF_HIT);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
vec4 boundPlane = getBoundPlane(u_renderBoundPlanesTexture, i);
|
||||
vec4 intersection = intersectPlane(ray, boundPlane);
|
||||
if (dot(ray.dir, boundPlane.xyz) < 0.0) {
|
||||
lastEntry = intersection.w > lastEntry.w ? intersection : lastEntry;
|
||||
} else {
|
||||
firstExit = intersection.w < firstExit.w ? intersection: firstExit;
|
||||
}
|
||||
}
|
||||
|
||||
// Normals point outside the volume
|
||||
float signFlip = convex ? 1.0 : -1.0;
|
||||
vec4 intersectMin = vec4(0.0, 0.0, -1.0 * signFlip, tmin);
|
||||
vec4 intersectMax = vec4(0.0, 0.0, 1.0 * signFlip, tmax);
|
||||
|
||||
bool topEntry = zDirection < 0.0;
|
||||
vec4 entry = topEntry ? intersectMax : intersectMin;
|
||||
vec4 exit = topEntry ? intersectMin : intersectMax;
|
||||
|
||||
return RayShapeIntersection(entry, exit);
|
||||
if (lastEntry.w < firstExit.w) {
|
||||
return RayShapeIntersection(lastEntry, firstExit);
|
||||
} else {
|
||||
return RayShapeIntersection(vec4(-ray.dir, NO_HIT), vec4(ray.dir, NO_HIT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,8 +68,11 @@ RayShapeIntersection intersectCylinder(in Ray ray, in float radius, in bool conv
|
|||
float t1 = (-b - determinant) / a;
|
||||
float t2 = (-b + determinant) / a;
|
||||
float signFlip = convex ? 1.0 : -1.0;
|
||||
vec4 intersect1 = vec4(normalize(position + t1 * direction) * signFlip, 0.0, t1);
|
||||
vec4 intersect2 = vec4(normalize(position + t2 * direction) * signFlip, 0.0, t2);
|
||||
vec3 normal1 = vec3((position + t1 * direction) * signFlip, 0.0);
|
||||
vec3 normal2 = vec3((position + t2 * direction) * signFlip, 0.0);
|
||||
// Return normals in eye coordinates
|
||||
vec4 intersect1 = vec4(normalize(czm_normal * normal1), t1);
|
||||
vec4 intersect2 = vec4(normalize(czm_normal * normal2), t2);
|
||||
|
||||
return RayShapeIntersection(intersect1, intersect2);
|
||||
}
|
||||
|
|
@ -80,21 +81,16 @@ RayShapeIntersection intersectCylinder(in Ray ray, in float radius, in bool conv
|
|||
* Find the intersection of a ray with a right cylindrical solid of given
|
||||
* radius and height bounds. NOTE: The shape is assumed to be convex.
|
||||
*/
|
||||
RayShapeIntersection intersectBoundedCylinder(in Ray ray, in float radius, in vec2 minMaxHeight)
|
||||
RayShapeIntersection intersectBoundedCylinder(in Ray ray, in Ray rayEC, in float radius)
|
||||
{
|
||||
RayShapeIntersection cylinderIntersection = intersectCylinder(ray, radius, true);
|
||||
RayShapeIntersection heightBoundsIntersection = intersectHeightBounds(ray, minMaxHeight, true);
|
||||
RayShapeIntersection heightBoundsIntersection = intersectBoundPlanes(rayEC);
|
||||
return intersectIntersections(ray, cylinderIntersection, heightBoundsIntersection);
|
||||
}
|
||||
|
||||
void intersectShape(Ray ray, inout Intersections ix)
|
||||
void intersectShape(in Ray ray, in Ray rayEC, inout Intersections ix)
|
||||
{
|
||||
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
|
||||
// Direction is scaled as well to be in sync with position.
|
||||
ray.pos = ray.pos * 2.0 - 1.0;
|
||||
ray.dir *= 2.0;
|
||||
|
||||
RayShapeIntersection outerIntersect = intersectBoundedCylinder(ray, u_cylinderRenderRadiusMinMax.y, u_cylinderRenderHeightMinMax);
|
||||
RayShapeIntersection outerIntersect = intersectBoundedCylinder(ray, rayEC, u_cylinderRenderRadiusMinMax.y);
|
||||
|
||||
setShapeIntersection(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
#define DEPTH_INTERSECTION_INDEX ###
|
||||
*/
|
||||
|
||||
uniform mat4 u_transformPositionViewToUv;
|
||||
|
||||
void intersectDepth(in vec2 screenCoord, in Ray ray, inout Intersections ix) {
|
||||
float logDepthOrDepth = czm_unpackDepth(texture(czm_globeDepthTexture, screenCoord));
|
||||
float entry;
|
||||
|
|
@ -15,8 +13,7 @@ void intersectDepth(in vec2 screenCoord, in Ray ray, inout Intersections ix) {
|
|||
// Calculate how far the ray must travel before it hits the depth buffer.
|
||||
vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
|
||||
eyeCoordinateDepth /= eyeCoordinateDepth.w;
|
||||
vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
|
||||
entry = dot(depthPositionUv - ray.pos, ray.dir);
|
||||
entry = dot(eyeCoordinateDepth.xyz - ray.pos, ray.dir);
|
||||
exit = +INF_HIT;
|
||||
} else {
|
||||
// There's no depth at this location.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
#endif
|
||||
uniform float u_eccentricitySquared;
|
||||
uniform vec2 u_ellipsoidRenderLatitudeSinMinMax;
|
||||
uniform vec2 u_clipMinMaxHeight;
|
||||
uniform vec2 u_clipMinMaxHeight; // Values are negative: clipHeight - maxShapeHeight
|
||||
|
||||
RayShapeIntersection intersectZPlane(in Ray ray, in float z) {
|
||||
float t = -ray.pos.z / ray.dir.z;
|
||||
|
|
@ -44,11 +44,11 @@ RayShapeIntersection intersectZPlane(in Ray ray, in float z) {
|
|||
}
|
||||
}
|
||||
|
||||
RayShapeIntersection intersectHeight(in Ray ray, in float relativeHeight, in bool convex)
|
||||
RayShapeIntersection intersectHeight(in Ray ray, in float height, in bool convex)
|
||||
{
|
||||
// Scale the ray by the ellipsoid axes to make it a unit sphere
|
||||
// Note: approximating ellipsoid + height as an ellipsoid
|
||||
vec3 radiiCorrection = u_ellipsoidRadiiUv / (u_ellipsoidRadiiUv + relativeHeight);
|
||||
vec3 radiiCorrection = vec3(1.0) / (u_ellipsoidRadii + height);
|
||||
vec3 position = ray.pos * radiiCorrection;
|
||||
vec3 direction = ray.dir * radiiCorrection;
|
||||
|
||||
|
|
@ -74,10 +74,14 @@ RayShapeIntersection intersectHeight(in Ray ray, in float relativeHeight, in boo
|
|||
float tmax = max(t1, t2);
|
||||
|
||||
float directionScale = convex ? 1.0 : -1.0;
|
||||
vec3 d1 = directionScale * normalize(position + tmin * direction);
|
||||
vec3 d2 = directionScale * normalize(position + tmax * direction);
|
||||
vec3 d1 = directionScale * (position + tmin * direction);
|
||||
vec3 d2 = directionScale * (position + tmax * direction);
|
||||
|
||||
return RayShapeIntersection(vec4(d1, tmin), vec4(d2, tmax));
|
||||
// Return normals in eye coordinates. Use spherical approximation for the normal.
|
||||
vec3 normal1 = normalize(czm_normal * d1);
|
||||
vec3 normal2 = normalize(czm_normal * d2);
|
||||
|
||||
return RayShapeIntersection(vec4(normal1, tmin), vec4(normal2, tmax));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,16 +155,13 @@ float getLatitudeConeShift(in float sinLatitude) {
|
|||
// Find prime vertical radius of curvature:
|
||||
// the distance along the ellipsoid normal to the intersection with the z-axis
|
||||
float x2 = u_eccentricitySquared * sinLatitude * sinLatitude;
|
||||
float primeVerticalRadius = inversesqrt(1.0 - x2);
|
||||
float primeVerticalRadius = u_ellipsoidRadii.x * inversesqrt(1.0 - x2);
|
||||
|
||||
// Compute a shift from the origin to the intersection of the cone with the z-axis
|
||||
return primeVerticalRadius * u_eccentricitySquared * sinLatitude;
|
||||
}
|
||||
|
||||
void intersectFlippedCone(in Ray ray, in float cosHalfAngle, out RayShapeIntersection intersections[2]) {
|
||||
// Undo the scaling from ellipsoid to sphere
|
||||
ray.pos = ray.pos * u_ellipsoidRadiiUv;
|
||||
ray.dir = ray.dir * u_ellipsoidRadiiUv;
|
||||
// Shift the ray to account for the latitude cone not being centered at the Earth center
|
||||
ray.pos.z += getLatitudeConeShift(cosHalfAngle);
|
||||
|
||||
|
|
@ -206,9 +207,6 @@ void intersectFlippedCone(in Ray ray, in float cosHalfAngle, out RayShapeInterse
|
|||
}
|
||||
|
||||
RayShapeIntersection intersectRegularCone(in Ray ray, in float cosHalfAngle, in bool convex) {
|
||||
// Undo the scaling from ellipsoid to sphere
|
||||
ray.pos = ray.pos * u_ellipsoidRadiiUv;
|
||||
ray.dir = ray.dir * u_ellipsoidRadiiUv;
|
||||
// Shift the ray to account for the latitude cone not being centered at the Earth center
|
||||
ray.pos.z += getLatitudeConeShift(cosHalfAngle);
|
||||
|
||||
|
|
@ -245,13 +243,7 @@ RayShapeIntersection intersectRegularCone(in Ray ray, in float cosHalfAngle, in
|
|||
}
|
||||
}
|
||||
|
||||
void intersectShape(in Ray ray, inout Intersections ix) {
|
||||
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
|
||||
// Direction is scaled as well to be in sync with position.
|
||||
ray.pos = ray.pos * 2.0 - 1.0;
|
||||
ray.dir *= 2.0;
|
||||
|
||||
// Outer ellipsoid
|
||||
void intersectShape(in Ray ray, in Ray rayEC, inout Intersections ix) { // Outer ellipsoid
|
||||
RayShapeIntersection outerIntersect = intersectHeight(ray, u_clipMinMaxHeight.y, true);
|
||||
setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// See IntersectionUtils.glsl for the definitions of Ray, NO_HIT, INF_HIT,
|
||||
// RayShapeIntersection
|
||||
|
||||
vec4 transformNormalToEC(in vec4 intersection) {
|
||||
return vec4(normalize(czm_normal * intersection.xyz), intersection.w);
|
||||
}
|
||||
|
||||
RayShapeIntersection transformNormalsToEC(in RayShapeIntersection ix) {
|
||||
return RayShapeIntersection(transformNormalToEC(ix.entry), transformNormalToEC(ix.exit));
|
||||
}
|
||||
|
||||
vec4 intersectLongitude(in Ray ray, in float angle, in bool positiveNormal) {
|
||||
float normalSign = positiveNormal ? 1.0 : -1.0;
|
||||
vec2 planeNormal = vec2(-sin(angle), cos(angle)) * normalSign;
|
||||
|
|
@ -32,8 +40,8 @@ RayShapeIntersection intersectHalfSpace(in Ray ray, in float angle, in bool posi
|
|||
|
||||
void intersectFlippedWedge(in Ray ray, in vec2 minMaxAngle, out RayShapeIntersection intersections[2])
|
||||
{
|
||||
intersections[0] = intersectHalfSpace(ray, minMaxAngle.x, false);
|
||||
intersections[1] = intersectHalfSpace(ray, minMaxAngle.y, true);
|
||||
intersections[0] = transformNormalsToEC(intersectHalfSpace(ray, minMaxAngle.x, false));
|
||||
intersections[1] = transformNormalsToEC(intersectHalfSpace(ray, minMaxAngle.y, true));
|
||||
}
|
||||
|
||||
bool hitPositiveHalfPlane(in Ray ray, in vec4 intersection, in bool positiveNormal) {
|
||||
|
|
@ -46,14 +54,18 @@ bool hitPositiveHalfPlane(in Ray ray, in vec4 intersection, in bool positiveNorm
|
|||
void intersectHalfPlane(in Ray ray, in float angle, out RayShapeIntersection intersections[2]) {
|
||||
vec4 intersection = intersectLongitude(ray, angle, true);
|
||||
vec4 farSide = vec4(normalize(ray.dir), INF_HIT);
|
||||
bool hitPositiveSide = hitPositiveHalfPlane(ray, intersection, true);
|
||||
|
||||
if (hitPositiveHalfPlane(ray, intersection, true)) {
|
||||
farSide = transformNormalToEC(farSide);
|
||||
|
||||
if (hitPositiveSide) {
|
||||
intersection = transformNormalToEC(intersection);
|
||||
intersections[0].entry = -1.0 * farSide;
|
||||
intersections[0].exit = vec4(-1.0 * intersection.xy, 0.0, intersection.w);
|
||||
intersections[0].exit = vec4(-1.0 * intersection.xyz, intersection.w);
|
||||
intersections[1].entry = intersection;
|
||||
intersections[1].exit = farSide;
|
||||
} else {
|
||||
vec4 miss = vec4(normalize(ray.dir), NO_HIT);
|
||||
vec4 miss = vec4(normalize(czm_normal * ray.dir), NO_HIT);
|
||||
intersections[0].entry = -1.0 * farSide;
|
||||
intersections[0].exit = farSide;
|
||||
intersections[1].entry = miss;
|
||||
|
|
@ -88,15 +100,15 @@ RayShapeIntersection intersectRegularWedge(in Ray ray, in vec2 minMaxAngle)
|
|||
|
||||
if (exitFromInside && enterFromOutside) {
|
||||
// Ray crosses both faces of negative wedge, exiting then entering the positive shape
|
||||
return RayShapeIntersection(first, last);
|
||||
return transformNormalsToEC(RayShapeIntersection(first, last));
|
||||
} else if (!exitFromInside && enterFromOutside) {
|
||||
// Ray starts inside wedge. last is in shadow wedge, and first is actually the entry
|
||||
return RayShapeIntersection(-1.0 * farSide, first);
|
||||
return transformNormalsToEC(RayShapeIntersection(-1.0 * farSide, first));
|
||||
} else if (exitFromInside && !enterFromOutside) {
|
||||
// First intersection was in the shadow wedge, so last is actually the exit
|
||||
return RayShapeIntersection(last, farSide);
|
||||
return transformNormalsToEC(RayShapeIntersection(last, farSide));
|
||||
} else { // !exitFromInside && !enterFromOutside
|
||||
// Both intersections were in the shadow wedge
|
||||
return RayShapeIntersection(miss, miss);
|
||||
return transformNormalsToEC(RayShapeIntersection(miss, miss));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ vec4 intersectPlane(in Ray ray, in vec4 plane) {
|
|||
return vec4(n, t);
|
||||
}
|
||||
|
||||
#ifdef CLIPPING_PLANES
|
||||
void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
|
||||
vec4 backSide = vec4(-ray.dir, -INF_HIT);
|
||||
vec4 farSide = vec4(ray.dir, +INF_HIT);
|
||||
|
|
@ -30,7 +31,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
|
|||
#if (CLIPPING_PLANES_COUNT == 1)
|
||||
// Union and intersection are the same when there's one clipping plane, and the code
|
||||
// is more simplified.
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, 0, u_clippingPlanesMatrix);
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, 0);
|
||||
vec4 intersection = intersectPlane(ray, planeUv);
|
||||
bool reflects = dot(ray.dir, intersection.xyz) < 0.0;
|
||||
clippingVolume.entry = reflects ? backSide : intersection;
|
||||
|
|
@ -40,7 +41,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
|
|||
vec4 firstTransmission = vec4(ray.dir, +INF_HIT);
|
||||
vec4 lastReflection = vec4(-ray.dir, -INF_HIT);
|
||||
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i);
|
||||
vec4 intersection = intersectPlane(ray, planeUv);
|
||||
if (dot(ray.dir, planeUv.xyz) > 0.0) {
|
||||
firstTransmission = intersection.w <= firstTransmission.w ? intersection : firstTransmission;
|
||||
|
|
@ -58,7 +59,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
|
|||
vec4 lastTransmission = vec4(ray.dir, -INF_HIT);
|
||||
vec4 firstReflection = vec4(-ray.dir, +INF_HIT);
|
||||
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
|
||||
vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i);
|
||||
vec4 intersection = intersectPlane(ray, planeUv);
|
||||
if (dot(ray.dir, planeUv.xyz) > 0.0) {
|
||||
lastTransmission = intersection.w > lastTransmission.w ? intersection : lastTransmission;
|
||||
|
|
@ -76,3 +77,4 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
|
|||
setShapeIntersection(ix, CLIPPING_PLANES_INTERSECTION_INDEX, clippingVolume);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
#define INTERSECTION_COUNT ###
|
||||
*/
|
||||
|
||||
RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, out Intersections ix) {
|
||||
RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, in Ray rayEC, out Intersections ix) {
|
||||
// Do a ray-shape intersection to find the exact starting and ending points.
|
||||
intersectShape(ray, ix);
|
||||
intersectShape(ray, rayEC, ix);
|
||||
|
||||
// Exit early if the positive shape was completely missed or behind the ray.
|
||||
RayShapeIntersection intersection = getFirstIntersection(ix);
|
||||
|
|
@ -28,7 +28,7 @@ RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, out Interse
|
|||
#endif
|
||||
|
||||
// Depth
|
||||
intersectDepth(screenCoord, ray, ix);
|
||||
intersectDepth(screenCoord, rayEC, ix);
|
||||
|
||||
// Find the first intersection that's in front of the ray
|
||||
#if (INTERSECTION_COUNT > 1)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ struct TraversalData {
|
|||
int parentOctreeIndex;
|
||||
};
|
||||
|
||||
struct TileAndUvCoordinate {
|
||||
ivec4 tileCoords;
|
||||
vec3 tileUv;
|
||||
};
|
||||
|
||||
struct SampleData {
|
||||
int megatextureIndex;
|
||||
ivec4 tileCoords;
|
||||
|
|
@ -79,23 +84,20 @@ int getOctreeParentIndex(in int octreeIndex) {
|
|||
return parentOctreeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a position in the uv-space of the tileset bounding shape
|
||||
* into the uv-space of a tile within the tileset
|
||||
*/
|
||||
vec3 getTileUv(in vec3 shapePosition, in ivec4 octreeCoords) {
|
||||
// PERFORMANCE_IDEA: use bit-shifting (only in WebGL2)
|
||||
float dimAtLevel = exp2(float(octreeCoords.w));
|
||||
return shapePosition * dimAtLevel - vec3(octreeCoords.xyz);
|
||||
vec3 getTileUv(in TileAndUvCoordinate tileAndUv, in ivec4 octreeCoords) {
|
||||
int levelDifference = tileAndUv.tileCoords.w - octreeCoords.w;
|
||||
float scalar = exp2(-1.0 * float(levelDifference));
|
||||
vec3 originShift = vec3(tileAndUv.tileCoords.xyz - (octreeCoords.xyz << levelDifference)) * scalar;
|
||||
return tileAndUv.tileUv * scalar + originShift;
|
||||
}
|
||||
|
||||
vec3 getClampedTileUv(in vec3 shapePosition, in ivec4 octreeCoords) {
|
||||
vec3 tileUv = getTileUv(shapePosition, octreeCoords);
|
||||
vec3 getClampedTileUv(in TileAndUvCoordinate tileAndUv, in ivec4 octreeCoords) {
|
||||
vec3 tileUv = getTileUv(tileAndUv, octreeCoords);
|
||||
return clamp(tileUv, vec3(0.0), vec3(1.0));
|
||||
}
|
||||
|
||||
void addSampleCoordinates(in vec3 shapePosition, inout SampleData sampleData) {
|
||||
vec3 tileUv = getClampedTileUv(shapePosition, sampleData.tileCoords);
|
||||
void addSampleCoordinates(in TileAndUvCoordinate tileAndUv, inout SampleData sampleData) {
|
||||
vec3 tileUv = getClampedTileUv(tileAndUv, sampleData.tileCoords);
|
||||
|
||||
vec3 inputCoordinate = tileUv * vec3(u_dimensions);
|
||||
#if defined(PADDING)
|
||||
|
|
@ -162,32 +164,25 @@ void getOctreeLeafSampleDatas(in OctreeNodeData data, in ivec4 octreeCoords, out
|
|||
}
|
||||
#endif
|
||||
|
||||
OctreeNodeData traverseOctreeDownwards(in vec3 shapePosition, inout TraversalData traversalData) {
|
||||
float sizeAtLevel = exp2(-1.0 * float(traversalData.octreeCoords.w));
|
||||
vec3 start = vec3(traversalData.octreeCoords.xyz) * sizeAtLevel;
|
||||
vec3 end = start + vec3(sizeAtLevel);
|
||||
OctreeNodeData traverseOctreeDownwards(in ivec4 tileCoordinate, inout TraversalData traversalData) {
|
||||
OctreeNodeData childData;
|
||||
|
||||
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
|
||||
// Find out which octree child contains the position
|
||||
// 0 if before center, 1 if after
|
||||
vec3 center = 0.5 * (start + end);
|
||||
vec3 childCoord = step(center, shapePosition);
|
||||
// tileCoordinate.xyz is defined at the level of detail tileCoordinate.w.
|
||||
// Find the corresponding coordinate at the level traversalData.octreeCoords.w
|
||||
int level = traversalData.octreeCoords.w + 1;
|
||||
int levelDifference = tileCoordinate.w - level;
|
||||
ivec3 coordinateAtLevel = tileCoordinate.xyz >> levelDifference;
|
||||
traversalData.octreeCoords = ivec4(coordinateAtLevel, level);
|
||||
|
||||
// Get octree coords for the next level down
|
||||
ivec4 octreeCoords = traversalData.octreeCoords;
|
||||
traversalData.octreeCoords = ivec4(octreeCoords.xyz * 2 + ivec3(childCoord), octreeCoords.w + 1);
|
||||
|
||||
childData = getOctreeChildData(traversalData.parentOctreeIndex, ivec3(childCoord));
|
||||
ivec3 childCoordinate = coordinateAtLevel & 1;
|
||||
childData = getOctreeChildData(traversalData.parentOctreeIndex, childCoordinate);
|
||||
|
||||
if (childData.flag != OCTREE_FLAG_INTERNAL) {
|
||||
// leaf tile - stop traversing
|
||||
break;
|
||||
}
|
||||
|
||||
// interior tile - keep going deeper
|
||||
start = mix(start, center, childCoord);
|
||||
end = mix(center, end, childCoord);
|
||||
traversalData.parentOctreeIndex = childData.data;
|
||||
}
|
||||
|
||||
|
|
@ -198,50 +193,50 @@ OctreeNodeData traverseOctreeDownwards(in vec3 shapePosition, inout TraversalDat
|
|||
* Transform a given position to an octree tile coordinate and a position within that tile,
|
||||
* and find the corresponding megatexture index and texture coordinates
|
||||
*/
|
||||
void traverseOctreeFromBeginning(in vec3 shapePosition, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
|
||||
void traverseOctreeFromBeginning(in TileAndUvCoordinate tileAndUv, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
|
||||
traversalData.octreeCoords = ivec4(0);
|
||||
traversalData.parentOctreeIndex = 0;
|
||||
|
||||
OctreeNodeData nodeData = getOctreeNodeData(vec2(0.0));
|
||||
if (nodeData.flag != OCTREE_FLAG_LEAF) {
|
||||
nodeData = traverseOctreeDownwards(shapePosition, traversalData);
|
||||
nodeData = traverseOctreeDownwards(tileAndUv.tileCoords, traversalData);
|
||||
}
|
||||
|
||||
#if (SAMPLE_COUNT == 1)
|
||||
getOctreeLeafSampleData(nodeData, traversalData.octreeCoords, sampleDatas[0]);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[0]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[0]);
|
||||
#else
|
||||
getOctreeLeafSampleDatas(nodeData, traversalData.octreeCoords, sampleDatas);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[0]);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[1]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[0]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[1]);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool inRange(in vec3 v, in vec3 minVal, in vec3 maxVal) {
|
||||
return clamp(v, minVal, maxVal) == v;
|
||||
bool insideTile(in ivec4 tileCoordinate, in ivec4 octreeCoords) {
|
||||
int levelDifference = tileCoordinate.w - octreeCoords.w;
|
||||
if (levelDifference < 0) {
|
||||
return false;
|
||||
}
|
||||
ivec3 coordinateAtLevel = tileCoordinate.xyz >> levelDifference;
|
||||
return coordinateAtLevel == octreeCoords.xyz;
|
||||
}
|
||||
|
||||
bool insideTile(in vec3 shapePosition, in ivec4 octreeCoords) {
|
||||
vec3 tileUv = getTileUv(shapePosition, octreeCoords);
|
||||
bool inside = inRange(tileUv, vec3(0.0), vec3(1.0));
|
||||
// Assume (!) the position is always inside the root tile.
|
||||
return inside || octreeCoords.w == 0;
|
||||
}
|
||||
|
||||
void traverseOctreeFromExisting(in vec3 shapePosition, inout TraversalData traversalData, inout SampleData sampleDatas[SAMPLE_COUNT]) {
|
||||
if (insideTile(shapePosition, traversalData.octreeCoords)) {
|
||||
void traverseOctreeFromExisting(in TileAndUvCoordinate tileAndUv, inout TraversalData traversalData, inout SampleData sampleDatas[SAMPLE_COUNT]) {
|
||||
ivec4 tileCoords = tileAndUv.tileCoords;
|
||||
if (insideTile(tileCoords, traversalData.octreeCoords)) {
|
||||
for (int i = 0; i < SAMPLE_COUNT; i++) {
|
||||
addSampleCoordinates(shapePosition, sampleDatas[i]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Go up tree until we find a parent tile containing shapePosition
|
||||
// Go up tree until we find a parent tile containing tileCoords.
|
||||
// Assumes all parents are available all they way up to the root.
|
||||
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
|
||||
traversalData.octreeCoords.xyz /= 2;
|
||||
traversalData.octreeCoords.w -= 1;
|
||||
|
||||
if (insideTile(shapePosition, traversalData.octreeCoords)) {
|
||||
if (insideTile(tileCoords, traversalData.octreeCoords)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -249,14 +244,14 @@ void traverseOctreeFromExisting(in vec3 shapePosition, inout TraversalData trave
|
|||
}
|
||||
|
||||
// Go down tree
|
||||
OctreeNodeData nodeData = traverseOctreeDownwards(shapePosition, traversalData);
|
||||
OctreeNodeData nodeData = traverseOctreeDownwards(tileCoords, traversalData);
|
||||
|
||||
#if (SAMPLE_COUNT == 1)
|
||||
getOctreeLeafSampleData(nodeData, traversalData.octreeCoords, sampleDatas[0]);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[0]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[0]);
|
||||
#else
|
||||
getOctreeLeafSampleDatas(nodeData, traversalData.octreeCoords, sampleDatas);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[0]);
|
||||
addSampleCoordinates(shapePosition, sampleDatas[1]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[0]);
|
||||
addSampleCoordinates(tileAndUv, sampleDatas[1]);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// See Intersection.glsl for the definition of intersectScene
|
||||
// See IntersectionUtils.glsl for the definition of nextIntersection
|
||||
// See convertUvToBox.glsl, convertUvToCylinder.glsl, or convertUvToEllipsoid.glsl
|
||||
// for the definition of convertUvToShapeUvSpace. The appropriate function is
|
||||
// selected based on the VoxelPrimitive shape type, and added to the shader in
|
||||
// Scene/VoxelRenderResources.js.
|
||||
// See convertLocalToBoxUv.glsl, convertLocalToCylinderUv.glsl, or convertLocalToEllipsoidUv.glsl
|
||||
// for the definitions of convertLocalToShapeSpaceDerivative and getTileAndUvCoordinate.
|
||||
// The appropriate functions are selected based on the VoxelPrimitive shape type,
|
||||
// and added to the shader in Scene/VoxelRenderResources.js.
|
||||
// See Octree.glsl for the definitions of TraversalData, SampleData,
|
||||
// traverseOctreeFromBeginning, and traverseOctreeFromExisting
|
||||
// See Megatexture.glsl for the definition of accumulatePropertiesFromMegatexture
|
||||
|
|
@ -15,10 +15,10 @@
|
|||
#define ALPHA_ACCUM_MAX 0.98 // Must be > 0.0 and <= 1.0
|
||||
#endif
|
||||
|
||||
uniform mat4 u_transformPositionUvToView;
|
||||
uniform mat4 u_transformPositionViewToLocal;
|
||||
uniform mat3 u_transformDirectionViewToLocal;
|
||||
uniform vec3 u_cameraPositionUv;
|
||||
uniform vec3 u_cameraDirectionUv;
|
||||
uniform vec3 u_cameraPositionLocal;
|
||||
uniform vec3 u_cameraDirectionLocal;
|
||||
uniform float u_stepSize;
|
||||
|
||||
#if defined(PICKING)
|
||||
|
|
@ -63,16 +63,15 @@ RayShapeIntersection getVoxelIntersection(in vec3 tileUv, in vec3 sampleSizeAlon
|
|||
}
|
||||
|
||||
vec4 getStepSize(in SampleData sampleData, in Ray viewRay, in RayShapeIntersection shapeIntersection, in mat3 jacobianT, in float currentT) {
|
||||
// The Jacobian is computed in a space where the shape spans [-1, 1].
|
||||
// But the ray is marched in a space where the shape fills [0, 1].
|
||||
// So we need to scale the Jacobian by 2.
|
||||
vec3 gradient = 2.0 * viewRay.rawDir * jacobianT;
|
||||
vec3 gradient = viewRay.dir * jacobianT;
|
||||
vec3 sampleSizeAlongRay = getSampleSize(sampleData.tileCoords.w) / gradient;
|
||||
|
||||
RayShapeIntersection voxelIntersection = getVoxelIntersection(sampleData.tileUv, sampleSizeAlongRay);
|
||||
|
||||
// Transform normal from shape space to Cartesian space
|
||||
vec3 voxelNormal = normalize(jacobianT * voxelIntersection.entry.xyz);
|
||||
// Transform normal from shape space to Cartesian space to eye space
|
||||
vec3 voxelNormal = jacobianT * voxelIntersection.entry.xyz;
|
||||
voxelNormal = normalize(czm_normal * voxelNormal);
|
||||
|
||||
// Compare with the shape intersection, to choose the appropriate normal
|
||||
vec4 voxelEntry = vec4(voxelNormal, currentT + voxelIntersection.entry.w);
|
||||
vec4 entry = intersectionMax(shapeIntersection.entry, voxelEntry);
|
||||
|
|
@ -114,37 +113,40 @@ int getSampleIndex(in SampleData sampleData) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Compute the view ray at the current fragment, in the local UV coordinates of the shape.
|
||||
* Compute the view ray at the current fragment, in the local coordinates of the shape.
|
||||
*/
|
||||
Ray getViewRayUv() {
|
||||
Ray getViewRayLocal() {
|
||||
vec4 eyeCoordinates = czm_windowToEyeCoordinates(gl_FragCoord);
|
||||
vec3 viewDirUv;
|
||||
vec3 viewPosUv;
|
||||
vec3 origin;
|
||||
vec3 direction;
|
||||
if (czm_orthographicIn3D == 1.0) {
|
||||
eyeCoordinates.z = 0.0;
|
||||
viewPosUv = (u_transformPositionViewToUv * eyeCoordinates).xyz;
|
||||
viewDirUv = normalize(u_cameraDirectionUv);
|
||||
origin = (u_transformPositionViewToLocal * eyeCoordinates).xyz;
|
||||
direction = u_cameraDirectionLocal;
|
||||
} else {
|
||||
viewPosUv = u_cameraPositionUv;
|
||||
viewDirUv = normalize(u_transformDirectionViewToLocal * eyeCoordinates.xyz);
|
||||
origin = u_cameraPositionLocal;
|
||||
direction = u_transformDirectionViewToLocal * normalize(eyeCoordinates.xyz);
|
||||
}
|
||||
#if defined(SHAPE_ELLIPSOID)
|
||||
// viewDirUv has been scaled to a space where the ellipsoid is a sphere.
|
||||
// Undo this scaling to get the raw direction.
|
||||
vec3 rawDir = viewDirUv * u_ellipsoidRadiiUv;
|
||||
return Ray(viewPosUv, viewDirUv, rawDir);
|
||||
#else
|
||||
return Ray(viewPosUv, viewDirUv, viewDirUv);
|
||||
#endif
|
||||
return Ray(origin, direction);
|
||||
}
|
||||
|
||||
Ray getViewRayEC() {
|
||||
vec4 eyeCoordinates = czm_windowToEyeCoordinates(gl_FragCoord);
|
||||
vec3 viewPosEC = (czm_orthographicIn3D == 1.0)
|
||||
? vec3(eyeCoordinates.xy, 0.0)
|
||||
: vec3(0.0);
|
||||
vec3 viewDirEC = normalize(eyeCoordinates.xyz);
|
||||
return Ray(viewPosEC, viewDirEC);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
Ray viewRayUv = getViewRayUv();
|
||||
Ray viewRayLocal = getViewRayLocal();
|
||||
Ray viewRayEC = getViewRayEC();
|
||||
|
||||
Intersections ix;
|
||||
vec2 screenCoord = (gl_FragCoord.xy - czm_viewport.xy) / czm_viewport.zw; // [0,1]
|
||||
RayShapeIntersection shapeIntersection = intersectScene(screenCoord, viewRayUv, ix);
|
||||
RayShapeIntersection shapeIntersection = intersectScene(screenCoord, viewRayLocal, viewRayEC, ix);
|
||||
// Exit early if the scene was completely missed.
|
||||
if (shapeIntersection.entry.w == NO_HIT) {
|
||||
discard;
|
||||
|
|
@ -152,20 +154,17 @@ void main()
|
|||
|
||||
float currentT = shapeIntersection.entry.w;
|
||||
float endT = shapeIntersection.exit.w;
|
||||
vec3 positionUv = viewRayUv.pos + currentT * viewRayUv.dir;
|
||||
PointJacobianT pointJacobian = convertUvToShapeUvSpaceDerivative(positionUv);
|
||||
|
||||
vec3 positionEC = viewRayEC.pos + currentT * viewRayEC.dir;
|
||||
TileAndUvCoordinate tileAndUv = getTileAndUvCoordinate(positionEC);
|
||||
vec3 positionLocal = viewRayLocal.pos + currentT * viewRayLocal.dir;
|
||||
mat3 jacobianT = convertLocalToShapeSpaceDerivative(positionLocal);
|
||||
|
||||
// Traverse the tree from the start position
|
||||
TraversalData traversalData;
|
||||
SampleData sampleDatas[SAMPLE_COUNT];
|
||||
traverseOctreeFromBeginning(pointJacobian.point, traversalData, sampleDatas);
|
||||
vec4 step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, pointJacobian.jacobianT, currentT);
|
||||
|
||||
#if defined(JITTER)
|
||||
float noise = hash(screenCoord); // [0,1]
|
||||
currentT += noise * step.w;
|
||||
positionUv += noise * step.w * viewRayUv.dir;
|
||||
#endif
|
||||
traverseOctreeFromBeginning(tileAndUv, traversalData, sampleDatas);
|
||||
vec4 step = getStepSize(sampleDatas[0], viewRayLocal, shapeIntersection, jacobianT, currentT);
|
||||
|
||||
FragmentInput fragmentInput;
|
||||
#if defined(STATISTICS)
|
||||
|
|
@ -182,10 +181,11 @@ void main()
|
|||
// Prepare the custom shader inputs
|
||||
copyPropertiesToMetadata(properties, fragmentInput.metadata);
|
||||
|
||||
fragmentInput.attributes.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
|
||||
fragmentInput.attributes.normalEC = normalize(czm_normal * step.xyz);
|
||||
fragmentInput.attributes.positionEC = positionEC;
|
||||
// Re-normalize normals: some shape intersections may have been scaled to encode positive/negative shapes
|
||||
fragmentInput.attributes.normalEC = normalize(step.xyz);
|
||||
|
||||
fragmentInput.voxel.viewDirUv = viewRayUv.dir;
|
||||
fragmentInput.voxel.viewDirUv = viewRayLocal.dir;
|
||||
|
||||
fragmentInput.voxel.travelDistance = step.w;
|
||||
fragmentInput.voxel.stepCount = stepCount;
|
||||
|
|
@ -233,13 +233,15 @@ void main()
|
|||
}
|
||||
#endif
|
||||
}
|
||||
positionUv = viewRayUv.pos + currentT * viewRayUv.dir;
|
||||
positionEC = viewRayEC.pos + currentT * viewRayEC.dir;
|
||||
tileAndUv = getTileAndUvCoordinate(positionEC);
|
||||
positionLocal = viewRayLocal.pos + currentT * viewRayLocal.dir;
|
||||
jacobianT = convertLocalToShapeSpaceDerivative(positionLocal);
|
||||
|
||||
// Traverse the tree from the current ray position.
|
||||
// This is similar to traverseOctreeFromBeginning but is faster when the ray is in the same tile as the previous step.
|
||||
pointJacobian = convertUvToShapeUvSpaceDerivative(positionUv);
|
||||
traverseOctreeFromExisting(pointJacobian.point, traversalData, sampleDatas);
|
||||
step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, pointJacobian.jacobianT, currentT);
|
||||
traverseOctreeFromExisting(tileAndUv, traversalData, sampleDatas);
|
||||
step = getStepSize(sampleDatas[0], viewRayLocal, shapeIntersection, jacobianT, currentT);
|
||||
}
|
||||
|
||||
// Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]
|
||||
|
|
|
|||
|
|
@ -1,22 +1,8 @@
|
|||
struct Ray {
|
||||
vec3 pos;
|
||||
vec3 dir;
|
||||
vec3 rawDir;
|
||||
};
|
||||
|
||||
#if defined(JITTER)
|
||||
/**
|
||||
* Generate a pseudo-random value for a given 2D screen coordinate.
|
||||
* Similar to https://www.shadertoy.com/view/4djSRW with a modified hashscale.
|
||||
*/
|
||||
float hash(vec2 p)
|
||||
{
|
||||
vec3 p3 = fract(vec3(p.xyx) * 50.0);
|
||||
p3 += dot(p3, p3.yzx + 19.19);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
#endif
|
||||
|
||||
float minComponent(in vec3 v) {
|
||||
return min(min(v.x, v.y), v.z);
|
||||
}
|
||||
|
|
@ -24,8 +10,3 @@ float minComponent(in vec3 v) {
|
|||
float maxComponent(in vec3 v) {
|
||||
return max(max(v.x, v.y), v.z);
|
||||
}
|
||||
|
||||
struct PointJacobianT {
|
||||
vec3 point;
|
||||
mat3 jacobianT;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
uniform vec3 u_boxLocalToShapeUvScale;
|
||||
uniform vec3 u_boxLocalToShapeUvTranslate;
|
||||
|
||||
uniform ivec4 u_cameraTileCoordinates;
|
||||
uniform vec3 u_cameraTileUv;
|
||||
uniform mat3 u_boxEcToXyz;
|
||||
|
||||
mat3 convertLocalToShapeSpaceDerivative(in vec3 positionLocal) {
|
||||
// For BOX, local space = shape space, so the Jacobian is the identity matrix.
|
||||
return mat3(1.0);
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
return shapeUv / u_boxLocalToShapeUvScale;
|
||||
}
|
||||
|
||||
vec3 convertEcToDeltaTile(in vec3 positionEC) {
|
||||
vec3 dPosition = u_boxEcToXyz * positionEC;
|
||||
return u_boxLocalToShapeUvScale * dPosition * float(1 << u_cameraTileCoordinates.w);
|
||||
}
|
||||
|
||||
TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
|
||||
vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
|
||||
vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
|
||||
ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
|
||||
tileCoordinate = min(max(ivec3(0), tileCoordinate), ivec3((1 << u_cameraTileCoordinates.w) - 1));
|
||||
ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
|
||||
vec3 tileUv = clamp(tileUvSum - vec3(tileCoordinateChange), 0.0, 1.0);
|
||||
return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
uniform vec2 u_cylinderLocalToShapeUvRadius; // x = scale, y = offset
|
||||
uniform vec2 u_cylinderLocalToShapeUvHeight; // x = scale, y = offset
|
||||
uniform vec2 u_cylinderLocalToShapeUvAngle; // x = scale, y = offset
|
||||
uniform float u_cylinderShapeUvAngleRangeOrigin;
|
||||
uniform mat3 u_cylinderEcToRadialTangentUp;
|
||||
uniform ivec4 u_cameraTileCoordinates;
|
||||
uniform vec3 u_cameraTileUv;
|
||||
uniform vec3 u_cameraShapePosition; // (radial distance, angle, height) of camera in shape space
|
||||
|
||||
mat3 convertLocalToShapeSpaceDerivative(in vec3 position) {
|
||||
vec3 radial = normalize(vec3(position.xy, 0.0));
|
||||
vec3 z = vec3(0.0, 0.0, 1.0);
|
||||
vec3 east = normalize(vec3(-position.y, position.x, 0.0));
|
||||
return mat3(radial, east / length(position.xy), z);
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
float radius = shapeUv.x / u_cylinderLocalToShapeUvRadius.x;
|
||||
float angle = shapeUv.y * czm_twoPi / u_cylinderLocalToShapeUvAngle.x;
|
||||
float height = shapeUv.z / u_cylinderLocalToShapeUvHeight.x;
|
||||
|
||||
return vec3(radius, angle, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the change in polar coordinates given a change in position.
|
||||
* @param {vec2} dPosition The change in position in Cartesian coordinates.
|
||||
* @param {float} cameraRadialDistance The radial distance of the camera from the origin.
|
||||
* @return {vec2} The change in polar coordinates (radial distance, angle).
|
||||
*/
|
||||
vec2 computePolarChange(in vec2 dPosition, in float cameraRadialDistance) {
|
||||
float dAngle = atan(dPosition.y, cameraRadialDistance + dPosition.x);
|
||||
// Find the direction of the radial axis at the output angle, in Cartesian coordinates
|
||||
vec2 outputRadialAxis = vec2(cos(dAngle), sin(dAngle));
|
||||
float sinHalfAngle = sin(dAngle / 2.0);
|
||||
float versine = 2.0 * sinHalfAngle * sinHalfAngle;
|
||||
float dRadial = dot(dPosition, outputRadialAxis) - cameraRadialDistance * versine;
|
||||
return vec2(dRadial, dAngle);
|
||||
}
|
||||
|
||||
vec3 convertEcToDeltaShape(in vec3 positionEC) {
|
||||
// 1. Rotate to radial, tangent, and up coordinates
|
||||
vec3 rtu = u_cylinderEcToRadialTangentUp * positionEC;
|
||||
// 2. Compute change in angular and radial coordinates.
|
||||
vec2 dPolar = computePolarChange(rtu.xy, u_cameraShapePosition.x);
|
||||
return vec3(dPolar.xy, rtu.z);
|
||||
}
|
||||
|
||||
vec3 convertEcToDeltaTile(in vec3 positionEC) {
|
||||
vec3 deltaShape = convertEcToDeltaShape(positionEC);
|
||||
// Convert to tileset coordinates in [0, 1]
|
||||
float dx = u_cylinderLocalToShapeUvRadius.x * deltaShape.x;
|
||||
float dy = deltaShape.y / czm_twoPi;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
|
||||
// Wrap to ensure dy is not crossing through the unoccupied angle range, where
|
||||
// angle to tile coordinate conversions would be more complicated
|
||||
float cameraUvAngle = (u_cameraShapePosition.y + czm_pi) / czm_twoPi;
|
||||
float cameraUvAngleShift = fract(cameraUvAngle - u_cylinderShapeUvAngleRangeOrigin);
|
||||
float rawOutputUvAngle = cameraUvAngleShift + dy;
|
||||
float rotation = floor(rawOutputUvAngle);
|
||||
dy -= rotation;
|
||||
#endif
|
||||
dy *= u_cylinderLocalToShapeUvAngle.x;
|
||||
float dz = u_cylinderLocalToShapeUvHeight.x * deltaShape.z;
|
||||
// Convert to tile coordinate changes
|
||||
return vec3(dx, dy, dz) * float(1 << u_cameraTileCoordinates.w);
|
||||
}
|
||||
|
||||
TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
|
||||
vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
|
||||
vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
|
||||
ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
|
||||
int maxTileCoordinate = (1 << u_cameraTileCoordinates.w) - 1;
|
||||
tileCoordinate.x = min(max(0, tileCoordinate.x), maxTileCoordinate);
|
||||
tileCoordinate.z = min(max(0, tileCoordinate.z), maxTileCoordinate);
|
||||
#if (!defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE))
|
||||
ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
|
||||
if (tileCoordinate.y < 0) {
|
||||
tileCoordinate.y += (maxTileCoordinate + 1);
|
||||
} else if (tileCoordinate.y > maxTileCoordinate) {
|
||||
tileCoordinate.y -= (maxTileCoordinate + 1);
|
||||
}
|
||||
#else
|
||||
tileCoordinate.y = min(max(0, tileCoordinate.y), maxTileCoordinate);
|
||||
ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
|
||||
#endif
|
||||
vec3 tileUv = tileUvSum - vec3(tileCoordinateChange);
|
||||
tileUv.x = clamp(tileUv.x, 0.0, 1.0);
|
||||
#if (!defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE))
|
||||
// If there is only one tile spanning 2*PI angle, the coordinate wraps around
|
||||
tileUv.y = (u_cameraTileCoordinates.w == 0) ? fract(tileUv.y) : clamp(tileUv.y, 0.0, 1.0);
|
||||
#else
|
||||
tileUv.y = clamp(tileUv.y, 0.0, 1.0);
|
||||
#endif
|
||||
tileUv.z = clamp(tileUv.z, 0.0, 1.0);
|
||||
return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/* Ellipsoid defines (set in Scene/VoxelEllipsoidShape.js)
|
||||
#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
|
||||
#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
|
||||
*/
|
||||
|
||||
uniform vec3 u_cameraPositionCartographic; // (longitude, latitude, height) in radians and meters
|
||||
uniform vec2 u_ellipsoidCurvatureAtLatitude;
|
||||
uniform mat3 u_ellipsoidEcToEastNorthUp;
|
||||
uniform vec3 u_ellipsoidRadii;
|
||||
uniform vec2 u_evoluteScale; // (radii.x ^ 2 - radii.z ^ 2) * vec2(1.0, -1.0) / radii;
|
||||
uniform vec3 u_ellipsoidInverseRadiiSquared;
|
||||
#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
|
||||
uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
|
||||
#endif
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
|
||||
uniform vec2 u_ellipsoidLocalToShapeUvLongitude; // x = scale, y = offset
|
||||
uniform float u_ellipsoidShapeUvLongitudeRangeOrigin;
|
||||
#endif
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
|
||||
uniform vec2 u_ellipsoidLocalToShapeUvLatitude; // x = scale, y = offset
|
||||
#endif
|
||||
uniform float u_ellipsoidInverseHeightDifference;
|
||||
|
||||
uniform ivec4 u_cameraTileCoordinates;
|
||||
uniform vec3 u_cameraTileUv;
|
||||
|
||||
// robust iterative solution without trig functions
|
||||
// https://github.com/0xfaded/ellipse_demo/issues/1
|
||||
// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
|
||||
// Extended to return radius of curvature along with the point
|
||||
vec3 nearestPointAndRadiusOnEllipse(vec2 pos, vec2 radii) {
|
||||
vec2 p = abs(pos);
|
||||
vec2 inverseRadii = 1.0 / radii;
|
||||
|
||||
// We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
|
||||
// but store the cos and sin of t in a vec2 for efficiency.
|
||||
// Initial guess: t = pi/4
|
||||
vec2 tTrigs = vec2(0.7071067811865476);
|
||||
// Initial guess of point on ellipsoid
|
||||
vec2 v = radii * tTrigs;
|
||||
// Center of curvature of the ellipse at v
|
||||
vec2 evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
|
||||
|
||||
const int iterations = 3;
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
// Find the (approximate) intersection of p - evolute with the ellipsoid.
|
||||
vec2 q = normalize(p - evolute) * length(v - evolute);
|
||||
// Update the estimate of t.
|
||||
tTrigs = (q + evolute) * inverseRadii;
|
||||
tTrigs = normalize(clamp(tTrigs, 0.0, 1.0));
|
||||
v = radii * tTrigs;
|
||||
evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
|
||||
}
|
||||
|
||||
return vec3(v * sign(pos), length(v - evolute));
|
||||
}
|
||||
|
||||
mat3 convertLocalToShapeSpaceDerivative(in vec3 position) {
|
||||
vec3 east = normalize(vec3(-position.y, position.x, 0.0));
|
||||
|
||||
// Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z)
|
||||
// (assume radii.y == radii.x) and find the nearest point on the ellipse and its normal
|
||||
float distanceFromZAxis = length(position.xy);
|
||||
vec2 posEllipse = vec2(distanceFromZAxis, position.z);
|
||||
vec3 surfacePointAndRadius = nearestPointAndRadiusOnEllipse(posEllipse, u_ellipsoidRadii.xz);
|
||||
vec2 surfacePoint = surfacePointAndRadius.xy;
|
||||
|
||||
vec2 normal2d = normalize(surfacePoint * u_ellipsoidInverseRadiiSquared.xz);
|
||||
vec3 north = vec3(-normal2d.y * normalize(position.xy), abs(normal2d.x));
|
||||
|
||||
float heightSign = length(posEllipse) < length(surfacePoint) ? -1.0 : 1.0;
|
||||
float height = heightSign * length(posEllipse - surfacePoint);
|
||||
vec3 up = normalize(cross(east, north));
|
||||
|
||||
return mat3(east / distanceFromZAxis, north / (surfacePointAndRadius.z + height), up);
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
// Convert from [0, 1] to radians [-pi, pi]
|
||||
float longitude = shapeUv.x * czm_twoPi;
|
||||
#if defined (ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
|
||||
longitude /= u_ellipsoidLocalToShapeUvLongitude.x;
|
||||
#endif
|
||||
|
||||
// Convert from [0, 1] to radians [-pi/2, pi/2]
|
||||
float latitude = shapeUv.y * czm_pi;
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
|
||||
latitude /= u_ellipsoidLocalToShapeUvLatitude.x;
|
||||
#endif
|
||||
|
||||
float height = shapeUv.z / u_ellipsoidInverseHeightDifference;
|
||||
|
||||
return vec3(longitude, latitude, height);
|
||||
}
|
||||
|
||||
vec3 convertEcToDeltaShape(in vec3 positionEC) {
|
||||
vec3 enu = u_ellipsoidEcToEastNorthUp * positionEC;
|
||||
|
||||
// 1. Compute the change in longitude from the camera to the ENU point
|
||||
// First project the camera and ENU positions to the equatorial XY plane,
|
||||
// positioning the camera on the +x axis, so that enu.x projects along the +y axis
|
||||
float cosLatitude = cos(u_cameraPositionCartographic.y);
|
||||
float sinLatitude = sin(u_cameraPositionCartographic.y);
|
||||
float primeVerticalRadius = 1.0 / u_ellipsoidCurvatureAtLatitude.x;
|
||||
vec2 cameraXY = vec2((primeVerticalRadius + u_cameraPositionCartographic.z) * cosLatitude, 0.0);
|
||||
// Note precision loss in positionXY.x if length(enu) << length(cameraXY)
|
||||
vec2 positionXY = cameraXY + vec2(-enu.y * sinLatitude + enu.z * cosLatitude, enu.x);
|
||||
float dLongitude = atan(positionXY.y, positionXY.x);
|
||||
|
||||
// 2. Find the longitude component of positionXY, by rotating about Z until the y component is zero.
|
||||
// Use the versine to compute the change in x directly from the change in angle:
|
||||
// versine(angle) = 2 * sin^2(angle/2)
|
||||
float sinHalfLongitude = sin(dLongitude / 2.0);
|
||||
float dx = length(positionXY) * 2.0 * sinHalfLongitude * sinHalfLongitude;
|
||||
// Rotate longitude component back to ENU North and Up, and remove from enu
|
||||
enu += vec3(-enu.x, -dx * sinLatitude, dx * cosLatitude);
|
||||
|
||||
// 3. Compute the change in latitude from the camera to the ENU point.
|
||||
// First project the camera and ENU positions to the meridional ZX plane,
|
||||
// positioning the camera on the +Z axis, so that enu.y maps to the +X axis.
|
||||
float meridionalRadius = 1.0 / u_ellipsoidCurvatureAtLatitude.y;
|
||||
vec2 cameraZX = vec2(meridionalRadius + u_cameraPositionCartographic.z, 0.0);
|
||||
vec2 positionZX = cameraZX + vec2(enu.z, enu.y);
|
||||
float dLatitude = atan(positionZX.y, positionZX.x);
|
||||
|
||||
// 4. Compute the change in height above the ellipsoid
|
||||
// Find the change in enu.z associated with rotating the point to the latitude of the camera
|
||||
float sinHalfLatitude = sin(dLatitude / 2.0);
|
||||
float dz = length(positionZX) * 2.0 * sinHalfLatitude * sinHalfLatitude;
|
||||
// The remaining change in enu.z is the change in height above the ellipsoid
|
||||
float dHeight = enu.z + dz;
|
||||
|
||||
return vec3(dLongitude, dLatitude, dHeight);
|
||||
}
|
||||
|
||||
vec3 convertEcToDeltaTile(in vec3 positionEC) {
|
||||
vec3 deltaShape = convertEcToDeltaShape(positionEC);
|
||||
// Convert to tileset coordinates in [0, 1]
|
||||
float dx = deltaShape.x / czm_twoPi;
|
||||
|
||||
#if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
|
||||
// Wrap to ensure dx is not crossing through the unoccupied angle range, where
|
||||
// angle to tile coordinate conversions would be more complicated
|
||||
float cameraUvLongitude = (u_cameraPositionCartographic.x + czm_pi) / czm_twoPi;
|
||||
float cameraUvLongitudeShift = fract(cameraUvLongitude - u_ellipsoidShapeUvLongitudeRangeOrigin);
|
||||
float rawOutputUvLongitude = cameraUvLongitudeShift + dx;
|
||||
float rotation = floor(rawOutputUvLongitude);
|
||||
dx -= rotation;
|
||||
dx *= u_ellipsoidLocalToShapeUvLongitude.x;
|
||||
#endif
|
||||
|
||||
float dy = deltaShape.y / czm_pi;
|
||||
#if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE))
|
||||
dy *= u_ellipsoidLocalToShapeUvLatitude.x;
|
||||
#endif
|
||||
|
||||
float dz = u_ellipsoidInverseHeightDifference * deltaShape.z;
|
||||
// Convert to tile coordinate changes
|
||||
return vec3(dx, dy, dz) * float(1 << u_cameraTileCoordinates.w);
|
||||
}
|
||||
|
||||
TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
|
||||
vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
|
||||
vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
|
||||
ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
|
||||
int maxTileCoordinate = (1 << u_cameraTileCoordinates.w) - 1;
|
||||
tileCoordinate.y = min(max(0, tileCoordinate.y), maxTileCoordinate);
|
||||
tileCoordinate.z = min(max(0, tileCoordinate.z), maxTileCoordinate);
|
||||
#if (!defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
|
||||
ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
|
||||
if (tileCoordinate.x < 0) {
|
||||
tileCoordinate.x += (maxTileCoordinate + 1);
|
||||
} else if (tileCoordinate.x > maxTileCoordinate) {
|
||||
tileCoordinate.x -= (maxTileCoordinate + 1);
|
||||
}
|
||||
#else
|
||||
tileCoordinate.x = min(max(0, tileCoordinate.x), maxTileCoordinate);
|
||||
ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
|
||||
#endif
|
||||
vec3 tileUv = tileUvSum - vec3(tileCoordinateChange);
|
||||
#if (!defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
|
||||
// If there is only one tile spanning 2*PI angle, the coordinate wraps around
|
||||
tileUv.x = (u_cameraTileCoordinates.w == 0) ? fract(tileUv.x) : clamp(tileUv.x, 0.0, 1.0);
|
||||
#else
|
||||
tileUv.x = clamp(tileUv.x, 0.0, 1.0);
|
||||
#endif
|
||||
tileUv.y = clamp(tileUv.y, 0.0, 1.0);
|
||||
tileUv.z = clamp(tileUv.z, 0.0, 1.0);
|
||||
return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/* Box defines (set in Scene/VoxelBoxShape.js)
|
||||
#define BOX_HAS_SHAPE_BOUNDS
|
||||
*/
|
||||
|
||||
#if defined(BOX_HAS_SHAPE_BOUNDS)
|
||||
uniform vec3 u_boxUvToShapeUvScale;
|
||||
uniform vec3 u_boxUvToShapeUvTranslate;
|
||||
#endif
|
||||
|
||||
PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
|
||||
// For BOX, UV space = shape space, so we can use positionUv as-is,
|
||||
// and the Jacobian is the identity matrix, except that a step of 1
|
||||
// only spans half the shape space [-1, 1], so the identity is scaled.
|
||||
return PointJacobianT(positionUv, mat3(0.5));
|
||||
}
|
||||
|
||||
vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
|
||||
#if defined(BOX_HAS_SHAPE_BOUNDS)
|
||||
return positionShape * u_boxUvToShapeUvScale + u_boxUvToShapeUvTranslate;
|
||||
#else
|
||||
return positionShape;
|
||||
#endif
|
||||
}
|
||||
|
||||
PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
|
||||
PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
|
||||
pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
|
||||
return pointJacobian;
|
||||
}
|
||||
|
||||
vec3 convertShapeUvToUvSpace(in vec3 shapeUv) {
|
||||
#if defined(BOX_HAS_SHAPE_BOUNDS)
|
||||
return (shapeUv - u_boxUvToShapeUvTranslate) / u_boxUvToShapeUvScale;
|
||||
#else
|
||||
return shapeUv;
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
#if defined(BOX_HAS_SHAPE_BOUNDS)
|
||||
return shapeUv / u_boxUvToShapeUvScale;
|
||||
#else
|
||||
return shapeUv;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/* Cylinder defines (set in Scene/VoxelCylinderShape.js)
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY
|
||||
#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED
|
||||
*/
|
||||
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
|
||||
uniform vec2 u_cylinderUvToShapeUvRadius; // x = scale, y = offset
|
||||
#endif
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
|
||||
uniform vec2 u_cylinderUvToShapeUvHeight; // x = scale, y = offset
|
||||
#endif
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
|
||||
uniform vec2 u_cylinderUvToShapeUvAngle; // x = scale, y = offset
|
||||
#endif
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
|
||||
uniform vec2 u_cylinderShapeUvAngleMinMax;
|
||||
#endif
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
|
||||
uniform float u_cylinderShapeUvAngleRangeZeroMid;
|
||||
#endif
|
||||
|
||||
PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
|
||||
// Convert from Cartesian UV space [0, 1] to Cartesian local space [-1, 1]
|
||||
vec3 position = positionUv * 2.0 - 1.0;
|
||||
|
||||
float radius = length(position.xy); // [0, 1]
|
||||
vec3 radial = normalize(vec3(position.xy, 0.0));
|
||||
|
||||
// Shape space height is defined within [0, 1]
|
||||
float height = positionUv.z; // [0, 1]
|
||||
vec3 z = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
float angle = atan(position.y, position.x);
|
||||
vec3 east = normalize(vec3(-position.y, position.x, 0.0));
|
||||
|
||||
vec3 point = vec3(radius, angle, height);
|
||||
mat3 jacobianT = mat3(radial, east / length(position.xy), z);
|
||||
return PointJacobianT(point, jacobianT);
|
||||
}
|
||||
|
||||
vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
|
||||
float radius = positionShape.x;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
|
||||
radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y;
|
||||
#endif
|
||||
|
||||
float angle = (positionShape.y + czm_pi) / czm_twoPi;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
|
||||
// Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleRangeZeroMid is more conservative.
|
||||
angle += float(angle < u_cylinderShapeUvAngleRangeZeroMid);
|
||||
#endif
|
||||
|
||||
// Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY)
|
||||
angle = angle > u_cylinderShapeUvAngleRangeZeroMid ? u_cylinderShapeUvAngleMinMax.x : angle;
|
||||
#elif defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
|
||||
angle = angle < u_cylinderShapeUvAngleRangeZeroMid ? u_cylinderShapeUvAngleMinMax.y : angle;
|
||||
#endif
|
||||
|
||||
angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y;
|
||||
#endif
|
||||
|
||||
float height = positionShape.z;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
|
||||
height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y;
|
||||
#endif
|
||||
|
||||
return vec3(radius, angle, height);
|
||||
}
|
||||
|
||||
PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
|
||||
PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
|
||||
pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
|
||||
return pointJacobian;
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
float radius = shapeUv.x;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
|
||||
radius /= u_cylinderUvToShapeUvRadius.x;
|
||||
#endif
|
||||
|
||||
float angle = shapeUv.y * czm_twoPi;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
|
||||
angle /= u_cylinderUvToShapeUvAngle.x;
|
||||
#endif
|
||||
|
||||
float height = shapeUv.z;
|
||||
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
|
||||
height /= u_cylinderUvToShapeUvHeight.x;
|
||||
#endif
|
||||
|
||||
return vec3(radius, angle, height);
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
/* Ellipsoid defines (set in Scene/VoxelEllipsoidShape.js)
|
||||
#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
|
||||
#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
|
||||
#define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
|
||||
*/
|
||||
|
||||
uniform vec3 u_ellipsoidRadiiUv; // [0,1]
|
||||
uniform vec2 u_evoluteScale; // (radiiUv.x ^ 2 - radiiUv.z ^ 2) * vec2(1.0, -1.0) / radiiUv;
|
||||
uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
|
||||
#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
|
||||
uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
|
||||
#endif
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
|
||||
uniform vec2 u_ellipsoidUvToShapeUvLongitude; // x = scale, y = offset
|
||||
#endif
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
|
||||
uniform vec2 u_ellipsoidUvToShapeUvLatitude; // x = scale, y = offset
|
||||
#endif
|
||||
uniform float u_ellipsoidInverseHeightDifferenceUv;
|
||||
|
||||
// robust iterative solution without trig functions
|
||||
// https://github.com/0xfaded/ellipse_demo/issues/1
|
||||
// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
|
||||
// Extended to return radius of curvature along with the point
|
||||
vec3 nearestPointAndRadiusOnEllipse(vec2 pos, vec2 radii) {
|
||||
vec2 p = abs(pos);
|
||||
vec2 inverseRadii = 1.0 / radii;
|
||||
|
||||
// We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
|
||||
// but store the cos and sin of t in a vec2 for efficiency.
|
||||
// Initial guess: t = pi/4
|
||||
vec2 tTrigs = vec2(0.7071067811865476);
|
||||
// Initial guess of point on ellipsoid
|
||||
vec2 v = radii * tTrigs;
|
||||
// Center of curvature of the ellipse at v
|
||||
vec2 evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
|
||||
|
||||
const int iterations = 3;
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
// Find the (approximate) intersection of p - evolute with the ellipsoid.
|
||||
vec2 q = normalize(p - evolute) * length(v - evolute);
|
||||
// Update the estimate of t.
|
||||
tTrigs = (q + evolute) * inverseRadii;
|
||||
tTrigs = normalize(clamp(tTrigs, 0.0, 1.0));
|
||||
v = radii * tTrigs;
|
||||
evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
|
||||
}
|
||||
|
||||
return vec3(v * sign(pos), length(v - evolute));
|
||||
}
|
||||
|
||||
PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
|
||||
// Convert from UV space [0, 1] to local space [-1, 1]
|
||||
vec3 position = positionUv * 2.0 - 1.0;
|
||||
// Undo the scaling from ellipsoid to sphere
|
||||
position = position * u_ellipsoidRadiiUv;
|
||||
|
||||
float longitude = atan(position.y, position.x);
|
||||
vec3 east = normalize(vec3(-position.y, position.x, 0.0));
|
||||
|
||||
// Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z)
|
||||
// (assume radii.y == radii.x) and find the nearest point on the ellipse and its normal
|
||||
float distanceFromZAxis = length(position.xy);
|
||||
vec2 posEllipse = vec2(distanceFromZAxis, position.z);
|
||||
vec3 surfacePointAndRadius = nearestPointAndRadiusOnEllipse(posEllipse, u_ellipsoidRadiiUv.xz);
|
||||
vec2 surfacePoint = surfacePointAndRadius.xy;
|
||||
|
||||
vec2 normal2d = normalize(surfacePoint * u_ellipsoidInverseRadiiSquaredUv.xz);
|
||||
float latitude = atan(normal2d.y, normal2d.x);
|
||||
vec3 north = vec3(-normal2d.y * normalize(position.xy), abs(normal2d.x));
|
||||
|
||||
float heightSign = length(posEllipse) < length(surfacePoint) ? -1.0 : 1.0;
|
||||
float height = heightSign * length(posEllipse - surfacePoint);
|
||||
vec3 up = normalize(cross(east, north));
|
||||
|
||||
vec3 point = vec3(longitude, latitude, height);
|
||||
mat3 jacobianT = mat3(east / distanceFromZAxis, north / (surfacePointAndRadius.z + height), up);
|
||||
return PointJacobianT(point, jacobianT);
|
||||
}
|
||||
|
||||
vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
|
||||
// Longitude: shift & scale to [0, 1]
|
||||
float longitude = (positionShape.x + czm_pi) / czm_twoPi;
|
||||
|
||||
// Correct the angle when max < min
|
||||
// Technically this should compare against min longitude - but it has precision problems so compare against the middle of empty space.
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
|
||||
longitude += float(longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z);
|
||||
#endif
|
||||
|
||||
// Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
|
||||
#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY)
|
||||
longitude = longitude > u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.x : longitude;
|
||||
#endif
|
||||
#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY)
|
||||
longitude = longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.y : longitude;
|
||||
#endif
|
||||
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
|
||||
longitude = longitude * u_ellipsoidUvToShapeUvLongitude.x + u_ellipsoidUvToShapeUvLongitude.y;
|
||||
#endif
|
||||
|
||||
// Latitude: shift and scale to [0, 1]
|
||||
float latitude = (positionShape.y + czm_piOverTwo) / czm_pi;
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
|
||||
latitude = latitude * u_ellipsoidUvToShapeUvLatitude.x + u_ellipsoidUvToShapeUvLatitude.y;
|
||||
#endif
|
||||
|
||||
// Height: scale to the range [0, 1]
|
||||
float height = 1.0 + positionShape.z * u_ellipsoidInverseHeightDifferenceUv;
|
||||
|
||||
return vec3(longitude, latitude, height);
|
||||
}
|
||||
|
||||
PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
|
||||
PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
|
||||
pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
|
||||
return pointJacobian;
|
||||
}
|
||||
|
||||
vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
|
||||
// Convert from [0, 1] to radians [-pi, pi]
|
||||
float longitude = shapeUv.x * czm_twoPi;
|
||||
#if defined (ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
|
||||
longitude /= u_ellipsoidUvToShapeUvLongitude.x;
|
||||
#endif
|
||||
|
||||
// Convert from [0, 1] to radians [-pi/2, pi/2]
|
||||
float latitude = shapeUv.y * czm_pi;
|
||||
#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
|
||||
latitude /= u_ellipsoidUvToShapeUvLatitude.x;
|
||||
#endif
|
||||
|
||||
float height = shapeUv.z / u_ellipsoidInverseHeightDifferenceUv;
|
||||
|
||||
return vec3(longitude, latitude, height);
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { defaultValue } from "../../index.js";
|
||||
|
||||
describe("Core/defaultValue", function () {
|
||||
it("Works with first parameter undefined", function () {
|
||||
expect(defaultValue(undefined, 5)).toEqual(5);
|
||||
});
|
||||
|
||||
it("Works with first parameter null", function () {
|
||||
expect(defaultValue(null, 5)).toEqual(5);
|
||||
});
|
||||
|
||||
it("Works with first parameter not undefined and not null", function () {
|
||||
expect(defaultValue(1, 5)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
import {
|
||||
Math as CesiumMath,
|
||||
Rectangle,
|
||||
Request,
|
||||
RequestScheduler,
|
||||
Resource,
|
||||
WebMercatorTilingScheme,
|
||||
Imagery,
|
||||
ImageryLayer,
|
||||
ImageryProvider,
|
||||
ImageryState,
|
||||
Azure2DImageryProvider,
|
||||
} from "../../index.js";
|
||||
|
||||
import pollToPromise from "../../../../Specs/pollToPromise.js";
|
||||
|
||||
describe("Scene/Azure2DImageryProvider", function () {
|
||||
afterEach(function () {
|
||||
Resource._Implementations.createImage =
|
||||
Resource._DefaultImplementations.createImage;
|
||||
});
|
||||
|
||||
it("conforms to ImageryProvider interface", function () {
|
||||
expect(Azure2DImageryProvider).toConformToInterface(ImageryProvider);
|
||||
});
|
||||
|
||||
it("requires the subscription key to be specified", function () {
|
||||
expect(function () {
|
||||
return new Azure2DImageryProvider({
|
||||
tilesetId: "a-tileset-id",
|
||||
});
|
||||
}).toThrowDeveloperError(
|
||||
"options.subscriptionKey is required, actual value was undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("requires tilesetId to be specified", function () {
|
||||
expect(function () {
|
||||
return new Azure2DImageryProvider({
|
||||
subscriptionKey: "a-subscription-key",
|
||||
});
|
||||
}).toThrowDeveloperError(
|
||||
"options.tilesetId is required, actual value was undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("requestImage returns a promise for an image and loads it for cross-origin use", function () {
|
||||
const provider = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
});
|
||||
|
||||
expect(provider.url).toEqual(
|
||||
"https://atlas.microsoft.com/map/tile?api-version=2024-04-01&tilesetId=a-tileset-id&zoom={z}&x={x}&y={y}&subscription-key=test-subscriptionKey",
|
||||
);
|
||||
expect(provider.tileWidth).toEqual(256);
|
||||
expect(provider.tileHeight).toEqual(256);
|
||||
expect(provider.maximumLevel).toBe(22);
|
||||
expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
|
||||
expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle);
|
||||
|
||||
spyOn(Resource._Implementations, "createImage").and.callFake(
|
||||
function (request, crossOrigin, deferred) {
|
||||
// Just return any old image.
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return provider.requestImage(0, 0, 0).then(function (image) {
|
||||
expect(Resource._Implementations.createImage).toHaveBeenCalled();
|
||||
expect(image).toBeImageOrImageBitmap();
|
||||
});
|
||||
});
|
||||
|
||||
it("rectangle passed to constructor does not affect tile numbering", function () {
|
||||
const rectangle = new Rectangle(0.1, 0.2, 0.3, 0.4);
|
||||
const provider = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
rectangle: rectangle,
|
||||
});
|
||||
|
||||
expect(provider.tileWidth).toEqual(256);
|
||||
expect(provider.tileHeight).toEqual(256);
|
||||
expect(provider.maximumLevel).toBe(22);
|
||||
expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
|
||||
expect(provider.rectangle).toEqualEpsilon(rectangle, CesiumMath.EPSILON14);
|
||||
expect(provider.tileDiscardPolicy).toBeUndefined();
|
||||
|
||||
spyOn(Resource._Implementations, "createImage").and.callFake(
|
||||
function (request, crossOrigin, deferred) {
|
||||
expect(request.url).toContain("zoom=0&x=0&y=0");
|
||||
|
||||
// Just return any old image.
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return provider.requestImage(0, 0, 0).then(function (image) {
|
||||
expect(Resource._Implementations.createImage).toHaveBeenCalled();
|
||||
expect(image).toBeImageOrImageBitmap();
|
||||
});
|
||||
});
|
||||
|
||||
it("uses maximumLevel passed to constructor", function () {
|
||||
const provider = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
maximumLevel: 5,
|
||||
});
|
||||
expect(provider.maximumLevel).toEqual(5);
|
||||
});
|
||||
|
||||
it("uses minimumLevel passed to constructor", function () {
|
||||
const provider = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
minimumLevel: 1,
|
||||
});
|
||||
expect(provider.minimumLevel).toEqual(1);
|
||||
});
|
||||
|
||||
it("turns the supplied credit into a logo", function () {
|
||||
const creditText = "Thanks to our awesome made up source of this imagery!";
|
||||
const providerWithCredit = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
credit: creditText,
|
||||
});
|
||||
expect(providerWithCredit.credit.html).toEqual(creditText);
|
||||
});
|
||||
|
||||
it("raises error event when image cannot be loaded", function () {
|
||||
const provider = new Azure2DImageryProvider({
|
||||
subscriptionKey: "test-subscriptionKey",
|
||||
tilesetId: "a-tileset-id",
|
||||
});
|
||||
|
||||
const layer = new ImageryLayer(provider);
|
||||
|
||||
let tries = 0;
|
||||
provider.errorEvent.addEventListener(function (error) {
|
||||
expect(error.timesRetried).toEqual(tries);
|
||||
++tries;
|
||||
if (tries < 3) {
|
||||
error.retry = true;
|
||||
}
|
||||
setTimeout(function () {
|
||||
RequestScheduler.update();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
Resource._Implementations.createImage = function (
|
||||
request,
|
||||
crossOrigin,
|
||||
deferred,
|
||||
) {
|
||||
if (tries === 2) {
|
||||
// Succeed after 2 tries
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
} else {
|
||||
// fail
|
||||
setTimeout(function () {
|
||||
deferred.reject();
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const imagery = new Imagery(layer, 0, 0, 0);
|
||||
imagery.addReference();
|
||||
layer._requestImagery(imagery);
|
||||
RequestScheduler.update();
|
||||
|
||||
return pollToPromise(function () {
|
||||
return imagery.state === ImageryState.RECEIVED;
|
||||
}).then(function () {
|
||||
expect(imagery.image).toBeImageOrImageBitmap();
|
||||
expect(tries).toEqual(2);
|
||||
imagery.releaseReference();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
import {
|
||||
Math as CesiumMath,
|
||||
Rectangle,
|
||||
Request,
|
||||
RequestScheduler,
|
||||
Resource,
|
||||
WebMercatorTilingScheme,
|
||||
Imagery,
|
||||
ImageryLayer,
|
||||
ImageryProvider,
|
||||
ImageryState,
|
||||
Google2DImageryProvider,
|
||||
} from "../../index.js";
|
||||
|
||||
import pollToPromise from "../../../../Specs/pollToPromise.js";
|
||||
|
||||
describe("Scene/Google2DImageryProvider", function () {
|
||||
beforeEach(function () {
|
||||
RequestScheduler.clearForSpecs();
|
||||
spyOn(
|
||||
Google2DImageryProvider.prototype,
|
||||
"getViewportCredits",
|
||||
).and.returnValue(Promise.resolve(""));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Resource._Implementations.createImage =
|
||||
Resource._DefaultImplementations.createImage;
|
||||
});
|
||||
|
||||
it("conforms to ImageryProvider interface", function () {
|
||||
expect(Google2DImageryProvider).toConformToInterface(ImageryProvider);
|
||||
});
|
||||
|
||||
it("requires the session token to be specified", function () {
|
||||
expect(function () {
|
||||
return new Google2DImageryProvider({});
|
||||
}).toThrowDeveloperError();
|
||||
});
|
||||
|
||||
it("requires the tileWidth to be specified", function () {
|
||||
expect(function () {
|
||||
return new Google2DImageryProvider({
|
||||
session: "a-session-token",
|
||||
});
|
||||
}).toThrowDeveloperError();
|
||||
});
|
||||
|
||||
it("requires the key to be specified", function () {
|
||||
expect(function () {
|
||||
return new Google2DImageryProvider({
|
||||
session: "a-session-token",
|
||||
tileHeight: 256,
|
||||
tileWidth: 256,
|
||||
});
|
||||
}).toThrowDeveloperError();
|
||||
});
|
||||
|
||||
it("fromIonAssetId throws if assetId is not provided", async function () {
|
||||
await expectAsync(
|
||||
Google2DImageryProvider.fromIonAssetId(),
|
||||
).toBeRejectedWithDeveloperError(
|
||||
"options.assetId is required, actual value was undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it("requestImage returns a promise for an image and loads it for cross-origin use", function () {
|
||||
const provider = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
});
|
||||
|
||||
expect(provider.url).toEqual(
|
||||
"https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?session=test-session-token&key=test-key",
|
||||
);
|
||||
expect(provider.tileWidth).toEqual(256);
|
||||
expect(provider.tileHeight).toEqual(256);
|
||||
expect(provider.maximumLevel).toBe(22);
|
||||
expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
|
||||
expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle);
|
||||
|
||||
spyOn(Resource._Implementations, "createImage").and.callFake(
|
||||
function (request, crossOrigin, deferred) {
|
||||
// Just return any old image.
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return provider.requestImage(0, 0, 0).then(function (image) {
|
||||
expect(Resource._Implementations.createImage).toHaveBeenCalled();
|
||||
expect(image).toBeImageOrImageBitmap();
|
||||
});
|
||||
});
|
||||
|
||||
it("rectangle passed to constructor does not affect tile numbering", function () {
|
||||
const rectangle = new Rectangle(0.1, 0.2, 0.3, 0.4);
|
||||
const provider = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
rectangle: rectangle,
|
||||
});
|
||||
|
||||
expect(provider.tileWidth).toEqual(256);
|
||||
expect(provider.tileHeight).toEqual(256);
|
||||
expect(provider.maximumLevel).toBe(22);
|
||||
expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
|
||||
expect(provider.rectangle).toEqualEpsilon(rectangle, CesiumMath.EPSILON14);
|
||||
expect(provider.tileDiscardPolicy).toBeUndefined();
|
||||
|
||||
spyOn(Resource._Implementations, "createImage").and.callFake(
|
||||
function (request, crossOrigin, deferred) {
|
||||
expect(request.url).toContain("/0/0/0");
|
||||
|
||||
// Just return any old image.
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return provider.requestImage(0, 0, 0).then(function (image) {
|
||||
expect(Resource._Implementations.createImage).toHaveBeenCalled();
|
||||
expect(image).toBeImageOrImageBitmap();
|
||||
});
|
||||
});
|
||||
|
||||
it("uses maximumLevel passed to constructor", function () {
|
||||
const provider = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
maximumLevel: 5,
|
||||
});
|
||||
expect(provider.maximumLevel).toEqual(5);
|
||||
});
|
||||
|
||||
it("uses minimumLevel passed to constructor", function () {
|
||||
const provider = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
minimumLevel: 1,
|
||||
});
|
||||
expect(provider.minimumLevel).toEqual(1);
|
||||
});
|
||||
|
||||
it("turns the supplied credit into a logo", function () {
|
||||
const creditText = "Thanks to our awesome made up source of this imagery!";
|
||||
const providerWithCredit = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
credit: creditText,
|
||||
});
|
||||
expect(providerWithCredit.credit.html).toEqual(creditText);
|
||||
});
|
||||
|
||||
it("raises error event when image cannot be loaded", function () {
|
||||
const provider = new Google2DImageryProvider({
|
||||
session: "test-session-token",
|
||||
key: "test-key",
|
||||
tileWidth: 256,
|
||||
tileHeight: 256,
|
||||
});
|
||||
|
||||
const layer = new ImageryLayer(provider);
|
||||
|
||||
let tries = 0;
|
||||
provider.errorEvent.addEventListener(function (error) {
|
||||
expect(error.timesRetried).toEqual(tries);
|
||||
++tries;
|
||||
if (tries < 3) {
|
||||
error.retry = true;
|
||||
}
|
||||
setTimeout(function () {
|
||||
RequestScheduler.update();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
Resource._Implementations.createImage = function (
|
||||
request,
|
||||
crossOrigin,
|
||||
deferred,
|
||||
) {
|
||||
if (tries === 2) {
|
||||
// Succeed after 2 tries
|
||||
Resource._DefaultImplementations.createImage(
|
||||
new Request({ url: "Data/Images/Red16x16.png" }),
|
||||
crossOrigin,
|
||||
deferred,
|
||||
);
|
||||
} else {
|
||||
// fail
|
||||
setTimeout(function () {
|
||||
deferred.reject();
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const imagery = new Imagery(layer, 0, 0, 0);
|
||||
imagery.addReference();
|
||||
layer._requestImagery(imagery);
|
||||
RequestScheduler.update();
|
||||
|
||||
return pollToPromise(function () {
|
||||
return imagery.state === ImageryState.RECEIVED;
|
||||
}).then(function () {
|
||||
expect(imagery.image).toBeImageOrImageBitmap();
|
||||
expect(tries).toEqual(2);
|
||||
imagery.releaseReference();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -2446,7 +2446,6 @@ describe("Scene/LabelCollection", function () {
|
|||
});
|
||||
|
||||
expect(l._clampedPosition).toBeDefined();
|
||||
expect(l._glyphs[0].billboard._clampedPosition).toBeDefined();
|
||||
|
||||
l.heightReference = HeightReference.NONE;
|
||||
expect(l._clampedPosition).toBeUndefined();
|
||||
|
|
@ -2458,6 +2457,7 @@ describe("Scene/LabelCollection", function () {
|
|||
heightReference: HeightReference.CLAMP_TO_GROUND,
|
||||
text: "t",
|
||||
position: Cartesian3.fromDegrees(-72.0, 40.0),
|
||||
showBackground: true,
|
||||
});
|
||||
|
||||
await pollToPromise(() => {
|
||||
|
|
@ -2465,7 +2465,7 @@ describe("Scene/LabelCollection", function () {
|
|||
return labelsWithHeight.ready;
|
||||
});
|
||||
|
||||
const billboard = l._glyphs[0].billboard;
|
||||
const billboard = l._backgroundBillboard;
|
||||
expect(billboard._removeCallbackFunc).toBeDefined();
|
||||
const spy = spyOn(billboard, "_removeCallbackFunc");
|
||||
labelsWithHeight.remove(l);
|
||||
|
|
|
|||
|
|
@ -90,17 +90,17 @@ describe("Scene/VoxelEllipsoidShape", function () {
|
|||
).toEqualEpsilon(expectedOrientedBoundingBox.center, CesiumMath.EPSILON12);
|
||||
|
||||
const expectedShapeTransform = Matrix4.fromRowMajorArray([
|
||||
(scale.x + maxHeight) * Math.cos(angle),
|
||||
-(scale.x + maxHeight) * Math.sin(angle),
|
||||
Math.cos(angle),
|
||||
-Math.sin(angle),
|
||||
0.0,
|
||||
expectedOrientedBoundingBox.center.x,
|
||||
(scale.y + maxHeight) * Math.sin(angle),
|
||||
(scale.y + maxHeight) * Math.cos(angle),
|
||||
Math.sin(angle),
|
||||
Math.cos(angle),
|
||||
0.0,
|
||||
expectedOrientedBoundingBox.center.y,
|
||||
0.0,
|
||||
0.0,
|
||||
scale.z + maxHeight,
|
||||
1.0,
|
||||
expectedOrientedBoundingBox.center.z,
|
||||
0.0,
|
||||
0.0,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ describe("Scene/buildVoxelDrawCommands", function () {
|
|||
const { shaderProgram } = primitive._drawCommand;
|
||||
const fragmentShaderText = shaderProgram._fragmentShaderText;
|
||||
const clippingFunctionSignature =
|
||||
"vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)";
|
||||
"vec4 getClippingPlane(highp sampler2D packedPlanes, int planeNumber)";
|
||||
|
||||
expect(fragmentShaderText.includes(clippingFunctionSignature)).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@cesium/engine",
|
||||
"version": "20.0.1",
|
||||
"version": "21.0.0",
|
||||
"description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.",
|
||||
"keywords": [
|
||||
"3D",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import * as Cesium from "cesium";
|
||||
|
||||
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
|
||||
Cesium.Ion.defaultAccessToken = "";
|
||||
|
||||
const assetId = 1683;
|
||||
|
||||
const azure = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(azure);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
legacyId: Azure 2D Tiles.html
|
||||
title: Azure 2D Tiles
|
||||
description: Global imagery data from Azure Maps.
|
||||
development: true
|
||||
labels:
|
||||
- Imagery
|
||||
- Development
|
||||
thumbnail: thumbnail.jpg
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import * as Cesium from "cesium";
|
||||
|
||||
const assetId = 3830184;
|
||||
|
||||
const base = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
assetId,
|
||||
mapType: "satellite",
|
||||
}),
|
||||
);
|
||||
|
||||
const overlay = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||
assetId,
|
||||
overlayLayerType: "layerRoadmap",
|
||||
styles: [
|
||||
{
|
||||
stylers: [{ hue: "#00ffe6" }, { saturation: -20 }],
|
||||
},
|
||||
{
|
||||
featureType: "road",
|
||||
elementType: "geometry",
|
||||
stylers: [{ lightness: 100 }, { visibility: "simplified" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(base);
|
||||
viewer.imageryLayers.add(overlay);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
legacyId: Google 2D Tiles with Roadmap Styles.html
|
||||
title: Google 2D Tiles with Custom Styles
|
||||
description: Imagery tiles from Google Maps with additional parameters to create overlays and custom styles.
|
||||
labels:
|
||||
- Imagery
|
||||
- Showcases
|
||||
thumbnail: thumbnail.jpg
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import * as Cesium from "cesium";
|
||||
|
||||
const assetId = 3830184;
|
||||
|
||||
const google = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
viewer.imageryLayers.add(google);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
legacyId: Google 2D Tiles.html
|
||||
title: Google 2D Tiles
|
||||
description: Global imagery data from Google Maps.
|
||||
labels:
|
||||
- Imagery
|
||||
- Showcases
|
||||
thumbnail: thumbnail.jpg
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import * as Cesium from "cesium";
|
||||
import Sandcastle from "Sandcastle";
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
baseLayer: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: Cesium.IonGeocodeProviderType.GOOGLE,
|
||||
timeline: false,
|
||||
sceneModePicker: false,
|
||||
navigationHelpButton: false,
|
||||
homeButton: false,
|
||||
terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1),
|
||||
});
|
||||
viewer.geocoder.viewModel.keepExpanded = true;
|
||||
|
||||
const menuOptions = [];
|
||||
|
||||
const dropdownOptions = [
|
||||
{ label: "Google Maps 2D Contour", assetId: 3830186 },
|
||||
{ label: "Google Maps 2D Labels Only", assetId: 3830185 },
|
||||
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
|
||||
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
|
||||
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
|
||||
{ label: "Bing Maps Aerial", assetId: 2 },
|
||||
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
|
||||
{ label: "Bing Maps Road", assetId: 4 },
|
||||
{ label: "Bing Maps Labels Only", assetId: 2411391 },
|
||||
{ label: "Sentinel-2", assetId: 3954 },
|
||||
];
|
||||
|
||||
function showLayer(assetId) {
|
||||
viewer.imageryLayers.removeAll(true);
|
||||
const layer = Cesium.ImageryLayer.fromProviderAsync(
|
||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||
);
|
||||
viewer.imageryLayers.add(layer);
|
||||
}
|
||||
|
||||
dropdownOptions.forEach((opt) => {
|
||||
const option = {
|
||||
text: opt.label,
|
||||
onselect: function () {
|
||||
showLayer(opt.assetId);
|
||||
},
|
||||
};
|
||||
menuOptions.push(option);
|
||||
});
|
||||
|
||||
Sandcastle.addToolbarMenu(menuOptions);
|
||||
|
||||
showLayer(3830186);
|
||||
|
||||
viewer.scene.camera.flyTo({
|
||||
duration: 0,
|
||||
destination: new Cesium.Rectangle.fromDegrees(
|
||||
//Philly
|
||||
-75.280266,
|
||||
39.867004,
|
||||
-74.955763,
|
||||
40.137992,
|
||||
),
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
legacyId: Imagery Assets available from ion.html
|
||||
title: Imagery Assets available from ion
|
||||
description: Global imagery assets available from Cesium ion.
|
||||
labels:
|
||||
- Showcases
|
||||
thumbnail: thumbnail.jpg
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
|
@ -35,16 +35,16 @@ function ProceduralMultiTileVoxelProvider(shape) {
|
|||
this.names = ["color"];
|
||||
this.types = [Cesium.MetadataType.VEC4];
|
||||
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
|
||||
this._levelCount = 3;
|
||||
this.availableLevels = 3;
|
||||
this.globalTransform = globalTransform;
|
||||
}
|
||||
|
||||
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
|
||||
const { tileLevel, tileX, tileY, tileZ } = options;
|
||||
|
||||
if (tileLevel >= this._levelCount) {
|
||||
if (tileLevel >= this.availableLevels) {
|
||||
return Promise.reject(
|
||||
`No tiles available beyond level ${this._levelCount}`,
|
||||
`No tiles available beyond level ${this.availableLevels - 1}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +129,7 @@ function createPrimitive(provider) {
|
|||
customShader: customShader,
|
||||
});
|
||||
voxelPrimitive.nearestSampling = true;
|
||||
voxelPrimitive.stepSize = 0.7;
|
||||
|
||||
viewer.scene.primitives.add(voxelPrimitive);
|
||||
camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
|
||||
|
|
|
|||
|
|
@ -92,14 +92,14 @@ function ProceduralMultiTileVoxelProvider(shape) {
|
|||
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
|
||||
this.globalTransform = globalTransform;
|
||||
|
||||
this._levelCount = 2;
|
||||
this._allVoxelData = new Array(this._levelCount);
|
||||
this.availableLevels = 2;
|
||||
this._allVoxelData = new Array(this.availableLevels);
|
||||
|
||||
const allVoxelData = this._allVoxelData;
|
||||
const channelCount = Cesium.MetadataType.getComponentCount(this.types[0]);
|
||||
const { dimensions } = this;
|
||||
|
||||
for (let level = 0; level < this._levelCount; level++) {
|
||||
for (let level = 0; level < this.availableLevels; level++) {
|
||||
const dimAtLevel = Math.pow(2, level);
|
||||
const voxelCountX = dimensions.x * dimAtLevel;
|
||||
const voxelCountY = dimensions.y * dimAtLevel;
|
||||
|
|
@ -127,9 +127,9 @@ function ProceduralMultiTileVoxelProvider(shape) {
|
|||
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
|
||||
const { tileLevel, tileX, tileY, tileZ } = options;
|
||||
|
||||
if (tileLevel >= this._levelCount) {
|
||||
if (tileLevel >= this.availableLevels) {
|
||||
return Promise.reject(
|
||||
`No tiles available beyond level ${this._levelCount - 1}`,
|
||||
`No tiles available beyond level ${this.availableLevels - 1}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@cesium/sandcastle",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"scripts/buildGallery.js"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import process from "process";
|
||||
|
||||
const config = {
|
||||
root: ".",
|
||||
sourceUrl: "https://github.com/CesiumGS/cesium/blob/main/packages/sandcastle",
|
||||
|
|
@ -19,7 +21,7 @@ const config = {
|
|||
labels: [],
|
||||
development: false,
|
||||
},
|
||||
includeDevelopment: true,
|
||||
includeDevelopment: !process.env.PROD,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,10 @@ function App() {
|
|||
synchronizeColorScheme
|
||||
>
|
||||
<div className="banner">
|
||||
<Anchor href="https://sandcastle.cesium.com" tone="accent">
|
||||
<Anchor
|
||||
href="https://cesium.com/downloads/cesiumjs/releases/1.134/Apps/Sandcastle/index.html"
|
||||
tone="accent"
|
||||
>
|
||||
Looking for the old Sandcastle? It's still here (for a little while) →
|
||||
</Anchor>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -126,11 +126,9 @@
|
|||
padding: var(--stratakit-space-x2);
|
||||
gap: var(--stratakit-space-x2);
|
||||
overflow-y: auto;
|
||||
&:focus-visible {
|
||||
/* small margin to allow focus highlight to be visible all the way around */
|
||||
margin: 0 3px 4px;
|
||||
border-radius: var(--stratakit-ext-radius-xs);
|
||||
}
|
||||
/* small margin to allow focus highlight to be visible all the way around */
|
||||
margin: 0 3px 4px;
|
||||
border-radius: var(--stratakit-ext-radius-xs);
|
||||
}
|
||||
|
||||
.empty-list div {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,21 @@ export function useGalleryItemStore() {
|
|||
return isGalleryLoaded ? () => loadFromUrl(items, legacyIds) : null;
|
||||
}, [items, legacyIds]);
|
||||
|
||||
const [isFirstSearch, setFirstSearch] = useState(true);
|
||||
const setSearchTermWrapper = useCallback(
|
||||
(newSearchTerm: string | null) => {
|
||||
// the default label filter for Showcases can be confusing when it doesn't
|
||||
// search everything after page load. Remove the filter on the first search only
|
||||
// to ensure we search everything
|
||||
if (isFirstSearch) {
|
||||
setSearchFilter(null);
|
||||
setFirstSearch(false);
|
||||
}
|
||||
setSearchTerm(newSearchTerm);
|
||||
},
|
||||
[setSearchTerm, isFirstSearch, setSearchFilter],
|
||||
);
|
||||
|
||||
return {
|
||||
items,
|
||||
galleryLoaded,
|
||||
|
|
@ -203,7 +218,7 @@ export function useGalleryItemStore() {
|
|||
searchFilter,
|
||||
searchTerm,
|
||||
isSearchPending,
|
||||
setSearchTerm,
|
||||
setSearchTerm: setSearchTermWrapper,
|
||||
setSearchFilter,
|
||||
searchResults: memoizedSearchResults,
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,20 @@ export function loadFromUrl(
|
|||
) {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const codeParam = searchParams.get("code");
|
||||
if (codeParam) {
|
||||
// This is a legacy support type url that was used by ion.
|
||||
// Ideally we use the #c= param as that results in shorter urls
|
||||
// The code query parameter is a Base64 encoded JSON string with `code` and `html` properties.
|
||||
const json = JSON.parse(window.atob(codeParam.replaceAll(" ", "+")));
|
||||
|
||||
return {
|
||||
title: "New Sandcastle",
|
||||
code: json.code,
|
||||
html: json.html,
|
||||
};
|
||||
}
|
||||
|
||||
if (window.location.hash.indexOf("#c=") === 0) {
|
||||
const base64String = window.location.hash.substr(3);
|
||||
const { code, html } = decodeBase64Data(base64String);
|
||||
|
|
|
|||
|
|
@ -83,10 +83,28 @@ function SandcastleEditor({
|
|||
const {
|
||||
settings: { fontFamily, fontSize, fontLigatures },
|
||||
} = useContext(SettingsContext);
|
||||
const documentRef = useRef(document);
|
||||
useEffect(() => {
|
||||
internalEditorRef.current?.updateOptions({
|
||||
fontFamily: availableFonts[fontFamily]?.cssValue ?? "Droid Sans Mono",
|
||||
});
|
||||
const cssName = availableFonts[fontFamily]?.cssValue ?? "Droid Sans Mono";
|
||||
const fontFace = [...documentRef.current.fonts].find(
|
||||
(font) => font.family === cssName && font.weight === "400",
|
||||
);
|
||||
if (fontFace?.status !== "loaded") {
|
||||
// Monaco needs to check the size of the font for things like cursor position
|
||||
// and variable highlighting. If it does this check before the font has loaded
|
||||
// it will get the wrong size and may be horribly offset especially with ligatures
|
||||
// https://github.com/microsoft/monaco-editor/issues/392
|
||||
documentRef.current.fonts.load(`1rem ${cssName}`).then(() => {
|
||||
internalEditorRef.current?.updateOptions({
|
||||
fontFamily: cssName,
|
||||
});
|
||||
monaco.editor.remeasureFonts();
|
||||
});
|
||||
} else {
|
||||
internalEditorRef.current?.updateOptions({
|
||||
fontFamily: cssName,
|
||||
});
|
||||
}
|
||||
}, [fontFamily]);
|
||||
useEffect(() => {
|
||||
internalEditorRef.current?.updateOptions({
|
||||
|
|
|
|||
|
|
@ -302,6 +302,79 @@ of the world.\nhttp://www.openstreetmap.org",
|
|||
}),
|
||||
);
|
||||
|
||||
providerViewModels.push(
|
||||
new ProviderViewModel({
|
||||
name: "Google Maps Satellite",
|
||||
iconUrl: buildModuleUrl(
|
||||
"Widgets/Images/ImageryProviders/googleSatellite.png",
|
||||
),
|
||||
tooltip: "Imagery from Google Maps",
|
||||
category: "Cesium ion",
|
||||
creationFunction: function () {
|
||||
return IonImageryProvider.fromAssetId(3830182);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
providerViewModels.push(
|
||||
new ProviderViewModel({
|
||||
name: "Google Maps Satellite with Labels",
|
||||
iconUrl: buildModuleUrl(
|
||||
"Widgets/Images/ImageryProviders/googleSatelliteLabels.png",
|
||||
),
|
||||
tooltip: "Imagery with place labels from Google Maps",
|
||||
category: "Cesium ion",
|
||||
creationFunction: function () {
|
||||
return IonImageryProvider.fromAssetId(3830183);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
providerViewModels.push(
|
||||
new ProviderViewModel({
|
||||
name: "Google Maps Roadmap",
|
||||
iconUrl: buildModuleUrl(
|
||||
"Widgets/Images/ImageryProviders/googleRoadmap.png",
|
||||
),
|
||||
tooltip:
|
||||
"Labeled roads and other features on a base landscape from Google Maps",
|
||||
category: "Cesium ion",
|
||||
creationFunction: function () {
|
||||
return IonImageryProvider.fromAssetId(3830184);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
providerViewModels.push(
|
||||
new ProviderViewModel({
|
||||
name: "Google Maps Labels Only",
|
||||
iconUrl: buildModuleUrl(
|
||||
"Widgets/Images/ImageryProviders/googleLabels.png",
|
||||
),
|
||||
tooltip:
|
||||
"Place labels from Google Maps to combine with other imagery such as Sentinel-2",
|
||||
category: "Cesium ion",
|
||||
creationFunction: function () {
|
||||
return IonImageryProvider.fromAssetId(3830185);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
providerViewModels.push(
|
||||
new ProviderViewModel({
|
||||
name: "Google Maps Contour",
|
||||
iconUrl: buildModuleUrl(
|
||||
"Widgets/Images/ImageryProviders/googleContour.png",
|
||||
),
|
||||
tooltip:
|
||||
"Hillshade mapping, contour lines, natural features (roadmap features hidden) from Google Maps",
|
||||
category: "Cesium ion",
|
||||
creationFunction: function () {
|
||||
return IonImageryProvider.fromAssetId(3830186);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return providerViewModels;
|
||||
}
|
||||
export default createDefaultImageryProviderViewModels;
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@cesium/widgets",
|
||||
"version": "13.1.1",
|
||||
"version": "13.2.0",
|
||||
"description": "A widgets library for use with CesiumJS. CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.",
|
||||
"keywords": [
|
||||
"3D",
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"node": ">=20.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cesium/engine": "^20.0.1",
|
||||
"@cesium/engine": "^21.0.0",
|
||||
"nosleep.js": "^0.12.0"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||