mirror of https://github.com/CesiumGS/cesium.git
Compare commits
367 Commits
fe54e66971
...
7b3b5c25a9
| Author | SHA1 | Date |
|---|---|---|
|
|
7b3b5c25a9 | |
|
|
dce3549952 | |
|
|
162458dd58 | |
|
|
528acebaca | |
|
|
fc1da0452e | |
|
|
b423ef6c62 | |
|
|
e9ab31a23f | |
|
|
9a0da95b1b | |
|
|
1c8b77b31f | |
|
|
2d1e175deb | |
|
|
b3a6c52f9c | |
|
|
ee2b3813b2 | |
|
|
4e3980cc53 | |
|
|
c92d0d80a1 | |
|
|
16d0cccfdb | |
|
|
5213dfc1c0 | |
|
|
bd5aa3ff29 | |
|
|
bcc5ea383e | |
|
|
278174f9f1 | |
|
|
cfce282f08 | |
|
|
debd74eb91 | |
|
|
d063514d1a | |
|
|
d748edee14 | |
|
|
cdbd86b05b | |
|
|
a1419b267c | |
|
|
644c18332b | |
|
|
71a12a341b | |
|
|
9113ae75bc | |
|
|
a6e0226968 | |
|
|
e70ec8043c | |
|
|
7145e11ba4 | |
|
|
95cb3bb3b9 | |
|
|
e6ac78ff3b | |
|
|
034653c846 | |
|
|
45f8734037 | |
|
|
64f07aec37 | |
|
|
febfade1df | |
|
|
9b42731f52 | |
|
|
55b2ba865a | |
|
|
d4582b519b | |
|
|
d34e3ccc30 | |
|
|
85fabf515b | |
|
|
84890e6d95 | |
|
|
829a768abf | |
|
|
4ed382f4f3 | |
|
|
7148621fda | |
|
|
3cc5f439f3 | |
|
|
ba462e219d | |
|
|
1c30cf64b5 | |
|
|
cdf07f4214 | |
|
|
e399a8a306 | |
|
|
c0acb452b7 | |
|
|
1689691878 | |
|
|
ac61464780 | |
|
|
edceb42f3a | |
|
|
4547dbb196 | |
|
|
26bb7275c3 | |
|
|
8f923ededf | |
|
|
b833c8c0a1 | |
|
|
34d6698079 | |
|
|
cf19f38748 | |
|
|
a129f06ea9 | |
|
|
caf297d63e | |
|
|
30ed7083c3 | |
|
|
88951ffde4 | |
|
|
96c4ca3f4b | |
|
|
90e527961e | |
|
|
7aae7e358e | |
|
|
c59e106436 | |
|
|
7d3efcb544 | |
|
|
eb1fa20a49 | |
|
|
a3ae7943f9 | |
|
|
b321b6fadc | |
|
|
7f81c437f4 | |
|
|
3f911ee8e1 | |
|
|
9675ce52d7 | |
|
|
01fde5c93f | |
|
|
488bc7c961 | |
|
|
c8b00a4ec7 | |
|
|
8e57c5a37c | |
|
|
b823e08546 | |
|
|
7081df8824 | |
|
|
fdb8ee42a4 | |
|
|
ba5b5d77e8 | |
|
|
1d6d5e6570 | |
|
|
f73f2f2b5c | |
|
|
6f1349bc09 | |
|
|
b0f14b30f3 | |
|
|
5d00fca596 | |
|
|
08ecab02d0 | |
|
|
cbfa3cea27 | |
|
|
2e3d6b6bf2 | |
|
|
83364cc5a5 | |
|
|
86049c8fe6 | |
|
|
ec3e71b360 | |
|
|
40bce95af4 | |
|
|
ce0c6a79e9 | |
|
|
b222893b2a | |
|
|
1f737c2d78 | |
|
|
9b215be7b7 | |
|
|
b00b65cee1 | |
|
|
1e8d248e34 | |
|
|
63a8371eb6 | |
|
|
5cc27ef976 | |
|
|
22df371dda | |
|
|
3af3ef03a2 | |
|
|
8b212bd7b5 | |
|
|
9cfc449c55 | |
|
|
b1e03247cb | |
|
|
53fa96bf85 | |
|
|
790962d129 | |
|
|
b478c3fb63 | |
|
|
61f8dcfeef | |
|
|
f02b48a50a | |
|
|
e53c22d09d | |
|
|
d08ce7a58e | |
|
|
2de6dd7ba3 | |
|
|
668b8bba24 | |
|
|
77fb377592 | |
|
|
023b4f2d60 | |
|
|
f58ea2c51e | |
|
|
a8f7d8f9b8 | |
|
|
948e00cfc3 | |
|
|
64ec998897 | |
|
|
ba045e7e77 | |
|
|
b74d62da2a | |
|
|
e944a82c68 | |
|
|
c2024630f7 | |
|
|
bf11b3af93 | |
|
|
5c07c4dbbc | |
|
|
2b7861edab | |
|
|
d31a0c3ba7 | |
|
|
453096724d | |
|
|
2afb7b65b9 | |
|
|
4439df1658 | |
|
|
da2dd8a942 | |
|
|
c9c27259ac | |
|
|
2d3be0f559 | |
|
|
470fbdd000 | |
|
|
ebaf3e7d20 | |
|
|
c8d2d81425 | |
|
|
3a0e0aad04 | |
|
|
62b5da41fa | |
|
|
0ed8a626ba | |
|
|
aa140c1a79 | |
|
|
0d3daff896 | |
|
|
6872ef2e99 | |
|
|
8e0ba0c6d3 | |
|
|
f5fad8ed20 | |
|
|
bc97809deb | |
|
|
7a01e6ebe3 | |
|
|
f3351b7133 | |
|
|
c0f3fd44fd | |
|
|
116ef099dd | |
|
|
80abee3125 | |
|
|
3b6dd1e271 | |
|
|
988b72f44e | |
|
|
452dd1984d | |
|
|
ba486ca401 | |
|
|
e0eadb4628 | |
|
|
c2f5433469 | |
|
|
639ea459b0 | |
|
|
bee0278186 | |
|
|
cae6a029d1 | |
|
|
981e206407 | |
|
|
9550c39aa0 | |
|
|
00959e31ab | |
|
|
3363ee863c | |
|
|
0c9a5236b1 | |
|
|
fd8540856e | |
|
|
dde0e07c3a | |
|
|
00dce5a3f1 | |
|
|
f3ecf5ba96 | |
|
|
61d330af7d | |
|
|
b695f5de55 | |
|
|
aaf4ae1bd1 | |
|
|
c4e8eea3ac | |
|
|
b586c14aa6 | |
|
|
c708661860 | |
|
|
0f7a9c8f30 | |
|
|
26a011d5d3 | |
|
|
d83fb7631a | |
|
|
1bd41e93cc | |
|
|
f1ab9d0c1e | |
|
|
63c8ccbe66 | |
|
|
d35002027a | |
|
|
20f3edc9aa | |
|
|
2fcefac82f | |
|
|
4aab5650d9 | |
|
|
97f606dec1 | |
|
|
dcc4ec107b | |
|
|
4de8de12e4 | |
|
|
f3f4075c55 | |
|
|
aabcb3d9e6 | |
|
|
f7c4d62d50 | |
|
|
814cb73efe | |
|
|
f490f639ad | |
|
|
77aba4fc58 | |
|
|
7b7636fc00 | |
|
|
43991b4372 | |
|
|
11d846a7f9 | |
|
|
a8532185a2 | |
|
|
3407e681ec | |
|
|
4caa147876 | |
|
|
a64c37f6f8 | |
|
|
3ef69c7324 | |
|
|
d1b4b547c7 | |
|
|
4cfef57f41 | |
|
|
6fe50a1fe2 | |
|
|
6c5c2134f0 | |
|
|
9d8871a09e | |
|
|
fd3594ff5d | |
|
|
78a73e7a1e | |
|
|
04a28b6947 | |
|
|
9a8a070977 | |
|
|
8805911607 | |
|
|
8e9c687490 | |
|
|
a0e2657fd2 | |
|
|
c60a29bc3b | |
|
|
74d31b2022 | |
|
|
b5c1b76a32 | |
|
|
61fbc02a40 | |
|
|
33a8f3e20f | |
|
|
97f53e0038 | |
|
|
f7d9d1793d | |
|
|
9c2f2f7b58 | |
|
|
6dbc922d1a | |
|
|
2b990bfff5 | |
|
|
9bac174f8a | |
|
|
40f8736c60 | |
|
|
0bc0b2b6e4 | |
|
|
e1be5f9c3f | |
|
|
8ad5fd9c32 | |
|
|
538fd35db9 | |
|
|
cedafe74da | |
|
|
204fabdc90 | |
|
|
1a03cf2bec | |
|
|
3c86935826 | |
|
|
b3c1a1d06f | |
|
|
237e33700e | |
|
|
43d4fc36ed | |
|
|
349f253bd7 | |
|
|
2e73640ab9 | |
|
|
31d2611e1a | |
|
|
e8a76fab32 | |
|
|
f4e6772e29 | |
|
|
404b2202ea | |
|
|
8c48cb0407 | |
|
|
3f69f5f8e0 | |
|
|
3111ef4a7f | |
|
|
526ea8ee45 | |
|
|
483ab35c9c | |
|
|
358a793793 | |
|
|
26fe02dd88 | |
|
|
a3b426d8d9 | |
|
|
1d005a67f8 | |
|
|
cd9ca23946 | |
|
|
c14a8a9d81 | |
|
|
fa4f844816 | |
|
|
b5c3d90d18 | |
|
|
98786878e7 | |
|
|
ea5bfcf727 | |
|
|
a47080272d | |
|
|
93a2487bee | |
|
|
de1d36c5f7 | |
|
|
d78719e445 | |
|
|
5d2dce28c1 | |
|
|
f31dc77928 | |
|
|
567a3c7f2f | |
|
|
aa9dbe71c9 | |
|
|
8d427e1882 | |
|
|
224836e426 | |
|
|
29ee6fa651 | |
|
|
f236c151ae | |
|
|
1018d81124 | |
|
|
fe06f25f9b | |
|
|
1bb353b5de | |
|
|
52af82768f | |
|
|
d136d73cd7 | |
|
|
6a85cd99a0 | |
|
|
853fe3564b | |
|
|
e195558d9d | |
|
|
4853568581 | |
|
|
26cbe4e001 | |
|
|
75ace54f12 | |
|
|
d506da6590 | |
|
|
a667116333 | |
|
|
fef9e252d5 | |
|
|
9d303b4bbb | |
|
|
1228d6a701 | |
|
|
377f9dc5ec | |
|
|
ab569b2ea5 | |
|
|
124262d6b1 | |
|
|
03adf9b17c | |
|
|
0fbb24c803 | |
|
|
15c652c60b | |
|
|
4a49d2e5c5 | |
|
|
c7c2d1ec77 | |
|
|
ab8871858d | |
|
|
e4d5c9fb54 | |
|
|
8e0b99ed17 | |
|
|
4e6fc4c372 | |
|
|
6cfefad2f3 | |
|
|
3c3756b2d9 | |
|
|
aadb8c22e1 | |
|
|
ea152095a8 | |
|
|
23abe63750 | |
|
|
0177f21311 | |
|
|
43e67eb3e9 | |
|
|
2380ce85b9 | |
|
|
d0cfece7ce | |
|
|
70e9b3a74c | |
|
|
25f75822b4 | |
|
|
9df18c8483 | |
|
|
192323ebed | |
|
|
eb3d6bae67 | |
|
|
9566943878 | |
|
|
01d5a0b755 | |
|
|
737eb8d66e | |
|
|
a0fad10a8c | |
|
|
3a6d8cca79 | |
|
|
63bc3d328a | |
|
|
ee3759a0a2 | |
|
|
52c16a0c8c | |
|
|
b391890dbd | |
|
|
10c2d4bfb2 | |
|
|
51db7326c6 | |
|
|
bd6a3af42d | |
|
|
1048196a13 | |
|
|
1ed774cf5a | |
|
|
494fd9d35b | |
|
|
a17a680a47 | |
|
|
a454c3c441 | |
|
|
b42fe7f7f9 | |
|
|
8ddbdb82f3 | |
|
|
19c63752a7 | |
|
|
a7dd147e97 | |
|
|
04387ec904 | |
|
|
a17d60187f | |
|
|
161a67584c | |
|
|
d082644cde | |
|
|
f1ce06120d | |
|
|
3aa7401aef | |
|
|
bdb276d7c9 | |
|
|
7e60cc60a6 | |
|
|
22bb9243cc | |
|
|
60e5164d57 | |
|
|
446307c2d9 | |
|
|
892bbe457e | |
|
|
75a68123cf | |
|
|
99b90a17b3 | |
|
|
7b21e6e330 | |
|
|
09b1961b80 | |
|
|
ba6d133c8f | |
|
|
d13bd0c606 | |
|
|
454d06cb84 | |
|
|
ba2919d467 | |
|
|
b82f5d2d79 | |
|
|
921ac1c0a4 | |
|
|
0c2becaaea | |
|
|
d9546f4667 | |
|
|
ba4aff81a6 | |
|
|
cc7e38c64a | |
|
|
e150206d4e | |
|
|
c7a8786303 | |
|
|
b95cbd282e | |
|
|
6012a5fd02 |
|
|
@ -2,7 +2,7 @@ name: deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'cesium.com'
|
- "cesium.com"
|
||||||
- production
|
- production
|
||||||
concurrency:
|
concurrency:
|
||||||
group: deploy-${{ github.ref }}
|
group: deploy-${{ github.ref }}
|
||||||
|
|
@ -22,14 +22,13 @@ jobs:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_REPO: ${{ github.repository }}
|
GITHUB_REPO: ${{ github.repository }}
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
BASE_URL: /cesium/${{ github.ref_name }}/
|
|
||||||
DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/
|
DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: install node 22
|
- name: install node 22
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: "22"
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: set the version in package.json
|
- name: set the version in package.json
|
||||||
|
|
@ -42,8 +41,6 @@ jobs:
|
||||||
run: npm pack --workspaces &> /dev/null
|
run: npm pack --workspaces &> /dev/null
|
||||||
- name: build apps
|
- name: build apps
|
||||||
run: npm run build-apps
|
run: npm run build-apps
|
||||||
- name: build sandcastle v2
|
|
||||||
run: npm run build-ci -w packages/sandcastle -- -l warn
|
|
||||||
- uses: ./.github/actions/verify-package
|
- uses: ./.github/actions/verify-package
|
||||||
- name: deploy to s3
|
- name: deploy to s3
|
||||||
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,17 @@ jobs:
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: build website release
|
- name: build website release
|
||||||
run: npm run website-release
|
run: npm run website-release
|
||||||
- name: build apps
|
|
||||||
run: npm run build-apps
|
|
||||||
- name: build types
|
- name: build types
|
||||||
run: npm run build-ts
|
run: npm run build-ts
|
||||||
- name: build prod sandcastle
|
- name: build apps
|
||||||
run: npm run build-prod -w packages/sandcastle -- -l warn
|
run: npm run build-apps
|
||||||
- name: deploy to cesium.com
|
- name: deploy to cesium.com
|
||||||
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
||||||
|
# Download zip from the Github release and unzip to Build/release/
|
||||||
|
# Publish that unzipped code to the bucket for https://cesium.com/downloads/cesiumjs/releases/[version]/... urls
|
||||||
|
# Publish the documentation files to the bucket for https://cesium.com/learn/cesiumjs/ref-doc/
|
||||||
|
# Publish the simple viewer app to the bucket for https://cesium.com/cesiumjs/cesium-viewer/
|
||||||
|
# Publish sandcastle to the bucket for https://sandcastle.cesium.com/
|
||||||
run: |
|
run: |
|
||||||
curl -LO $(curl https://api.github.com/repos/CesiumGS/cesium/releases/latest -H "Authorization: ${GITHUB_TOKEN}" | jq -r '.assets[0].browser_download_url')
|
curl -LO $(curl https://api.github.com/repos/CesiumGS/cesium/releases/latest -H "Authorization: ${GITHUB_TOKEN}" | jq -r '.assets[0].browser_download_url')
|
||||||
unzip Cesium-$(cat package.json | jq -r '.version' | sed 's/\.0$//').zip -d Build/release/ -x "Apps"
|
unzip Cesium-$(cat package.json | jq -r '.version' | sed 's/\.0$//').zip -d Build/release/ -x "Apps"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ name: sandcastle-dev
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'cesium.com'
|
- "main"
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
- name: install node 22
|
- name: install node 22
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: "22"
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: build website release
|
- name: build website release
|
||||||
|
|
@ -28,7 +28,7 @@ jobs:
|
||||||
- name: build types
|
- name: build types
|
||||||
run: npm run build-ts
|
run: npm run build-ts
|
||||||
- name: build prod sandcastle
|
- name: build prod sandcastle
|
||||||
run: npm run build-prod -w packages/sandcastle -- -l warn
|
run: npm run build-sandcastle
|
||||||
- name: deploy to dev-sandcastle.cesium.com
|
- name: deploy to dev-sandcastle.cesium.com
|
||||||
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
/scripts/
|
/scripts/
|
||||||
/favicon.ico
|
/favicon.ico
|
||||||
/gulpfile.js
|
/gulpfile.js
|
||||||
|
/gulpfile.apps.js
|
||||||
|
/gulpfile.makezip.js
|
||||||
/index.html
|
/index.html
|
||||||
/index.release.html
|
/index.release.html
|
||||||
/launches
|
/launches
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,7 @@
|
||||||
window.startup = async function (Cesium) {
|
window.startup = async function (Cesium) {
|
||||||
"use strict";
|
"use strict";
|
||||||
//Sandcastle_Begin
|
//Sandcastle_Begin
|
||||||
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
|
const assetId = 3891169;
|
||||||
Cesium.Ion.defaultAccessToken = "";
|
|
||||||
|
|
||||||
const assetId = 1683;
|
|
||||||
|
|
||||||
const azure = Cesium.ImageryLayer.fromProviderAsync(
|
const azure = Cesium.ImageryLayer.fromProviderAsync(
|
||||||
Cesium.IonImageryProvider.fromAssetId(assetId),
|
Cesium.IonImageryProvider.fromAssetId(assetId),
|
||||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
|
@ -0,0 +1,139 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||||
|
/>
|
||||||
|
<meta name="description" content="Apply materials to the globe." />
|
||||||
|
<meta name="cesium-sandcastle-labels" content="Showcases" />
|
||||||
|
<title>Cesium Demo</title>
|
||||||
|
<script type="text/javascript" src="../Sandcastle-header.js"></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="../../../Build/CesiumUnminified/Cesium.js"
|
||||||
|
nomodule
|
||||||
|
></script>
|
||||||
|
<script type="module" src="../load-cesium-es6.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
|
||||||
|
<style>
|
||||||
|
@import url(../templates/bucket.css);
|
||||||
|
</style>
|
||||||
|
<div id="cesiumContainer" class="fullSize"></div>
|
||||||
|
<div id="loadingOverlay"><h1>Loading...</h1></div>
|
||||||
|
<div id="toolbar">
|
||||||
|
<div id="zoomButtons"></div>
|
||||||
|
</div>
|
||||||
|
<script id="cesium_sandcastle_script">
|
||||||
|
window.startup = async function (Cesium) {
|
||||||
|
"use strict";
|
||||||
|
//Sandcastle_Begin
|
||||||
|
const terrainProvider = await Cesium.Cesium3DTilesTerrainProvider.fromIonAssetId(
|
||||||
|
3923568,
|
||||||
|
{
|
||||||
|
requestVertexNormals: true, // Needed for hillshade lighting
|
||||||
|
requestWaterMask: true, // Needed to distinguish land from water
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||||
|
terrainProvider: terrainProvider,
|
||||||
|
scene3DOnly: true,
|
||||||
|
sceneModePicker: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a globe material for shading elevation only on land
|
||||||
|
const customElevationMaterial = new Cesium.Material({
|
||||||
|
fabric: {
|
||||||
|
type: "ElevationLand",
|
||||||
|
materials: {
|
||||||
|
waterMaskMaterial: {
|
||||||
|
type: "WaterMask",
|
||||||
|
},
|
||||||
|
elevationRampMaterial: {
|
||||||
|
type: "ElevationRamp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
diffuse: "elevationRampMaterial.diffuse",
|
||||||
|
alpha: "1.0 - waterMaskMaterial.alpha", // We'll need the inverse of the watermask to shade land
|
||||||
|
},
|
||||||
|
},
|
||||||
|
translucent: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const minHeight = -414.0; // approximate dead sea elevation
|
||||||
|
const maxHeight = 8777.0; // approximate everest elevation
|
||||||
|
const elevationRamp = [0.0, 0.045, 0.45, 0.5, 0.55, 1.0];
|
||||||
|
function getColorRamp() {
|
||||||
|
const ramp = document.createElement("canvas");
|
||||||
|
ramp.width = 100;
|
||||||
|
ramp.height = 1;
|
||||||
|
const ctx = ramp.getContext("2d");
|
||||||
|
|
||||||
|
const values = elevationRamp;
|
||||||
|
|
||||||
|
const grd = ctx.createLinearGradient(0, 0, 100, 0);
|
||||||
|
|
||||||
|
// See https://gis.stackexchange.com/questions/25099/choosing-colour-ramp-to-use-for-elevation
|
||||||
|
grd.addColorStop(values[0], "#344f31");
|
||||||
|
grd.addColorStop(values[1], "#5b8742");
|
||||||
|
grd.addColorStop(values[2], "#e6daa5");
|
||||||
|
grd.addColorStop(values[3], "#fdc771");
|
||||||
|
grd.addColorStop(values[4], "#b99d89");
|
||||||
|
grd.addColorStop(values[5], "#f0f0f0");
|
||||||
|
|
||||||
|
ctx.fillStyle = grd;
|
||||||
|
ctx.fillRect(0, 0, 100, 1);
|
||||||
|
|
||||||
|
return ramp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const globe = viewer.scene.globe;
|
||||||
|
const material = customElevationMaterial;
|
||||||
|
const shadingUniforms = material.materials.elevationRampMaterial.uniforms;
|
||||||
|
|
||||||
|
globe.showWaterEffect = false;
|
||||||
|
globe.enableLighting = true;
|
||||||
|
|
||||||
|
shadingUniforms.minimumHeight = minHeight;
|
||||||
|
shadingUniforms.maximumHeight = maxHeight;
|
||||||
|
shadingUniforms.image = getColorRamp();
|
||||||
|
globe.material = material;
|
||||||
|
|
||||||
|
// Light the scene with a hillshade effect similar to https://pro.arcgis.com/en/pro-app/latest/tool-reference/3d-analyst/how-hillshade-works.htm
|
||||||
|
const scene = viewer.scene;
|
||||||
|
scene.light = new Cesium.DirectionalLight({
|
||||||
|
direction: new Cesium.Cartesian3(1, 0, 0), // Updated every frame
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the light position base on the camera
|
||||||
|
const scratchNormal = new Cesium.Cartesian3();
|
||||||
|
scene.preRender.addEventListener(function (scene, time) {
|
||||||
|
const surfaceNormal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(
|
||||||
|
scene.camera.positionWC,
|
||||||
|
scratchNormal,
|
||||||
|
);
|
||||||
|
const negativeNormal = Cesium.Cartesian3.negate(surfaceNormal, surfaceNormal);
|
||||||
|
scene.light.direction = Cesium.Cartesian3.normalize(
|
||||||
|
Cesium.Cartesian3.add(negativeNormal, scene.camera.rightWC, surfaceNormal),
|
||||||
|
scene.light.direction,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
//Sandcastle_End
|
||||||
|
};
|
||||||
|
if (typeof Cesium !== "undefined") {
|
||||||
|
window.startupCalled = true;
|
||||||
|
window.startup(Cesium).catch((error) => {
|
||||||
|
"use strict";
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
Sandcastle.finishedLoading();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -45,10 +45,9 @@
|
||||||
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
|
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
|
||||||
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
|
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
|
||||||
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
|
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
|
||||||
{ label: "Bing Maps Aerial", assetId: 2 },
|
{ label: "Azure Maps Aerial", assetId: 3891168 },
|
||||||
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
|
{ label: "Azure Maps Roads", assetId: 3891169 },
|
||||||
{ label: "Bing Maps Road", assetId: 4 },
|
{ label: "Azure Maps Labels Only", assetId: 3891170 },
|
||||||
{ label: "Bing Maps Labels Only", assetId: 2411391 },
|
|
||||||
{ label: "Sentinel-2", assetId: 3954 },
|
{ label: "Sentinel-2", assetId: 3954 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
//Sandcastle_Begin
|
//Sandcastle_Begin
|
||||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||||
baseLayer: Cesium.ImageryLayer.fromWorldImagery({
|
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
|
||||||
style: Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
|
Cesium.IonImageryProvider.fromAssetId(3830183),
|
||||||
}),
|
),
|
||||||
baseLayerPicker: false,
|
baseLayerPicker: false,
|
||||||
});
|
});
|
||||||
const layers = viewer.scene.imageryLayers;
|
const layers = viewer.scene.imageryLayers;
|
||||||
|
|
|
||||||
28
CHANGES.md
28
CHANGES.md
|
|
@ -1,29 +1,47 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 1.136
|
||||||
|
|
||||||
|
### @cesium/engine
|
||||||
|
|
||||||
|
#### Fixes :wrench:
|
||||||
|
|
||||||
|
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
|
||||||
|
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
|
||||||
|
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)
|
||||||
|
|
||||||
|
#### Additions :tada:
|
||||||
|
|
||||||
|
- Added `scene.pickAsync` for non GPU blocking picking using WebGL2 [#12983](https://github.com/CesiumGS/cesium/pull/12983)
|
||||||
|
- Improves performance of terrain picks via new terrain picking quadtrees [#8481](https://github.com/CesiumGS/cesium/issues/8481)
|
||||||
|
|
||||||
## 1.135 - 2025-11-03
|
## 1.135 - 2025-11-03
|
||||||
|
|
||||||
### @cesium/engine
|
### @cesium/engine
|
||||||
|
|
||||||
#### Breaking Changes :mega:
|
#### Breaking Changes :mega:
|
||||||
|
|
||||||
- `scene.drillPick` now uses a breadth-first search strategy instead of depth-first. This may change which entities are picked when
|
- Removed support for the `KHR_spz_gaussian_splats_compression` extension in favor of the latest 3D Gaussian splatting extensions for glTF, `KHR_gaussian_splatting` and `KHR_gaussian_splatting_compression_spz_2`. Please re-tile existing Gaussian splatting 3D Tiles [#12837](https://github.com/CesiumGS/cesium/issues/12837)
|
||||||
using large values of `width` and `height` when providing a `limit`, prioritizing entities closer to the camera.
|
- `scene.drillPick` now uses a breadth-first search strategy instead of depth-first. This may change which entities are picked when using large values of `width` and `height` when providing a `limit`, prioritizing entities closer to the camera. [#12916](https://github.com/CesiumGS/cesium/pull/12916)
|
||||||
|
|
||||||
#### Additions :tada:
|
#### Additions :tada:
|
||||||
|
|
||||||
|
- Added experimental support for loading 3D Tiles as terrain, via `Cesium3DTilesTerrainProvider`. See [the PR](https://github.com/CesiumGS/cesium/pull/12963) for limitations on the types of 3D Tiles that can be used. [#12296](https://github.com/CesiumGS/cesium/issues/12296)
|
||||||
- Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765)
|
- Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765)
|
||||||
|
|
||||||
#### Fixes :wrench:
|
#### Fixes :wrench:
|
||||||
|
|
||||||
- Fixed parsing content bounding volumes contained in 3D Tiles 1.1 subtree files. [#12972](https://github.com/CesiumGS/cesium/pull/12972)
|
|
||||||
- Fixes an event bug following recent changes, where adding a new listener during an event callback caused an infinite loop. [#12955](https://github.com/CesiumGS/cesium/pull/12955)
|
|
||||||
- Fix issues with label background when updating properties while `label.show` is `false`. [#12138](https://github.com/CesiumGS/cesium/issues/12138)
|
|
||||||
- Improved performance of `scene.drillPick`. [#12916](https://github.com/CesiumGS/cesium/pull/12916)
|
- Improved performance of `scene.drillPick`. [#12916](https://github.com/CesiumGS/cesium/pull/12916)
|
||||||
- Improved performance when removing primitives. [#3018](https://github.com/CesiumGS/cesium/pull/3018)
|
- Improved performance when removing primitives. [#3018](https://github.com/CesiumGS/cesium/pull/3018)
|
||||||
- Improved performance of terrain Quadtree handling of custom data [#12907](https://github.com/CesiumGS/cesium/pull/12907)
|
- Improved performance of terrain Quadtree handling of custom data [#12907](https://github.com/CesiumGS/cesium/pull/12907)
|
||||||
|
- Fixed vertical exaggeration of ellipsoid-shaped voxels. [#12811](https://github.com/CesiumGS/cesium/issues/12811)
|
||||||
|
- Fixed parsing content bounding volumes contained in 3D Tiles 1.1 subtree files. [#12972](https://github.com/CesiumGS/cesium/pull/12972)
|
||||||
|
- Fixes an event bug following recent changes, where adding a new listener during an event callback caused an infinite loop. [#12955](https://github.com/CesiumGS/cesium/pull/12955)
|
||||||
|
- Fix issues with label background when updating properties while `label.show` is `false`. [#12138](https://github.com/CesiumGS/cesium/issues/12138)
|
||||||
- Fixed picking of `GroundPrimitive` with multiple `PolygonGeometry` instances selecting the wrong instance. [#12978](https://github.com/CesiumGS/cesium/pull/12978)
|
- Fixed picking of `GroundPrimitive` with multiple `PolygonGeometry` instances selecting the wrong instance. [#12978](https://github.com/CesiumGS/cesium/pull/12978)
|
||||||
- Fixed a bug where the removal of draped imagery layers did not update the rendered state [#12923](https://github.com/CesiumGS/cesium/issues/12923)
|
- Fixed a bug where the removal of draped imagery layers did not update the rendered state [#12923](https://github.com/CesiumGS/cesium/issues/12923)
|
||||||
- Fixed precision issues with Gaussian splat tilesets where the root tile does not have a world transform. [#12925](https://github.com/CesiumGS/cesium/issues/12925)
|
- Fixed precision issues with Gaussian splat tilesets where the root tile does not have a world transform. [#12925](https://github.com/CesiumGS/cesium/issues/12925)
|
||||||
|
- Fixed infinite recursion that would happen if user append post-render callbacks within existing callbacks [#12983](https://github.com/CesiumGS/cesium/pull/12983)
|
||||||
|
|
||||||
## 1.134.1 - 2025-10-10
|
## 1.134.1 - 2025-10-10
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
|
||||||
- [Erin Ingram](https://github.com/eringram)
|
- [Erin Ingram](https://github.com/eringram)
|
||||||
- [Daniel Zhong](https://github.com/danielzhong)
|
- [Daniel Zhong](https://github.com/danielzhong)
|
||||||
- [Mark Schlosser](https://github.com/markschlosseratbentley)
|
- [Mark Schlosser](https://github.com/markschlosseratbentley)
|
||||||
|
- [Adam Larkeryd](https://github.com/alarkbentley)
|
||||||
|
- [Don McCurdy](https://github.com/donmccurdy)
|
||||||
- [Flightradar24 AB](https://www.flightradar24.com)
|
- [Flightradar24 AB](https://www.flightradar24.com)
|
||||||
- [Aleksei Kalmykov](https://github.com/kalmykov)
|
- [Aleksei Kalmykov](https://github.com/kalmykov)
|
||||||
- [BIT Systems](http://www.caci.com/bit-systems)
|
- [BIT Systems](http://www.caci.com/bit-systems)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ npm start
|
||||||
Then browse to [http://localhost:8080/](http://localhost:8080/). The landing page includes apps and tools commonly used during development, including:
|
Then browse to [http://localhost:8080/](http://localhost:8080/). The landing page includes apps and tools commonly used during development, including:
|
||||||
|
|
||||||
- **Hello World** : an example for how to create a 3D globe. [Tutorial here](https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/)
|
- **Hello World** : an example for how to create a 3D globe. [Tutorial here](https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/)
|
||||||
- **Sandcastle** : an app for viewing and creating [code examples](https://sandcastle.cesium.com?src=Hello%20World.html&label=Showcases), complete with a live preview
|
- **Sandcastle** : an app for viewing and creating [code examples](https://sandcastle.cesium.com), complete with a live preview
|
||||||
- **Test Suites** : tests using [Jasmine](https://jasmine.github.io/). [Testing guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/TestingGuide/README.md#testing-guide)
|
- **Test Suites** : tests using [Jasmine](https://jasmine.github.io/). [Testing guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/TestingGuide/README.md#testing-guide)
|
||||||
- **Documentation** : reference documentation built from source. [Documentation guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/DocumentationGuide/README.md#documentation-guide)
|
- **Documentation** : reference documentation built from source. [Documentation guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/DocumentationGuide/README.md#documentation-guide)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ There is no one release manager; instead, [our community shares the responsibili
|
||||||
- If `prettier` needs updated you _should_ still update it but keep the version pinned. If you run `npm install prettier@latest` you must remove the `^` in `package.json`. If the number of changes when running `npm run prettier` is large it may be worth opening a separate PR for only those.
|
- If `prettier` needs updated you _should_ still update it but keep the version pinned. If you run `npm install prettier@latest` you must remove the `^` in `package.json`. If the number of changes when running `npm run prettier` is large it may be worth opening a separate PR for only those.
|
||||||
3. Verify each update. If an update can be resolved, open a PR with your changes. If an update is incompatible, open an issue. Check the [`dependencies` label](https://github.com/CesiumGS/cesium/issues?q=is%3Aissue+is%3Aopen+label%3Adependencies) for any open issues pinning versions.
|
3. Verify each update. If an update can be resolved, open a PR with your changes. If an update is incompatible, open an issue. Check the [`dependencies` label](https://github.com/CesiumGS/cesium/issues?q=is%3Aissue+is%3Aopen+label%3Adependencies) for any open issues pinning versions.
|
||||||
4. Check the [`priority - next release` issues and PRs](https://github.com/CesiumGS/cesium/labels/priority%20-%20next%20release). If there are any outstanding items, post a message to the `CesiumJS` channel in Teams to figure out what needs to be addressed before we can release.
|
4. Check the [`priority - next release` issues and PRs](https://github.com/CesiumGS/cesium/labels/priority%20-%20next%20release). If there are any outstanding items, post a message to the `CesiumJS` channel in Teams to figure out what needs to be addressed before we can release.
|
||||||
5. Ensure you've generated valid [end to end testing snapshots](../TestingGuide/README.md) against a previous release tag with `npm run test-e2e-update`.
|
5. Check the [`remove in [this version number]` issues](https://github.com/CesiumGS/cesium/labels?q=remove) and open PRs to address any deprecations.
|
||||||
|
6. Ensure you've generated valid [end to end testing snapshots](../TestingGuide/README.md) against a previous release tag with `npm run test-e2e-update`.
|
||||||
|
7. Start thinking ahead for a good image for the release blog post in case you need to prepare any data, assets or examples.
|
||||||
|
|
||||||
## Release testing and packaging
|
## Release testing and packaging
|
||||||
|
|
||||||
|
|
@ -32,9 +34,9 @@ There is no one release manager; instead, [our community shares the responsibili
|
||||||
3. Make sure you are using the latest drivers for your video card.
|
3. Make sure you are using the latest drivers for your video card.
|
||||||
4. Ensure you've generated valid [end to end testing snapshots](../TestingGuide/README.md) against a previous release tag with `npm run test-e2e-update`.
|
4. Ensure you've generated valid [end to end testing snapshots](../TestingGuide/README.md) against a previous release tag with `npm run test-e2e-update`.
|
||||||
5. Pull down the latest `main` branch and run `npm install`.
|
5. Pull down the latest `main` branch and run `npm install`.
|
||||||
6. Update the Cesium ion demo token in `Ion.js` with a new token from the CesiumJS ion team account with read and geocode permissions. These tokens are named like this: `1.85 Release - Delete on November 1st, 2021`. Delete the token from 2 releases ago.
|
6. Update the Cesium ion demo token in `Ion.js` with a new token from the CesiumJS ion team account with `read` and `geocode` permissions. These tokens are named like this: `1.85 Release - Delete on November 1st, 2021`. Delete the token from 2 releases ago.
|
||||||
7. Update the ArcGIS Developer API key in `ArcGisMapService.js` with a new API key from the [CesiumJS ArcGIS Developer](https://links.esri.com/agol-sign-in) account. These API keys are named like this: `1.85 Release - Delete on November 1st, 2021`. Delete the API key from the last release.
|
7. Update the ArcGIS Developer API key in `ArcGisMapService.js` with a new API key from the [CesiumJS ArcGIS Developer](https://links.esri.com/agol-sign-in) account. These API keys are named like this: `1.85 Release - Delete on November 1st, 2021`. Delete the API key from the last release.
|
||||||
1. Sign in with LastPass
|
1. Sign in with Bitwarden
|
||||||
2. Go to Content at the top
|
2. Go to Content at the top
|
||||||
3. Click "New Item" -> Developer Credentials -> API Key credentials
|
3. Click "New Item" -> Developer Credentials -> API Key credentials
|
||||||
4. Set the expiration date to the day after the next release (no referrer URLs)
|
4. Set the expiration date to the day after the next release (no referrer URLs)
|
||||||
|
|
@ -59,7 +61,7 @@ There is no one release manager; instead, [our community shares the responsibili
|
||||||
20. Verify that the [documentation](http://localhost:8080/Build/Documentation/index.html) built correctly
|
20. Verify that the [documentation](http://localhost:8080/Build/Documentation/index.html) built correctly
|
||||||
21. Make sure [Hello World](http://localhost:8080/Apps/HelloWorld.html) loads.
|
21. Make sure [Hello World](http://localhost:8080/Apps/HelloWorld.html) loads.
|
||||||
22. Make sure [Cesium Viewer](http://localhost:8080/Apps/CesiumViewer/index.html) loads.
|
22. Make sure [Cesium Viewer](http://localhost:8080/Apps/CesiumViewer/index.html) loads.
|
||||||
23. Run [Sandcastle](http://localhost:8080/Apps/Sandcastle/index.html) on the browser of your choice (or multiple browsers if you are up for it). Switch to the `All` tab and spot test more complicated demos. Actually play with each of the buttons and sliders on each demo to ensure everything works as expected.
|
23. Run [Sandcastle](http://localhost:8080/Apps/Sandcastle2/index.html) on the browser of your choice (or multiple browsers if you are up for it). Remove the `Showcases` filter and spot test the more complicated demos. Actually play with each of the buttons and sliders on each demo to ensure everything works as expected.
|
||||||
24. If any of the above steps fail, post a message to the `CesiumJS` channel in Teams to figure out what needs to be fixed before we can release. **Do NOT proceed to the next step until issues are resolved.**
|
24. If any of the above steps fail, post a message to the `CesiumJS` channel in Teams to figure out what needs to be fixed before we can release. **Do NOT proceed to the next step until issues are resolved.**
|
||||||
25. Push your commits to main
|
25. Push your commits to main
|
||||||
- `git push`
|
- `git push`
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,66 +0,0 @@
|
||||||
{
|
|
||||||
"asset": {
|
|
||||||
"extras": {
|
|
||||||
"ion": {
|
|
||||||
"georeferenced": true,
|
|
||||||
"movable": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": "1.1"
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
"3DTILES_content_gltf": {
|
|
||||||
"extensionsRequired": [
|
|
||||||
"KHR_spz_gaussian_splats_compression"
|
|
||||||
],
|
|
||||||
"extensionsUsed": [
|
|
||||||
"KHR_spz_gaussian_splats_compression"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extensionsUsed": [
|
|
||||||
"3DTILES_content_gltf"
|
|
||||||
],
|
|
||||||
"geometricError": 14.76778488662848,
|
|
||||||
"root": {
|
|
||||||
"boundingVolume": {
|
|
||||||
"box": [
|
|
||||||
1.2307894825935364,
|
|
||||||
-0.6764951944351196,
|
|
||||||
186.9346923828125,
|
|
||||||
2.405353009700775,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
2.5862385034561157,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
6.484405517578125
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"content": {
|
|
||||||
"uri": "0/0.glb"
|
|
||||||
},
|
|
||||||
"geometricError": 0.0,
|
|
||||||
"refine": "REPLACE",
|
|
||||||
"transform": [
|
|
||||||
-0.14382095244935905,
|
|
||||||
0.9896037255571338,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
-0.7120020847592599,
|
|
||||||
-0.1034765889935951,
|
|
||||||
0.6945110703428119,
|
|
||||||
0.0,
|
|
||||||
0.6872907426519194,
|
|
||||||
0.09988524362332703,
|
|
||||||
0.7194820172674795,
|
|
||||||
0.0,
|
|
||||||
4391456.686631473,
|
|
||||||
638218.5788113032,
|
|
||||||
4566368.395700517,
|
|
||||||
1.0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
{"asset":{"version":"1.1"},"geometricError":631380.3810809468,"metadata":{"class":"tilesetInfo","properties":{"description":"Entire Earth description","name":"Entire Earth"}},"root":{"boundingVolume":{"region":[-3.141592653589793,-1.5707963267948966,3.141592653589793,1.5707963267948966,-130.93199157714844,80.01030731201172]},"children":[{"boundingVolume":{"region":[-3.141592653589793,-1.5707963267948966,0.0,1.5707963267948966,-76.75321197509766,67.20038604736328]},"content":{"uri":"0/{level}/{x}/{y}.glb"},"geometricError":157845.0952702367,"implicitTiling":{"availableLevels":2,"subdivisionScheme":"QUADTREE","subtreeLevels":7,"subtrees":{"uri":"0/{level}/{x}/{y}.subtree"}}},{"boundingVolume":{"region":[0.0,-1.5707963267948966,3.141592653589793,1.5707963267948966,-130.93199157714844,80.01030731201172]},"content":{"uri":"1/{level}/{x}/{y}.glb"},"geometricError":157845.0952702367,"implicitTiling":{"availableLevels":2,"subdivisionScheme":"QUADTREE","subtreeLevels":7,"subtrees":{"uri":"1/{level}/{x}/{y}.subtree"}}}],"geometricError":315690.1905404734,"refine":"REPLACE"},"schema":{"classes":{"subtreeTile":{"properties":{"boundingSphere":{"array":true,"componentType":"FLOAT64","count":4,"semantic":"TILE_BOUNDING_SPHERE","type":"SCALAR"},"horizonOcclusionPoint":{"componentType":"FLOAT64","semantic":"TILE_HORIZON_OCCLUSION_POINT","type":"VEC3"},"maximumHeight":{"componentType":"FLOAT64","semantic":"TILE_MAXIMUM_HEIGHT","type":"SCALAR"},"minimumHeight":{"componentType":"FLOAT64","semantic":"TILE_MINIMUM_HEIGHT","type":"SCALAR"}}},"tilesetInfo":{"properties":{"description":{"semantic":"DESCRIPTION","type":"STRING"},"name":{"semantic":"NAME","type":"STRING"}}}}}}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"license": [
|
"license": [
|
||||||
"BSD-3-Clause"
|
"BSD-3-Clause"
|
||||||
],
|
],
|
||||||
"version": "2.8.7",
|
"version": "2.8.8",
|
||||||
"url": "https://www.npmjs.com/package/@zip.js/zip.js"
|
"url": "https://www.npmjs.com/package/@zip.js/zip.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"license": [
|
"license": [
|
||||||
"Apache-2.0"
|
"Apache-2.0"
|
||||||
],
|
],
|
||||||
"version": "3.2.7",
|
"version": "3.3.0",
|
||||||
"url": "https://www.npmjs.com/package/dompurify",
|
"url": "https://www.npmjs.com/package/dompurify",
|
||||||
"notes": "dompurify is available as both MPL-2.0 OR Apache-2.0"
|
"notes": "dompurify is available as both MPL-2.0 OR Apache-2.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ export default [
|
||||||
"scripts/**/*.js",
|
"scripts/**/*.js",
|
||||||
"packages/sandcastle/scripts/**/*.js",
|
"packages/sandcastle/scripts/**/*.js",
|
||||||
"gulpfile.js",
|
"gulpfile.js",
|
||||||
|
"gulpfile.apps.js",
|
||||||
|
"gulpfile.makezip.js",
|
||||||
"server.js",
|
"server.js",
|
||||||
],
|
],
|
||||||
...configCesium.configs.node,
|
...configCesium.configs.node,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
import { join } from "path";
|
||||||
|
import { finished } from "stream/promises";
|
||||||
|
|
||||||
|
import gulp from "gulp";
|
||||||
|
import gulpReplace from "gulp-replace";
|
||||||
|
import { buildSandcastleApp } from "./scripts/buildSandcastle.js";
|
||||||
|
import { mkdirp } from "mkdirp";
|
||||||
|
import { bundleWorkers, defaultESBuildOptions } from "./scripts/build.js";
|
||||||
|
import { build as esbuild } from "esbuild";
|
||||||
|
|
||||||
|
const isProduction = process.env.PROD === "true";
|
||||||
|
|
||||||
|
// Print an esbuild warning
|
||||||
|
function printBuildWarning({ location, text }) {
|
||||||
|
const { column, file, line, lineText, suggestion } = location;
|
||||||
|
|
||||||
|
let message = `\n
|
||||||
|
> ${file}:${line}:${column}: warning: ${text}
|
||||||
|
${lineText}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (suggestion && suggestion !== "") {
|
||||||
|
message += `\n${suggestion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore `eval` warnings in third-party code we don't have control over
|
||||||
|
function handleBuildWarnings(result) {
|
||||||
|
for (const warning of result.warnings) {
|
||||||
|
if (
|
||||||
|
!warning.location.file.includes("protobufjs.js") &&
|
||||||
|
!warning.location.file.includes("Build/Cesium")
|
||||||
|
) {
|
||||||
|
printBuildWarning(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildLegacySandcastle() {
|
||||||
|
const streams = [];
|
||||||
|
let appStream = gulp.src(
|
||||||
|
[
|
||||||
|
"Apps/Sandcastle/**",
|
||||||
|
"!Apps/Sandcastle/load-cesium-es6.js",
|
||||||
|
"!Apps/Sandcastle/images/**",
|
||||||
|
"!Apps/Sandcastle/gallery/**.jpg",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
// Remove swap out ESM modules for the IIFE build
|
||||||
|
appStream = appStream
|
||||||
|
.pipe(
|
||||||
|
gulpReplace(
|
||||||
|
' <script type="module" src="../load-cesium-es6.js"></script>',
|
||||||
|
' <script src="../CesiumUnminified/Cesium.js"></script>\n' +
|
||||||
|
' <script>window.CESIUM_BASE_URL = "../CesiumUnminified/";</script>',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulpReplace(
|
||||||
|
' <script type="module" src="load-cesium-es6.js"></script>',
|
||||||
|
' <script src="CesiumUnminified/Cesium.js"></script>\n' +
|
||||||
|
' <script>window.CESIUM_BASE_URL = "CesiumUnminified/";</script>',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Fix relative paths for new location
|
||||||
|
.pipe(gulpReplace("../../../Build", ".."))
|
||||||
|
.pipe(gulpReplace("../../../Source", "../CesiumUnminified"))
|
||||||
|
.pipe(gulpReplace("../../Source", "."))
|
||||||
|
.pipe(gulpReplace("../../../ThirdParty", "./ThirdParty"))
|
||||||
|
.pipe(gulpReplace("../../ThirdParty", "./ThirdParty"))
|
||||||
|
.pipe(gulpReplace("../ThirdParty", "./ThirdParty"))
|
||||||
|
.pipe(gulpReplace("../Apps/Sandcastle", "."))
|
||||||
|
.pipe(gulpReplace("../../SampleData", "../SampleData"))
|
||||||
|
.pipe(
|
||||||
|
gulpReplace("../../Build/Documentation", "/learn/cesiumjs/ref-doc/"),
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest("Build/Sandcastle"));
|
||||||
|
} else {
|
||||||
|
// Remove swap out ESM modules for the IIFE build
|
||||||
|
appStream = appStream
|
||||||
|
.pipe(
|
||||||
|
gulpReplace(
|
||||||
|
' <script type="module" src="../load-cesium-es6.js"></script>',
|
||||||
|
' <script src="../../../Build/CesiumUnminified/Cesium.js"></script>\n' +
|
||||||
|
' <script>window.CESIUM_BASE_URL = "../../../Build/CesiumUnminified/";</script>',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulpReplace(
|
||||||
|
' <script type="module" src="load-cesium-es6.js"></script>',
|
||||||
|
' <script src="../../CesiumUnminified/Cesium.js"></script>\n' +
|
||||||
|
' <script>window.CESIUM_BASE_URL = "../../CesiumUnminified/";</script>',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Fix relative paths for new location
|
||||||
|
.pipe(gulpReplace("../../../Build", "../../.."))
|
||||||
|
.pipe(gulpReplace("../../Source", "../../../Source"))
|
||||||
|
.pipe(gulpReplace("../../ThirdParty", "../../../ThirdParty"))
|
||||||
|
.pipe(gulpReplace("../../SampleData", "../../../../Apps/SampleData"))
|
||||||
|
.pipe(gulpReplace("Build/Documentation", "Documentation"))
|
||||||
|
.pipe(gulp.dest("Build/Apps/Sandcastle"));
|
||||||
|
}
|
||||||
|
streams.push(appStream);
|
||||||
|
|
||||||
|
let imageStream = gulp.src(
|
||||||
|
["Apps/Sandcastle/gallery/**.jpg", "Apps/Sandcastle/images/**"],
|
||||||
|
{
|
||||||
|
base: "Apps/Sandcastle",
|
||||||
|
encoding: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (isProduction) {
|
||||||
|
imageStream = imageStream.pipe(gulp.dest("Build/Sandcastle"));
|
||||||
|
} else {
|
||||||
|
imageStream = imageStream.pipe(gulp.dest("Build/Apps/Sandcastle"));
|
||||||
|
}
|
||||||
|
streams.push(imageStream);
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
const fileStream = gulp
|
||||||
|
.src(["ThirdParty/**"], { encoding: false })
|
||||||
|
.pipe(gulp.dest("Build/Sandcastle/ThirdParty"));
|
||||||
|
streams.push(fileStream);
|
||||||
|
|
||||||
|
const dataStream = gulp
|
||||||
|
.src(["Apps/SampleData/**"], { encoding: false })
|
||||||
|
.pipe(gulp.dest("Build/Sandcastle/SampleData"));
|
||||||
|
streams.push(dataStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
let standaloneStream = gulp
|
||||||
|
.src(["Apps/Sandcastle/standalone.html"])
|
||||||
|
.pipe(gulpReplace("../../../", "."))
|
||||||
|
.pipe(
|
||||||
|
gulpReplace(
|
||||||
|
' <script type="module" src="load-cesium-es6.js"></script>',
|
||||||
|
' <script src="../CesiumUnminified/Cesium.js"></script>\n' +
|
||||||
|
' <script>window.CESIUM_BASE_URL = "../CesiumUnminified/";</script>',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(gulpReplace("../../Build", "."));
|
||||||
|
if (isProduction) {
|
||||||
|
standaloneStream = standaloneStream.pipe(gulp.dest("Build/Sandcastle"));
|
||||||
|
} else {
|
||||||
|
standaloneStream = standaloneStream.pipe(
|
||||||
|
gulp.dest("Build/Apps/Sandcastle"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
streams.push(standaloneStream);
|
||||||
|
|
||||||
|
return Promise.all(streams.map((s) => finished(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildCesiumViewer() {
|
||||||
|
const cesiumViewerOutputDirectory = isProduction
|
||||||
|
? "Build/CesiumViewer"
|
||||||
|
: "Build/Apps/CesiumViewer";
|
||||||
|
mkdirp.sync(cesiumViewerOutputDirectory);
|
||||||
|
|
||||||
|
const config = defaultESBuildOptions();
|
||||||
|
config.entryPoints = [
|
||||||
|
"Apps/CesiumViewer/CesiumViewer.js",
|
||||||
|
"Apps/CesiumViewer/CesiumViewer.css",
|
||||||
|
];
|
||||||
|
config.bundle = true; // Tree-shaking is enabled automatically
|
||||||
|
config.minify = true;
|
||||||
|
config.loader = {
|
||||||
|
".gif": "text",
|
||||||
|
".png": "text",
|
||||||
|
};
|
||||||
|
config.format = "iife";
|
||||||
|
// Configure Cesium base path to use built
|
||||||
|
config.define = { CESIUM_BASE_URL: `"."` };
|
||||||
|
config.outdir = cesiumViewerOutputDirectory;
|
||||||
|
config.outbase = "Apps/CesiumViewer";
|
||||||
|
config.logLevel = "error"; // print errors immediately, and collect warnings so we can filter out known ones
|
||||||
|
const result = await esbuild(config);
|
||||||
|
|
||||||
|
handleBuildWarnings(result);
|
||||||
|
|
||||||
|
await esbuild({
|
||||||
|
entryPoints: ["packages/widgets/Source/InfoBox/InfoBoxDescription.css"],
|
||||||
|
minify: true,
|
||||||
|
bundle: true,
|
||||||
|
loader: {
|
||||||
|
".gif": "text",
|
||||||
|
".png": "text",
|
||||||
|
},
|
||||||
|
outdir: join(cesiumViewerOutputDirectory, "Widgets"),
|
||||||
|
outbase: "packages/widgets/Source/",
|
||||||
|
});
|
||||||
|
|
||||||
|
await bundleWorkers({
|
||||||
|
minify: true,
|
||||||
|
removePragmas: true,
|
||||||
|
path: cesiumViewerOutputDirectory,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stream = gulp
|
||||||
|
.src(
|
||||||
|
[
|
||||||
|
"Apps/CesiumViewer/**",
|
||||||
|
"!Apps/CesiumViewer/Images",
|
||||||
|
"!Apps/CesiumViewer/**/*.js",
|
||||||
|
"!Apps/CesiumViewer/**/*.css",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulp.src(
|
||||||
|
[
|
||||||
|
"Build/Cesium/Assets/**",
|
||||||
|
"Build/Cesium/Workers/**",
|
||||||
|
"Build/Cesium/ThirdParty/**",
|
||||||
|
"Build/Cesium/Widgets/**",
|
||||||
|
"!Build/Cesium/Widgets/**/*.css",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
base: "Build/Cesium",
|
||||||
|
nodir: true,
|
||||||
|
encoding: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(gulp.src(["web.config"]))
|
||||||
|
.pipe(gulp.dest(cesiumViewerOutputDirectory));
|
||||||
|
|
||||||
|
await finished(stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buildSandcastle() {
|
||||||
|
return buildSandcastleApp({
|
||||||
|
outputToBuildDir: isProduction,
|
||||||
|
includeDevelopment: !isProduction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildApps = gulp.parallel(
|
||||||
|
buildCesiumViewer,
|
||||||
|
buildLegacySandcastle,
|
||||||
|
buildSandcastle,
|
||||||
|
);
|
||||||
431
gulpfile.js
431
gulpfile.js
|
|
@ -7,14 +7,9 @@ import { createRequire } from "module";
|
||||||
import { finished } from "stream/promises";
|
import { finished } from "stream/promises";
|
||||||
|
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import gulpTap from "gulp-tap";
|
|
||||||
import gulpZip from "gulp-zip";
|
|
||||||
import gulpRename from "gulp-rename";
|
|
||||||
import gulpReplace from "gulp-replace";
|
|
||||||
import { globby } from "globby";
|
import { globby } from "globby";
|
||||||
import open from "open";
|
import open from "open";
|
||||||
import { rimraf } from "rimraf";
|
import { rimraf } from "rimraf";
|
||||||
import { mkdirp } from "mkdirp";
|
|
||||||
import karma from "karma";
|
import karma from "karma";
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import typeScript from "typescript";
|
import typeScript from "typescript";
|
||||||
|
|
@ -29,7 +24,6 @@ import {
|
||||||
glslToJavaScript,
|
glslToJavaScript,
|
||||||
createCombinedSpecList,
|
createCombinedSpecList,
|
||||||
createJsHintOptions,
|
createJsHintOptions,
|
||||||
defaultESBuildOptions,
|
|
||||||
} from "./scripts/build.js";
|
} from "./scripts/build.js";
|
||||||
|
|
||||||
// Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine.
|
// Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine.
|
||||||
|
|
@ -55,7 +49,6 @@ function getWorkspaces(onlyDependencies = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const devDeployUrl = process.env.DEPLOYED_URL;
|
const devDeployUrl = process.env.DEPLOYED_URL;
|
||||||
const isProduction = process.env.PROD;
|
|
||||||
|
|
||||||
//Gulp doesn't seem to have a way to get the currently running tasks for setting
|
//Gulp doesn't seem to have a way to get the currently running tasks for setting
|
||||||
//per-task variables. We use the command line argument here to detect which task is being run.
|
//per-task variables. We use the command line argument here to detect which task is being run.
|
||||||
|
|
@ -92,34 +85,6 @@ const shaderFiles = [
|
||||||
"packages/engine/Source/ThirdParty/Shaders/*.glsl",
|
"packages/engine/Source/ThirdParty/Shaders/*.glsl",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Print an esbuild warning
|
|
||||||
function printBuildWarning({ location, text }) {
|
|
||||||
const { column, file, line, lineText, suggestion } = location;
|
|
||||||
|
|
||||||
let message = `\n
|
|
||||||
> ${file}:${line}:${column}: warning: ${text}
|
|
||||||
${lineText}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (suggestion && suggestion !== "") {
|
|
||||||
message += `\n${suggestion}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore `eval` warnings in third-party code we don't have control over
|
|
||||||
function handleBuildWarnings(result) {
|
|
||||||
for (const warning of result.warnings) {
|
|
||||||
if (
|
|
||||||
!warning.location.file.includes("protobufjs.js") &&
|
|
||||||
!warning.location.file.includes("Build/Cesium")
|
|
||||||
) {
|
|
||||||
printBuildWarning(warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function build() {
|
export async function build() {
|
||||||
// Configure build options from command line arguments.
|
// Configure build options from command line arguments.
|
||||||
const minify = argv.minify ?? false;
|
const minify = argv.minify ?? false;
|
||||||
|
|
@ -287,10 +252,6 @@ export async function buildTs() {
|
||||||
await createTypeScriptDefinitions();
|
await createTypeScriptDefinitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApps() {
|
|
||||||
return Promise.all([buildCesiumViewer(), buildSandcastle()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filesToClean = [
|
const filesToClean = [
|
||||||
"Source/Cesium.js",
|
"Source/Cesium.js",
|
||||||
"Source/Shaders/**/*.js",
|
"Source/Shaders/**/*.js",
|
||||||
|
|
@ -520,184 +481,6 @@ export const postversion = async function () {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes scripts from package.json files to ensure that
|
|
||||||
* they still work when run from within the ZIP file.
|
|
||||||
*
|
|
||||||
* @param {string} packageJsonPath The path to the package.json.
|
|
||||||
* @returns {WritableStream} A stream that writes to the updated package.json file.
|
|
||||||
*/
|
|
||||||
async function pruneScriptsForZip(packageJsonPath) {
|
|
||||||
// Read the contents of the file.
|
|
||||||
const contents = await readFile(packageJsonPath);
|
|
||||||
const contentsJson = JSON.parse(contents);
|
|
||||||
|
|
||||||
const scripts = contentsJson.scripts;
|
|
||||||
|
|
||||||
// Remove prepare step from package.json to avoid running "prepare" an extra time.
|
|
||||||
delete scripts.prepare;
|
|
||||||
|
|
||||||
// Remove build and transform tasks since they do not function as intended from within the release zip
|
|
||||||
delete scripts.build;
|
|
||||||
delete scripts["build-release"];
|
|
||||||
delete scripts["build-watch"];
|
|
||||||
delete scripts["build-ts"];
|
|
||||||
delete scripts["build-third-party"];
|
|
||||||
delete scripts["build-apps"];
|
|
||||||
delete scripts["build-sandcastle"];
|
|
||||||
delete scripts.clean;
|
|
||||||
delete scripts.cloc;
|
|
||||||
delete scripts["build-docs"];
|
|
||||||
delete scripts["build-docs-watch"];
|
|
||||||
delete scripts["make-zip"];
|
|
||||||
delete scripts.release;
|
|
||||||
delete scripts.prettier;
|
|
||||||
|
|
||||||
// Remove deploy tasks
|
|
||||||
delete scripts["deploy-status"];
|
|
||||||
delete scripts["deploy-set-version"];
|
|
||||||
delete scripts["website-release"];
|
|
||||||
|
|
||||||
// Set server tasks to use production flag
|
|
||||||
scripts["start"] = "node server.js --production";
|
|
||||||
scripts["start-public"] = "node server.js --public --production";
|
|
||||||
scripts["start-public"] = "node server.js --public --production";
|
|
||||||
scripts["test"] = "gulp test --production";
|
|
||||||
scripts["test-all"] = "gulp test --all --production";
|
|
||||||
scripts["test-webgl"] = "gulp test --include WebGL --production";
|
|
||||||
scripts["test-non-webgl"] = "gulp test --exclude WebGL --production";
|
|
||||||
scripts["test-webgl-validation"] = "gulp test --webglValidation --production";
|
|
||||||
scripts["test-webgl-stub"] = "gulp test --webglStub --production";
|
|
||||||
scripts["test-release"] = "gulp test --release --production";
|
|
||||||
|
|
||||||
// Write to a temporary package.json file.
|
|
||||||
const noPreparePackageJson = join(
|
|
||||||
dirname(packageJsonPath),
|
|
||||||
"package.noprepare.json",
|
|
||||||
);
|
|
||||||
await writeFile(noPreparePackageJson, JSON.stringify(contentsJson, null, 2));
|
|
||||||
|
|
||||||
return gulp.src(noPreparePackageJson, {
|
|
||||||
base: ".",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const makeZip = gulp.series(release, async function createZipFile() {
|
|
||||||
//For now we regenerate the JS glsl to force it to be unminified in the release zip
|
|
||||||
//See https://github.com/CesiumGS/cesium/pull/3106#discussion_r42793558 for discussion.
|
|
||||||
await glslToJavaScript(false, "Build/minifyShaders.state", "engine");
|
|
||||||
|
|
||||||
const packageJsonSrc = await pruneScriptsForZip("package.json");
|
|
||||||
const enginePackageJsonSrc = await pruneScriptsForZip(
|
|
||||||
"packages/engine/package.json",
|
|
||||||
);
|
|
||||||
const widgetsPackageJsonSrc = await pruneScriptsForZip(
|
|
||||||
"packages/widgets/package.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const src = gulp
|
|
||||||
.src("index.release.html")
|
|
||||||
.pipe(
|
|
||||||
gulpRename((file) => {
|
|
||||||
if (file.basename === "index.release") {
|
|
||||||
file.basename = "index";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(enginePackageJsonSrc)
|
|
||||||
.pipe(widgetsPackageJsonSrc)
|
|
||||||
.pipe(packageJsonSrc)
|
|
||||||
.pipe(
|
|
||||||
gulpRename((file) => {
|
|
||||||
if (file.basename === "package.noprepare") {
|
|
||||||
file.basename = "package";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulp.src(
|
|
||||||
[
|
|
||||||
"Build/Cesium/**",
|
|
||||||
"Build/CesiumUnminified/**",
|
|
||||||
"Build/Documentation/**",
|
|
||||||
"Build/Specs/**",
|
|
||||||
"Build/package.json",
|
|
||||||
"packages/engine/Build/**",
|
|
||||||
"packages/widgets/Build/**",
|
|
||||||
"!Build/Specs/e2e/**",
|
|
||||||
"!Build/InlineWorkers.js",
|
|
||||||
"!packages/engine/Build/Specs/**",
|
|
||||||
"!packages/widgets/Build/Specs/**",
|
|
||||||
"!packages/engine/Build/minifyShaders.state",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
encoding: false,
|
|
||||||
base: ".",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulp.src(
|
|
||||||
[
|
|
||||||
"Apps/**",
|
|
||||||
"Apps/Sandcastle/.jshintrc",
|
|
||||||
"packages/engine/index.js",
|
|
||||||
"packages/engine/index.d.ts",
|
|
||||||
"packages/engine/LICENSE.md",
|
|
||||||
"packages/engine/README.md",
|
|
||||||
"packages/engine/Source/**",
|
|
||||||
"packages/widgets/index.js",
|
|
||||||
"packages/widgets/index.d.ts",
|
|
||||||
"packages/widgets/LICENSE.md",
|
|
||||||
"packages/widgets/README.md",
|
|
||||||
"packages/widgets/Source/**",
|
|
||||||
"Source/**",
|
|
||||||
"Specs/**",
|
|
||||||
"ThirdParty/**",
|
|
||||||
"scripts/**",
|
|
||||||
"favicon.ico",
|
|
||||||
".prettierignore",
|
|
||||||
"eslint.config.js",
|
|
||||||
"gulpfile.js",
|
|
||||||
"server.js",
|
|
||||||
"index.cjs",
|
|
||||||
"LICENSE.md",
|
|
||||||
"CHANGES.md",
|
|
||||||
"README.md",
|
|
||||||
"web.config",
|
|
||||||
"!**/*.gitignore",
|
|
||||||
"!Specs/e2e/*-snapshots/**",
|
|
||||||
"!Apps/Sandcastle/gallery/development/**",
|
|
||||||
"!Apps/Sandcastle2/**",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
encoding: false,
|
|
||||||
base: ".",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulpTap(function (file) {
|
|
||||||
// Work around an issue with gulp-zip where archives generated on Windows do
|
|
||||||
// not properly have their directory executable mode set.
|
|
||||||
// see https://github.com/sindresorhus/gulp-zip/issues/64#issuecomment-205324031
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
file.stat.mode = parseInt("40777", 8);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(gulpZip(`Cesium-${version}.zip`))
|
|
||||||
.pipe(gulp.dest("."));
|
|
||||||
|
|
||||||
await finished(src);
|
|
||||||
|
|
||||||
rimraf.sync("./package.noprepare.json");
|
|
||||||
rimraf.sync("./packages/engine/package.noprepare.json");
|
|
||||||
rimraf.sync("./packages/widgets/package.noprepare.json");
|
|
||||||
|
|
||||||
return src;
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function deploySetVersion() {
|
export async function deploySetVersion() {
|
||||||
const buildVersion = argv.buildVersion;
|
const buildVersion = argv.buildVersion;
|
||||||
if (buildVersion) {
|
if (buildVersion) {
|
||||||
|
|
@ -1239,13 +1022,6 @@ function generateTypeScriptDefinitions(
|
||||||
"raiseEvent(...arguments: Parameters<Listener>): void;",
|
"raiseEvent(...arguments: Parameters<Listener>): void;",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wrap the source to actually be inside of a declared cesium module
|
|
||||||
// and add any workaround and private utility types.
|
|
||||||
source = `declare module "@${scope}/${workspaceName}" {
|
|
||||||
${source}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (importModules) {
|
if (importModules) {
|
||||||
let imports = "";
|
let imports = "";
|
||||||
Object.keys(importModules).forEach((workspace) => {
|
Object.keys(importModules).forEach((workspace) => {
|
||||||
|
|
@ -1259,6 +1035,13 @@ ${source}
|
||||||
source = imports + source;
|
source = imports + source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrap the source to actually be inside of a declared cesium module
|
||||||
|
// and add any workaround and private utility types.
|
||||||
|
source = `declare module "@${scope}/${workspaceName}" {
|
||||||
|
${source}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
// Write the final source file back out
|
// Write the final source file back out
|
||||||
writeFileSync(definitionsPath, source);
|
writeFileSync(definitionsPath, source);
|
||||||
|
|
||||||
|
|
@ -1630,203 +1413,3 @@ export async function buildThirdParty() {
|
||||||
|
|
||||||
return writeFile("ThirdParty.json", JSON.stringify(licenseJson, null, 2));
|
return writeFile("ThirdParty.json", JSON.stringify(licenseJson, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildSandcastle() {
|
|
||||||
const streams = [];
|
|
||||||
let appStream = gulp.src(
|
|
||||||
[
|
|
||||||
"Apps/Sandcastle/**",
|
|
||||||
"!Apps/Sandcastle/load-cesium-es6.js",
|
|
||||||
"!Apps/Sandcastle/images/**",
|
|
||||||
"!Apps/Sandcastle/gallery/**.jpg",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
encoding: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isProduction) {
|
|
||||||
// Remove swap out ESM modules for the IIFE build
|
|
||||||
appStream = appStream
|
|
||||||
.pipe(
|
|
||||||
gulpReplace(
|
|
||||||
' <script type="module" src="../load-cesium-es6.js"></script>',
|
|
||||||
' <script src="../CesiumUnminified/Cesium.js"></script>\n' +
|
|
||||||
' <script>window.CESIUM_BASE_URL = "../CesiumUnminified/";</script>',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulpReplace(
|
|
||||||
' <script type="module" src="load-cesium-es6.js"></script>',
|
|
||||||
' <script src="CesiumUnminified/Cesium.js"></script>\n' +
|
|
||||||
' <script>window.CESIUM_BASE_URL = "CesiumUnminified/";</script>',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// Fix relative paths for new location
|
|
||||||
.pipe(gulpReplace("../../../Build", ".."))
|
|
||||||
.pipe(gulpReplace("../../../Source", "../CesiumUnminified"))
|
|
||||||
.pipe(gulpReplace("../../Source", "."))
|
|
||||||
.pipe(gulpReplace("../../../ThirdParty", "./ThirdParty"))
|
|
||||||
.pipe(gulpReplace("../../ThirdParty", "./ThirdParty"))
|
|
||||||
.pipe(gulpReplace("../ThirdParty", "./ThirdParty"))
|
|
||||||
.pipe(gulpReplace("../Apps/Sandcastle", "."))
|
|
||||||
.pipe(gulpReplace("../../SampleData", "../SampleData"))
|
|
||||||
.pipe(
|
|
||||||
gulpReplace("../../Build/Documentation", "/learn/cesiumjs/ref-doc/"),
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest("Build/Sandcastle"));
|
|
||||||
} else {
|
|
||||||
// Remove swap out ESM modules for the IIFE build
|
|
||||||
appStream = appStream
|
|
||||||
.pipe(
|
|
||||||
gulpReplace(
|
|
||||||
' <script type="module" src="../load-cesium-es6.js"></script>',
|
|
||||||
' <script src="../../../Build/CesiumUnminified/Cesium.js"></script>\n' +
|
|
||||||
' <script>window.CESIUM_BASE_URL = "../../../Build/CesiumUnminified/";</script>',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulpReplace(
|
|
||||||
' <script type="module" src="load-cesium-es6.js"></script>',
|
|
||||||
' <script src="../../CesiumUnminified/Cesium.js"></script>\n' +
|
|
||||||
' <script>window.CESIUM_BASE_URL = "../../CesiumUnminified/";</script>',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// Fix relative paths for new location
|
|
||||||
.pipe(gulpReplace("../../../Build", "../../.."))
|
|
||||||
.pipe(gulpReplace("../../Source", "../../../Source"))
|
|
||||||
.pipe(gulpReplace("../../ThirdParty", "../../../ThirdParty"))
|
|
||||||
.pipe(gulpReplace("../../SampleData", "../../../../Apps/SampleData"))
|
|
||||||
.pipe(gulpReplace("Build/Documentation", "Documentation"))
|
|
||||||
.pipe(gulp.dest("Build/Apps/Sandcastle"));
|
|
||||||
}
|
|
||||||
streams.push(appStream);
|
|
||||||
|
|
||||||
let imageStream = gulp.src(
|
|
||||||
["Apps/Sandcastle/gallery/**.jpg", "Apps/Sandcastle/images/**"],
|
|
||||||
{
|
|
||||||
base: "Apps/Sandcastle",
|
|
||||||
encoding: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (isProduction) {
|
|
||||||
imageStream = imageStream.pipe(gulp.dest("Build/Sandcastle"));
|
|
||||||
} else {
|
|
||||||
imageStream = imageStream.pipe(gulp.dest("Build/Apps/Sandcastle"));
|
|
||||||
}
|
|
||||||
streams.push(imageStream);
|
|
||||||
|
|
||||||
if (isProduction) {
|
|
||||||
const fileStream = gulp
|
|
||||||
.src(["ThirdParty/**"], { encoding: false })
|
|
||||||
.pipe(gulp.dest("Build/Sandcastle/ThirdParty"));
|
|
||||||
streams.push(fileStream);
|
|
||||||
|
|
||||||
const dataStream = gulp
|
|
||||||
.src(["Apps/SampleData/**"], { encoding: false })
|
|
||||||
.pipe(gulp.dest("Build/Sandcastle/SampleData"));
|
|
||||||
streams.push(dataStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
let standaloneStream = gulp
|
|
||||||
.src(["Apps/Sandcastle/standalone.html"])
|
|
||||||
.pipe(gulpReplace("../../../", "."))
|
|
||||||
.pipe(
|
|
||||||
gulpReplace(
|
|
||||||
' <script type="module" src="load-cesium-es6.js"></script>',
|
|
||||||
' <script src="../CesiumUnminified/Cesium.js"></script>\n' +
|
|
||||||
' <script>window.CESIUM_BASE_URL = "../CesiumUnminified/";</script>',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(gulpReplace("../../Build", "."));
|
|
||||||
if (isProduction) {
|
|
||||||
standaloneStream = standaloneStream.pipe(gulp.dest("Build/Sandcastle"));
|
|
||||||
} else {
|
|
||||||
standaloneStream = standaloneStream.pipe(
|
|
||||||
gulp.dest("Build/Apps/Sandcastle"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
streams.push(standaloneStream);
|
|
||||||
|
|
||||||
return Promise.all(streams.map((s) => finished(s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildCesiumViewer() {
|
|
||||||
const cesiumViewerOutputDirectory = isProduction
|
|
||||||
? "Build/CesiumViewer"
|
|
||||||
: "Build/Apps/CesiumViewer";
|
|
||||||
mkdirp.sync(cesiumViewerOutputDirectory);
|
|
||||||
|
|
||||||
const config = defaultESBuildOptions();
|
|
||||||
config.entryPoints = [
|
|
||||||
"Apps/CesiumViewer/CesiumViewer.js",
|
|
||||||
"Apps/CesiumViewer/CesiumViewer.css",
|
|
||||||
];
|
|
||||||
config.bundle = true; // Tree-shaking is enabled automatically
|
|
||||||
config.minify = true;
|
|
||||||
config.loader = {
|
|
||||||
".gif": "text",
|
|
||||||
".png": "text",
|
|
||||||
};
|
|
||||||
config.format = "iife";
|
|
||||||
// Configure Cesium base path to use built
|
|
||||||
config.define = { CESIUM_BASE_URL: `"."` };
|
|
||||||
config.outdir = cesiumViewerOutputDirectory;
|
|
||||||
config.outbase = "Apps/CesiumViewer";
|
|
||||||
config.logLevel = "error"; // print errors immediately, and collect warnings so we can filter out known ones
|
|
||||||
const result = await esbuild(config);
|
|
||||||
|
|
||||||
handleBuildWarnings(result);
|
|
||||||
|
|
||||||
await esbuild({
|
|
||||||
entryPoints: ["packages/widgets/Source/InfoBox/InfoBoxDescription.css"],
|
|
||||||
minify: true,
|
|
||||||
bundle: true,
|
|
||||||
loader: {
|
|
||||||
".gif": "text",
|
|
||||||
".png": "text",
|
|
||||||
},
|
|
||||||
outdir: join(cesiumViewerOutputDirectory, "Widgets"),
|
|
||||||
outbase: "packages/widgets/Source/",
|
|
||||||
});
|
|
||||||
|
|
||||||
await bundleWorkers({
|
|
||||||
minify: true,
|
|
||||||
removePragmas: true,
|
|
||||||
path: cesiumViewerOutputDirectory,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stream = gulp
|
|
||||||
.src(
|
|
||||||
[
|
|
||||||
"Apps/CesiumViewer/**",
|
|
||||||
"!Apps/CesiumViewer/Images",
|
|
||||||
"!Apps/CesiumViewer/**/*.js",
|
|
||||||
"!Apps/CesiumViewer/**/*.css",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
encoding: false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
gulp.src(
|
|
||||||
[
|
|
||||||
"Build/Cesium/Assets/**",
|
|
||||||
"Build/Cesium/Workers/**",
|
|
||||||
"Build/Cesium/ThirdParty/**",
|
|
||||||
"Build/Cesium/Widgets/**",
|
|
||||||
"!Build/Cesium/Widgets/**/*.css",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
base: "Build/Cesium",
|
|
||||||
nodir: true,
|
|
||||||
encoding: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(gulp.src(["web.config"]))
|
|
||||||
.pipe(gulp.dest(cesiumViewerOutputDirectory));
|
|
||||||
|
|
||||||
await finished(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
import gulp from "gulp";
|
||||||
|
import gulpTap from "gulp-tap";
|
||||||
|
import gulpZip from "gulp-zip";
|
||||||
|
import gulpRename from "gulp-rename";
|
||||||
|
import { glslToJavaScript } from "./scripts/build.js";
|
||||||
|
import { release } from "./gulpfile.js";
|
||||||
|
import { readFile, writeFile } from "fs/promises";
|
||||||
|
import { rimraf } from "rimraf";
|
||||||
|
import { finished } from "stream/promises";
|
||||||
|
import { createRequire } from "module";
|
||||||
|
import { buildSandcastleApp } from "./scripts/buildSandcastle.js";
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const packageJson = require("./package.json");
|
||||||
|
let version = packageJson.version;
|
||||||
|
if (/\.0$/.test(version)) {
|
||||||
|
version = version.substring(0, version.length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes scripts from package.json files to ensure that
|
||||||
|
* they still work when run from within the ZIP file.
|
||||||
|
*
|
||||||
|
* @param {string} packageJsonPath The path to the package.json.
|
||||||
|
* @returns {WritableStream} A stream that writes to the updated package.json file.
|
||||||
|
*/
|
||||||
|
async function pruneScriptsForZip(packageJsonPath) {
|
||||||
|
// Read the contents of the file.
|
||||||
|
const contents = await readFile(packageJsonPath);
|
||||||
|
const contentsJson = JSON.parse(contents);
|
||||||
|
|
||||||
|
const scripts = contentsJson.scripts;
|
||||||
|
|
||||||
|
// Remove prepare step from package.json to avoid running "prepare" an extra time.
|
||||||
|
delete scripts.prepare;
|
||||||
|
|
||||||
|
// Remove build and transform tasks since they do not function as intended from within the release zip
|
||||||
|
delete scripts.build;
|
||||||
|
delete scripts["build-release"];
|
||||||
|
delete scripts["build-watch"];
|
||||||
|
delete scripts["build-ts"];
|
||||||
|
delete scripts["build-third-party"];
|
||||||
|
delete scripts["build-apps"];
|
||||||
|
delete scripts["build-sandcastle"];
|
||||||
|
delete scripts.clean;
|
||||||
|
delete scripts.cloc;
|
||||||
|
delete scripts["build-docs"];
|
||||||
|
delete scripts["build-docs-watch"];
|
||||||
|
delete scripts["make-zip"];
|
||||||
|
delete scripts.release;
|
||||||
|
delete scripts.prettier;
|
||||||
|
|
||||||
|
// Remove deploy tasks
|
||||||
|
delete scripts["deploy-status"];
|
||||||
|
delete scripts["deploy-set-version"];
|
||||||
|
delete scripts["website-release"];
|
||||||
|
|
||||||
|
// Set server tasks to use production flag
|
||||||
|
scripts["start"] = "node server.js --production";
|
||||||
|
scripts["start-public"] = "node server.js --public --production";
|
||||||
|
scripts["start-public"] = "node server.js --public --production";
|
||||||
|
scripts["test"] = "gulp test --production";
|
||||||
|
scripts["test-all"] = "gulp test --all --production";
|
||||||
|
scripts["test-webgl"] = "gulp test --include WebGL --production";
|
||||||
|
scripts["test-non-webgl"] = "gulp test --exclude WebGL --production";
|
||||||
|
scripts["test-webgl-validation"] = "gulp test --webglValidation --production";
|
||||||
|
scripts["test-webgl-stub"] = "gulp test --webglStub --production";
|
||||||
|
scripts["test-release"] = "gulp test --release --production";
|
||||||
|
|
||||||
|
// Write to a temporary package.json file.
|
||||||
|
const noPreparePackageJson = join(
|
||||||
|
dirname(packageJsonPath),
|
||||||
|
"package.noprepare.json",
|
||||||
|
);
|
||||||
|
await writeFile(noPreparePackageJson, JSON.stringify(contentsJson, null, 2));
|
||||||
|
|
||||||
|
return gulp.src(noPreparePackageJson, {
|
||||||
|
base: ".",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeZip = gulp.series(
|
||||||
|
release,
|
||||||
|
async function buildSandcastleStep() {
|
||||||
|
return buildSandcastleApp({
|
||||||
|
outputToBuildDir: false,
|
||||||
|
includeDevelopment: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async function createZipFile() {
|
||||||
|
//For now we regenerate the JS glsl to force it to be unminified in the release zip
|
||||||
|
//See https://github.com/CesiumGS/cesium/pull/3106#discussion_r42793558 for discussion.
|
||||||
|
await glslToJavaScript(false, "Build/minifyShaders.state", "engine");
|
||||||
|
|
||||||
|
const packageJsonSrc = await pruneScriptsForZip("package.json");
|
||||||
|
const enginePackageJsonSrc = await pruneScriptsForZip(
|
||||||
|
"packages/engine/package.json",
|
||||||
|
);
|
||||||
|
const widgetsPackageJsonSrc = await pruneScriptsForZip(
|
||||||
|
"packages/widgets/package.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
const src = gulp
|
||||||
|
.src("index.release.html")
|
||||||
|
.pipe(
|
||||||
|
gulpRename((file) => {
|
||||||
|
if (file.basename === "index.release") {
|
||||||
|
file.basename = "index";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(enginePackageJsonSrc)
|
||||||
|
.pipe(widgetsPackageJsonSrc)
|
||||||
|
.pipe(packageJsonSrc)
|
||||||
|
.pipe(
|
||||||
|
gulpRename((file) => {
|
||||||
|
if (file.basename === "package.noprepare") {
|
||||||
|
file.basename = "package";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulp.src(
|
||||||
|
[
|
||||||
|
"Build/Cesium/**",
|
||||||
|
"Build/CesiumUnminified/**",
|
||||||
|
"Build/Documentation/**",
|
||||||
|
"Build/Specs/**",
|
||||||
|
"Build/package.json",
|
||||||
|
"packages/engine/Build/**",
|
||||||
|
"packages/widgets/Build/**",
|
||||||
|
"!Build/Specs/e2e/**",
|
||||||
|
"!Build/InlineWorkers.js",
|
||||||
|
"!packages/engine/Build/Specs/**",
|
||||||
|
"!packages/widgets/Build/Specs/**",
|
||||||
|
"!packages/engine/Build/minifyShaders.state",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: false,
|
||||||
|
base: ".",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulp.src(
|
||||||
|
[
|
||||||
|
"Apps/**",
|
||||||
|
"Apps/Sandcastle/.jshintrc",
|
||||||
|
"packages/engine/index.js",
|
||||||
|
"packages/engine/index.d.ts",
|
||||||
|
"packages/engine/LICENSE.md",
|
||||||
|
"packages/engine/README.md",
|
||||||
|
"packages/engine/Source/**",
|
||||||
|
"packages/widgets/index.js",
|
||||||
|
"packages/widgets/index.d.ts",
|
||||||
|
"packages/widgets/LICENSE.md",
|
||||||
|
"packages/widgets/README.md",
|
||||||
|
"packages/widgets/Source/**",
|
||||||
|
"Source/**",
|
||||||
|
"Specs/**",
|
||||||
|
"ThirdParty/**",
|
||||||
|
"scripts/**",
|
||||||
|
"favicon.ico",
|
||||||
|
".prettierignore",
|
||||||
|
"eslint.config.js",
|
||||||
|
"gulpfile.js",
|
||||||
|
"server.js",
|
||||||
|
"index.cjs",
|
||||||
|
"LICENSE.md",
|
||||||
|
"CHANGES.md",
|
||||||
|
"README.md",
|
||||||
|
"web.config",
|
||||||
|
"!scripts/buildSandcastle.js",
|
||||||
|
"!**/*.gitignore",
|
||||||
|
"!Specs/e2e/*-snapshots/**",
|
||||||
|
"!Apps/Sandcastle/gallery/development/**",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: false,
|
||||||
|
base: ".",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
gulpTap(function (file) {
|
||||||
|
// Work around an issue with gulp-zip where archives generated on Windows do
|
||||||
|
// not properly have their directory executable mode set.
|
||||||
|
// see https://github.com/sindresorhus/gulp-zip/issues/64#issuecomment-205324031
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
file.stat.mode = parseInt("40777", 8);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(gulpZip(`Cesium-${version}.zip`))
|
||||||
|
.pipe(gulp.dest("."));
|
||||||
|
|
||||||
|
await finished(src);
|
||||||
|
|
||||||
|
rimraf.sync("./package.noprepare.json");
|
||||||
|
rimraf.sync("./packages/engine/package.noprepare.json");
|
||||||
|
rimraf.sync("./packages/widgets/package.noprepare.json");
|
||||||
|
|
||||||
|
return src;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="Apps/Sandcastle/index.html">Sandcastle</a></td>
|
<td><a href="Apps/Sandcastle2/index.html">Sandcastle</a></td>
|
||||||
<td>
|
<td>
|
||||||
Cesium's live code editor and example gallery. Browse examples
|
Cesium's live code editor and example gallery. Browse examples
|
||||||
highlighting features of the Cesium API and edit and run them
|
highlighting features of the Cesium API and edit and run them
|
||||||
|
|
|
||||||
12
package.json
12
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cesium",
|
"name": "cesium",
|
||||||
"version": "1.134.1",
|
"version": "1.135.0",
|
||||||
"description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.",
|
"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/",
|
"homepage": "http://cesium.com/cesiumjs/",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
@ -51,8 +51,8 @@
|
||||||
"./Specs/**/*"
|
"./Specs/**/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cesium/engine": "^21.0.1",
|
"@cesium/engine": "^22.0.0",
|
||||||
"@cesium/widgets": "^13.2.1"
|
"@cesium/widgets": "^14.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cesium/eslint-config": "^12.0.0",
|
"@cesium/eslint-config": "^12.0.0",
|
||||||
|
|
@ -114,15 +114,15 @@
|
||||||
"build-watch": "gulp buildWatch",
|
"build-watch": "gulp buildWatch",
|
||||||
"build-ts": "gulp buildTs",
|
"build-ts": "gulp buildTs",
|
||||||
"build-third-party": "gulp buildThirdParty",
|
"build-third-party": "gulp buildThirdParty",
|
||||||
"build-apps": "gulp buildApps",
|
"build-apps": "gulp -f gulpfile.apps.js buildApps",
|
||||||
"build-sandcastle": "npm run build-app --workspace packages/sandcastle",
|
"build-sandcastle": "gulp -f gulpfile.apps.js buildSandcastle",
|
||||||
"clean": "gulp clean",
|
"clean": "gulp clean",
|
||||||
"cloc": "gulp cloc",
|
"cloc": "gulp cloc",
|
||||||
"coverage": "gulp coverage",
|
"coverage": "gulp coverage",
|
||||||
"build-docs": "gulp buildDocs",
|
"build-docs": "gulp buildDocs",
|
||||||
"build-docs-watch": "gulp buildDocsWatch",
|
"build-docs-watch": "gulp buildDocsWatch",
|
||||||
"eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet",
|
"eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet",
|
||||||
"make-zip": "gulp makeZip",
|
"make-zip": "gulp -f gulpfile.makezip.js makeZip",
|
||||||
"markdownlint": "markdownlint \"**/*.md\"",
|
"markdownlint": "markdownlint \"**/*.md\"",
|
||||||
"release": "gulp release",
|
"release": "gulp release",
|
||||||
"website-release": "gulp websiteRelease",
|
"website-release": "gulp websiteRelease",
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,30 @@ AxisAlignedBoundingBox.intersectPlane = function (box, plane) {
|
||||||
return Intersect.INTERSECTING;
|
return Intersect.INTERSECTING;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether two axis aligned bounding boxes intersect.
|
||||||
|
*
|
||||||
|
* @param {AxisAlignedBoundingBox} box first box
|
||||||
|
* @param {AxisAlignedBoundingBox} other second box
|
||||||
|
* @returns {boolean} <code>true</code> if the boxes intersect; otherwise, <code>false</code>.
|
||||||
|
*/
|
||||||
|
AxisAlignedBoundingBox.intersectAxisAlignedBoundingBox = function (box, other) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("box", box);
|
||||||
|
Check.defined("other", other);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
// This short circuits in favor of AABBs that do not intersect.
|
||||||
|
return (
|
||||||
|
box.minimum.x <= other.maximum.x &&
|
||||||
|
box.maximum.x >= other.minimum.x &&
|
||||||
|
box.minimum.y <= other.maximum.y &&
|
||||||
|
box.maximum.y >= other.minimum.y &&
|
||||||
|
box.minimum.z <= other.maximum.z &&
|
||||||
|
box.maximum.z >= other.minimum.z
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicates this AxisAlignedBoundingBox instance.
|
* Duplicates this AxisAlignedBoundingBox instance.
|
||||||
*
|
*
|
||||||
|
|
@ -245,6 +269,18 @@ AxisAlignedBoundingBox.prototype.intersectPlane = function (plane) {
|
||||||
return AxisAlignedBoundingBox.intersectPlane(this, plane);
|
return AxisAlignedBoundingBox.intersectPlane(this, plane);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether some other axis aligned bounding box intersects this box.
|
||||||
|
*
|
||||||
|
* @param {AxisAlignedBoundingBox} other The other axis aligned bounding box.
|
||||||
|
* @returns {boolean} <code>true</code> if the boxes intersect; otherwise, <code>false</code>.
|
||||||
|
*/
|
||||||
|
AxisAlignedBoundingBox.prototype.intersectAxisAlignedBoundingBox = function (
|
||||||
|
other,
|
||||||
|
) {
|
||||||
|
return AxisAlignedBoundingBox.intersectAxisAlignedBoundingBox(this, other);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares this AxisAlignedBoundingBox against the provided AxisAlignedBoundingBox componentwise and returns
|
* Compares this AxisAlignedBoundingBox against the provided AxisAlignedBoundingBox componentwise and returns
|
||||||
* <code>true</code> if they are equal, <code>false</code> otherwise.
|
* <code>true</code> if they are equal, <code>false</code> otherwise.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,932 @@
|
||||||
|
import BoundingSphere from "./BoundingSphere.js";
|
||||||
|
import Cartesian2 from "./Cartesian2.js";
|
||||||
|
import Cartesian3 from "./Cartesian3.js";
|
||||||
|
import Cesium3DTilesTerrainGeometryProcessor from "./Cesium3DTilesTerrainGeometryProcessor.js";
|
||||||
|
import CesiumMath from "./Math.js";
|
||||||
|
import Check from "./Check.js";
|
||||||
|
import defined from "./defined.js";
|
||||||
|
import DeveloperError from "./DeveloperError.js";
|
||||||
|
import Frozen from "./Frozen.js";
|
||||||
|
import Intersections2D from "./Intersections2D.js";
|
||||||
|
import OrientedBoundingBox from "./OrientedBoundingBox.js";
|
||||||
|
import Rectangle from "./Rectangle.js";
|
||||||
|
import TaskProcessor from "./TaskProcessor.js";
|
||||||
|
import TerrainData from "./TerrainData.js";
|
||||||
|
import TerrainEncoding from "./TerrainEncoding.js";
|
||||||
|
import TerrainMesh from "./TerrainMesh.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terrain data for a single tile where the terrain data is represented as a glb (binary glTF).
|
||||||
|
*
|
||||||
|
* @alias Cesium3DTilesTerrainData
|
||||||
|
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {Object.<string,*>} options.gltf The parsed glTF JSON.
|
||||||
|
* @param {number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
|
||||||
|
* @param {number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
|
||||||
|
* @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
|
||||||
|
* @param {OrientedBoundingBox} options.orientedBoundingBox An oriented bounding box containing all of the vertices in the mesh.
|
||||||
|
* @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh. If this point
|
||||||
|
* is below the horizon, the entire tile is assumed to be below the horizon as well.
|
||||||
|
* The point is expressed in ellipsoid-scaled coordinates.
|
||||||
|
* @param {number} options.skirtHeight The height of the skirt to add on the edges of the tile.
|
||||||
|
* @param {boolean} [options.requestVertexNormals=false] Indicates whether normals should be loaded.
|
||||||
|
* @param {boolean} [options.requestWaterMask=false] Indicates whether water mask data should be loaded.
|
||||||
|
* @param {Credit[]} [options.credits] Array of credits for this tile.
|
||||||
|
* @param {number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
|
||||||
|
* If a child's bit is set, geometry will be requested for that tile as well when it
|
||||||
|
* is needed. If the bit is cleared, the child tile is not requested and geometry is
|
||||||
|
* instead upsampled from the parent. The bit values are as follows:
|
||||||
|
* <table>
|
||||||
|
* <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
|
||||||
|
* <tr><td>0</td><td>1</td><td>Southwest</td></tr>
|
||||||
|
* <tr><td>1</td><td>2</td><td>Southeast</td></tr>
|
||||||
|
* <tr><td>2</td><td>4</td><td>Northwest</td></tr>
|
||||||
|
* <tr><td>3</td><td>8</td><td>Northeast</td></tr>
|
||||||
|
* </table>
|
||||||
|
* @param {Uint8Array} [options.waterMask] The buffer containing the water mask.
|
||||||
|
* @see TerrainData
|
||||||
|
* @see QuantizedMeshTerrainData
|
||||||
|
* @see HeightmapTerrainData
|
||||||
|
* @see GoogleEarthEnterpriseTerrainData
|
||||||
|
*/
|
||||||
|
function Cesium3DTilesTerrainData(options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug)
|
||||||
|
Check.defined("options.gltf", options.gltf);
|
||||||
|
Check.typeOf.number("options.minimumHeight", options.minimumHeight);
|
||||||
|
Check.typeOf.number("options.maximumHeight", options.maximumHeight);
|
||||||
|
Check.typeOf.object("options.boundingSphere", options.boundingSphere);
|
||||||
|
Check.typeOf.object(
|
||||||
|
"option.orientedBoundingBox",
|
||||||
|
options.orientedBoundingBox,
|
||||||
|
);
|
||||||
|
Check.typeOf.object(
|
||||||
|
"options.horizonOcclusionPoint",
|
||||||
|
options.horizonOcclusionPoint,
|
||||||
|
);
|
||||||
|
Check.typeOf.number("options.skirtHeight", options.skirtHeight);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
this._minimumHeight = options.minimumHeight;
|
||||||
|
this._maximumHeight = options.maximumHeight;
|
||||||
|
this._skirtHeight = options.skirtHeight;
|
||||||
|
this._boundingSphere = BoundingSphere.clone(
|
||||||
|
options.boundingSphere,
|
||||||
|
new BoundingSphere(),
|
||||||
|
);
|
||||||
|
this._orientedBoundingBox = OrientedBoundingBox.clone(
|
||||||
|
options.orientedBoundingBox,
|
||||||
|
new OrientedBoundingBox(),
|
||||||
|
);
|
||||||
|
this._horizonOcclusionPoint = Cartesian3.clone(
|
||||||
|
options.horizonOcclusionPoint,
|
||||||
|
new Cartesian3(),
|
||||||
|
);
|
||||||
|
this._hasVertexNormals = options.requestVertexNormals ?? false;
|
||||||
|
this._hasWaterMask = options.requestWaterMask ?? false;
|
||||||
|
this._hasWebMercatorT = true;
|
||||||
|
this._credits = options.credits;
|
||||||
|
this._childTileMask = options.childTileMask ?? 15;
|
||||||
|
this._gltf = options.gltf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {TerrainMesh|undefined}
|
||||||
|
*/
|
||||||
|
this._mesh = undefined;
|
||||||
|
|
||||||
|
this._waterMask = options.waterMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Cesium3DTilesTerrainData.prototype, {
|
||||||
|
/**
|
||||||
|
* An array of credits for this tile.
|
||||||
|
* @memberof Cesium3DTilesTerrainData.prototype
|
||||||
|
* @type {Credit[]|undefined}
|
||||||
|
*/
|
||||||
|
credits: {
|
||||||
|
get: function () {
|
||||||
|
return this._credits;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The water mask included in this terrain data, if any. A water mask is a rectangular
|
||||||
|
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
||||||
|
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
||||||
|
* @memberof Cesium3DTilesTerrainData.prototype
|
||||||
|
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
|
||||||
|
*/
|
||||||
|
waterMask: {
|
||||||
|
get: function () {
|
||||||
|
return this._waterMask;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the terrain height at a specified longitude and latitude, or undefined if the mesh is undefined.
|
||||||
|
*
|
||||||
|
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
|
||||||
|
* @param {number} longitude The longitude in radians.
|
||||||
|
* @param {number} latitude The latitude in radians.
|
||||||
|
* @returns {number|undefined} The terrain height at the specified position, or undefined if the mesh is undefined.
|
||||||
|
* If the position is outside the rectangle, this method will extrapolate the height,
|
||||||
|
* which is likely to be wildly incorrect for positions far outside the rectangle.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype.interpolateHeight = function (
|
||||||
|
rectangle,
|
||||||
|
longitude,
|
||||||
|
latitude,
|
||||||
|
) {
|
||||||
|
const mesh = this._mesh;
|
||||||
|
if (mesh === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = interpolateMeshHeight(mesh, rectangle, longitude, latitude);
|
||||||
|
return height;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given child tile is available, based on the
|
||||||
|
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
|
||||||
|
* to be one of the four children of this tile. If non-child tile coordinates are
|
||||||
|
* given, the availability of the southeast child tile is returned.
|
||||||
|
*
|
||||||
|
* @param {number} thisX The tile X coordinate of this (the parent) tile.
|
||||||
|
* @param {number} thisY The tile Y coordinate of this (the parent) tile.
|
||||||
|
* @param {number} childX The tile X coordinate of the child tile to check for availability.
|
||||||
|
* @param {number} childY The tile Y coordinate of the child tile to check for availability.
|
||||||
|
* @returns {boolean} True if the child tile is available; otherwise, false.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype.isChildAvailable = function (
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
childX,
|
||||||
|
childY,
|
||||||
|
) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.typeOf.number("thisX", thisX);
|
||||||
|
Check.typeOf.number("thisY", thisY);
|
||||||
|
Check.typeOf.number("childX", childX);
|
||||||
|
Check.typeOf.number("childY", childY);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
let bitNumber = 2; // northwest child
|
||||||
|
if (childX !== thisX * 2) {
|
||||||
|
++bitNumber; // east child
|
||||||
|
}
|
||||||
|
if (childY !== thisY * 2) {
|
||||||
|
bitNumber -= 2; // south child
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this._childTileMask & (1 << bitNumber)) !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMeshTaskName = "createVerticesFromCesium3DTilesTerrain";
|
||||||
|
const createMeshTaskProcessorNoThrottle = new TaskProcessor(createMeshTaskName);
|
||||||
|
const createMeshTaskProcessorThrottle = new TaskProcessor(
|
||||||
|
createMeshTaskName,
|
||||||
|
TerrainData.maximumAsynchronousTasks,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link TerrainMesh} from this terrain data.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
|
||||||
|
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.level The level of the tile for which to create the terrain data.
|
||||||
|
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
|
||||||
|
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
|
||||||
|
* @param {boolean} [options.throttle=true] If true, indicates that this operation will need to be retried if too many asynchronous mesh creations are already in progress.
|
||||||
|
* @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
|
||||||
|
* asynchronous mesh creations are already in progress and the operation should
|
||||||
|
* be retried later.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype.createMesh = function (options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug)
|
||||||
|
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
|
||||||
|
Check.typeOf.number("options.x", options.x);
|
||||||
|
Check.typeOf.number("options.y", options.y);
|
||||||
|
Check.typeOf.number("options.level", options.level);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const throttle = options.throttle ?? true;
|
||||||
|
const createMeshTaskProcessor = throttle
|
||||||
|
? createMeshTaskProcessorThrottle
|
||||||
|
: createMeshTaskProcessorNoThrottle;
|
||||||
|
|
||||||
|
const tilingScheme = options.tilingScheme;
|
||||||
|
const ellipsoid = tilingScheme.ellipsoid;
|
||||||
|
const x = options.x;
|
||||||
|
const y = options.y;
|
||||||
|
const level = options.level;
|
||||||
|
const rectangle = tilingScheme.tileXYToRectangle(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
level,
|
||||||
|
new Rectangle(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const gltf = this._gltf;
|
||||||
|
const verticesPromise = createMeshTaskProcessor.scheduleTask({
|
||||||
|
ellipsoid: ellipsoid,
|
||||||
|
rectangle: rectangle,
|
||||||
|
hasVertexNormals: this._hasVertexNormals,
|
||||||
|
hasWaterMask: this._hasWaterMask,
|
||||||
|
hasWebMercatorT: this._hasWebMercatorT,
|
||||||
|
gltf: gltf,
|
||||||
|
minimumHeight: this._minimumHeight,
|
||||||
|
maximumHeight: this._maximumHeight,
|
||||||
|
boundingSphere: this._boundingSphere,
|
||||||
|
orientedBoundingBox: this._orientedBoundingBox,
|
||||||
|
horizonOcclusionPoint: this._horizonOcclusionPoint,
|
||||||
|
skirtHeight: this._skirtHeight,
|
||||||
|
exaggeration: options.exaggeration,
|
||||||
|
exaggerationRelativeHeight: options.exaggerationRelativeHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defined(verticesPromise)) {
|
||||||
|
// Too many active requests. Postponed.
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
return Promise.resolve(verticesPromise).then(function (result) {
|
||||||
|
const taskResult = result;
|
||||||
|
|
||||||
|
// Need to re-clone and re-wrap all buffers and complex objects to put them back into their normal state
|
||||||
|
const encoding = TerrainEncoding.clone(
|
||||||
|
taskResult.encoding,
|
||||||
|
new TerrainEncoding(),
|
||||||
|
);
|
||||||
|
const vertices = new Float32Array(taskResult.verticesBuffer);
|
||||||
|
const vertexCount = vertices.length / encoding.stride;
|
||||||
|
const vertexCountWithoutSkirts = taskResult.vertexCountWithoutSkirts;
|
||||||
|
// For consistency with glTF spec, 16 bit index buffer can't contain 65535
|
||||||
|
const SizedIndexType = vertexCount <= 65535 ? Uint16Array : Uint32Array;
|
||||||
|
const indices = new SizedIndexType(taskResult.indicesBuffer);
|
||||||
|
const westIndices = new SizedIndexType(taskResult.westIndicesBuffer);
|
||||||
|
const eastIndices = new SizedIndexType(taskResult.eastIndicesBuffer);
|
||||||
|
const southIndices = new SizedIndexType(taskResult.southIndicesBuffer);
|
||||||
|
const northIndices = new SizedIndexType(taskResult.northIndicesBuffer);
|
||||||
|
const indexCountWithoutSkirts = taskResult.indexCountWithoutSkirts;
|
||||||
|
const minimumHeight = that._minimumHeight;
|
||||||
|
const maximumHeight = that._maximumHeight;
|
||||||
|
const center = Cartesian3.clone(encoding.center, new Cartesian3());
|
||||||
|
const boundingSphere = BoundingSphere.clone(
|
||||||
|
that._boundingSphere,
|
||||||
|
new BoundingSphere(),
|
||||||
|
);
|
||||||
|
const horizonOcclusionPoint = Cartesian3.clone(
|
||||||
|
that._horizonOcclusionPoint,
|
||||||
|
new Cartesian3(),
|
||||||
|
);
|
||||||
|
const orientedBoundingBox = OrientedBoundingBox.clone(
|
||||||
|
that._orientedBoundingBox,
|
||||||
|
new OrientedBoundingBox(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mesh = new TerrainMesh(
|
||||||
|
center,
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
indexCountWithoutSkirts,
|
||||||
|
vertexCountWithoutSkirts,
|
||||||
|
minimumHeight,
|
||||||
|
maximumHeight,
|
||||||
|
rectangle,
|
||||||
|
boundingSphere,
|
||||||
|
horizonOcclusionPoint,
|
||||||
|
encoding.stride,
|
||||||
|
orientedBoundingBox,
|
||||||
|
encoding,
|
||||||
|
westIndices,
|
||||||
|
southIndices,
|
||||||
|
eastIndices,
|
||||||
|
northIndices,
|
||||||
|
);
|
||||||
|
|
||||||
|
that._mesh = mesh;
|
||||||
|
|
||||||
|
return Promise.resolve(mesh);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link TerrainMesh} from this terrain data synchronously.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
|
||||||
|
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.level The level of the tile for which to create the terrain data.
|
||||||
|
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
|
||||||
|
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
|
||||||
|
* @returns {Promise.<TerrainMesh>} A promise for the terrain mesh.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype._createMeshSync = function (options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug)
|
||||||
|
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
|
||||||
|
Check.typeOf.number("options.x", options.x);
|
||||||
|
Check.typeOf.number("options.y", options.y);
|
||||||
|
Check.typeOf.number("options.level", options.level);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const tilingScheme = options.tilingScheme;
|
||||||
|
const ellipsoid = tilingScheme.ellipsoid;
|
||||||
|
const x = options.x;
|
||||||
|
const y = options.y;
|
||||||
|
const level = options.level;
|
||||||
|
const rectangle = tilingScheme.tileXYToRectangle(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
level,
|
||||||
|
new Rectangle(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const meshPromise = Cesium3DTilesTerrainGeometryProcessor.createMesh({
|
||||||
|
ellipsoid: ellipsoid,
|
||||||
|
rectangle: rectangle,
|
||||||
|
hasVertexNormals: this._hasVertexNormals,
|
||||||
|
hasWebMercatorT: this._hasWebMercatorT,
|
||||||
|
gltf: this._gltf,
|
||||||
|
minimumHeight: this._minimumHeight,
|
||||||
|
maximumHeight: this._maximumHeight,
|
||||||
|
boundingSphere: this._boundingSphere,
|
||||||
|
orientedBoundingBox: this._orientedBoundingBox,
|
||||||
|
horizonOcclusionPoint: this._horizonOcclusionPoint,
|
||||||
|
skirtHeight: this._skirtHeight,
|
||||||
|
exaggeration: options.exaggeration,
|
||||||
|
exaggerationRelativeHeight: options.exaggerationRelativeHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
return Promise.resolve(meshPromise).then(function (mesh) {
|
||||||
|
that._mesh = mesh;
|
||||||
|
return Promise.resolve(mesh);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsamples this terrain data for use by a descendant tile.
|
||||||
|
*
|
||||||
|
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
|
||||||
|
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisLevel The level of this tile in the tiling scheme.
|
||||||
|
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if createMesh has not been called yet or too many asynchronous upsample operations are in progress and the request has been deferred.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype.upsample = function (
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
) {
|
||||||
|
// mesh is not defined, so there are no UVs yet, so exit early
|
||||||
|
const mesh = this._mesh;
|
||||||
|
if (mesh === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSynchronous = false;
|
||||||
|
const upsampledTerrainData = upsampleMesh(
|
||||||
|
isSynchronous,
|
||||||
|
mesh,
|
||||||
|
this._skirtHeight,
|
||||||
|
this._credits,
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
);
|
||||||
|
return upsampledTerrainData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsamples this terrain data for use by a descendant tile synchronously.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
|
||||||
|
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisLevel The level of this tile in the tiling scheme.
|
||||||
|
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if createMesh has not been called yet.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype._upsampleSync = function (
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
) {
|
||||||
|
// mesh is not defined, so there are no UVs yet, so exit early
|
||||||
|
const mesh = this._mesh;
|
||||||
|
if (mesh === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSynchronous = true;
|
||||||
|
const upsampledTerrainData = upsampleMesh(
|
||||||
|
isSynchronous,
|
||||||
|
mesh,
|
||||||
|
this._skirtHeight,
|
||||||
|
this._credits,
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
);
|
||||||
|
return upsampledTerrainData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
|
||||||
|
* terrain data. If this value is false, the data was obtained from some other source, such
|
||||||
|
* as by downloading it from a remote server. This method should return true for instances
|
||||||
|
* returned from a call to {@link Cesium3DTilesTerrainData#upsample}.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if this instance was created by upsampling; otherwise, false.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesTerrainData.prototype.wasCreatedByUpsampling = function () {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {TerrainMesh} options.terrainMesh The terrain mesh.
|
||||||
|
* @param {number} options.skirtHeight The height of the skirt to add on the edges of the tile.
|
||||||
|
* @param {Credit[]} [options.credits] Array of credits for this tile.
|
||||||
|
*/
|
||||||
|
function Cesium3DTilesUpsampleTerrainData(options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug)
|
||||||
|
Check.defined("options.terrainMesh", options.terrainMesh);
|
||||||
|
Check.defined("options.skirtHeight", options.skirtHeight);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
this._mesh = options.terrainMesh;
|
||||||
|
this._skirtHeight = options.skirtHeight;
|
||||||
|
this._credits = options.credits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link TerrainMesh} from this terrain data.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
|
||||||
|
* @param {number} options.x The X coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.y The Y coordinate of the tile for which to create the terrain data.
|
||||||
|
* @param {number} options.level The level of the tile for which to create the terrain data.
|
||||||
|
* @param {number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
|
||||||
|
* @param {number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
|
||||||
|
* @param {boolean} [options.throttle=true] If true, indicates that this operation will need to be retried if too many asynchronous mesh creations are already in progress.
|
||||||
|
* @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many asynchronous mesh creations are already in progress and the operation should be retried later.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype.createMesh = function (options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.typeOf.object("options.tilingScheme", options.tilingScheme);
|
||||||
|
Check.typeOf.number("options.x", options.x);
|
||||||
|
Check.typeOf.number("options.y", options.y);
|
||||||
|
Check.typeOf.number("options.level", options.level);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
return Promise.resolve(this._mesh);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsamples this terrain data for use by a descendant tile.
|
||||||
|
*
|
||||||
|
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
|
||||||
|
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisLevel The level of this tile in the tiling scheme.
|
||||||
|
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if too many asynchronous upsample operations are in progress and the request has been deferred.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype.upsample = function (
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
) {
|
||||||
|
const isSynchronous = false;
|
||||||
|
const upsampledTerrainData = upsampleMesh(
|
||||||
|
isSynchronous,
|
||||||
|
this._mesh,
|
||||||
|
this._skirtHeight,
|
||||||
|
this._credits,
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
);
|
||||||
|
return upsampledTerrainData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsamples this terrain data for use by a descendant tile synchronously.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
|
||||||
|
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisLevel The level of this tile in the tiling scheme.
|
||||||
|
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @returns {Promise.<TerrainData>} A promise for upsampled terrain data for the descendant tile.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype._upsampleSync = function (
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
) {
|
||||||
|
const isSynchronous = true;
|
||||||
|
return upsampleMesh(
|
||||||
|
isSynchronous,
|
||||||
|
this._mesh,
|
||||||
|
this._skirtHeight,
|
||||||
|
this._credits,
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the terrain height at a specified longitude and latitude.
|
||||||
|
*
|
||||||
|
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
|
||||||
|
* @param {number} longitude The longitude in radians.
|
||||||
|
* @param {number} latitude The latitude in radians.
|
||||||
|
* @returns {number} The terrain height at the specified position. If the position is outside the rectangle, this method will extrapolate the height, which is likely to be wildly incorrect for positions far outside the rectangle.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype.interpolateHeight = function (
|
||||||
|
rectangle,
|
||||||
|
longitude,
|
||||||
|
latitude,
|
||||||
|
) {
|
||||||
|
const mesh = this._mesh;
|
||||||
|
const height = interpolateMeshHeight(mesh, rectangle, longitude, latitude);
|
||||||
|
return height;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
|
||||||
|
* terrain data. If this value is false, the data was obtained from some other source, such
|
||||||
|
* as by downloading it from a remote server. This method should return true for instances
|
||||||
|
* returned from a call to {@link TerrainData#upsample}.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if this instance was created by upsampling; otherwise, false.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype.wasCreatedByUpsampling =
|
||||||
|
function () {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given child tile is available, based on the
|
||||||
|
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
|
||||||
|
* to be one of the four children of this tile. If non-child tile coordinates are
|
||||||
|
* given, the availability of the southeast child tile is returned.
|
||||||
|
*
|
||||||
|
* @param {number} _thisX The tile X coordinate of this (the parent) tile.
|
||||||
|
* @param {number} _thisY The tile Y coordinate of this (the parent) tile.
|
||||||
|
* @param {number} _childX The tile X coordinate of the child tile to check for availability.
|
||||||
|
* @param {number} _childY The tile Y coordinate of the child tile to check for availability.
|
||||||
|
* @returns {boolean} True if the child tile is available; otherwise, false.
|
||||||
|
*/
|
||||||
|
Cesium3DTilesUpsampleTerrainData.prototype.isChildAvailable = function (
|
||||||
|
_thisX,
|
||||||
|
_thisY,
|
||||||
|
_childX,
|
||||||
|
_childY,
|
||||||
|
) {
|
||||||
|
// upsample tiles are dynamic so they don't have children
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperties(Cesium3DTilesUpsampleTerrainData.prototype, {
|
||||||
|
/**
|
||||||
|
* An array of credits for this tile.
|
||||||
|
* @memberof Cesium3DTilesUpsampleTerrainData.prototype
|
||||||
|
* @type {Credit[]|undefined}
|
||||||
|
*/
|
||||||
|
credits: {
|
||||||
|
get: function () {
|
||||||
|
return this._credits;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The water mask included in this terrain data, if any. A water mask is a rectangular
|
||||||
|
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
||||||
|
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
||||||
|
* @memberof Cesium3DTilesUpsampleTerrainData.prototype
|
||||||
|
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
|
||||||
|
*/
|
||||||
|
waterMask: {
|
||||||
|
get: function () {
|
||||||
|
// Note: watermask not needed because there's a fallback in another file that checks for ancestor tile water mask
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsampleTaskProcessor = new TaskProcessor(
|
||||||
|
"upsampleVerticesFromCesium3DTilesTerrain",
|
||||||
|
TerrainData.maximumAsynchronousTasks,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsamples this terrain data for use by a descendant tile.
|
||||||
|
* @private
|
||||||
|
* @param {boolean} synchronous
|
||||||
|
* @param {TerrainMesh} thisMesh The mesh that is being upsampled
|
||||||
|
* @param {number} thisSkirtHeight The mesh's skirt height
|
||||||
|
* @param {Credit[]|undefined} credits The credits
|
||||||
|
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
|
||||||
|
* @param {number} thisX The X coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisY The Y coordinate of this tile in the tiling scheme.
|
||||||
|
* @param {number} thisLevel The level of this tile in the tiling scheme.
|
||||||
|
* @param {number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @param {number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
|
||||||
|
* @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile, or undefined if too many asynchronous upsample operations are in progress and the request has been deferred.
|
||||||
|
*/
|
||||||
|
function upsampleMesh(
|
||||||
|
synchronous,
|
||||||
|
thisMesh,
|
||||||
|
thisSkirtHeight,
|
||||||
|
credits,
|
||||||
|
tilingScheme,
|
||||||
|
thisX,
|
||||||
|
thisY,
|
||||||
|
thisLevel,
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
) {
|
||||||
|
//>>includeStart('debug', pragmas.debug)
|
||||||
|
Check.typeOf.bool("synchronous", synchronous);
|
||||||
|
Check.typeOf.object("thisMesh", thisMesh);
|
||||||
|
Check.typeOf.number("thisSkirtHeight", thisSkirtHeight);
|
||||||
|
Check.typeOf.object("tilingScheme", tilingScheme);
|
||||||
|
Check.typeOf.number("thisX", thisX);
|
||||||
|
Check.typeOf.number("thisY", thisY);
|
||||||
|
Check.typeOf.number("thisLevel", thisLevel);
|
||||||
|
Check.typeOf.number("descendantX", descendantX);
|
||||||
|
Check.typeOf.number("descendantY", descendantY);
|
||||||
|
Check.typeOf.number("descendantLevel", descendantLevel);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const levelDifference = descendantLevel - thisLevel;
|
||||||
|
if (levelDifference > 1) {
|
||||||
|
throw new DeveloperError(
|
||||||
|
"Upsampling through more than one level at a time is not currently supported.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsampleSkirtHeight = thisSkirtHeight * 0.5;
|
||||||
|
const isEastChild = thisX * 2 !== descendantX;
|
||||||
|
const isNorthChild = thisY * 2 === descendantY;
|
||||||
|
const upsampleRectangle = tilingScheme.tileXYToRectangle(
|
||||||
|
descendantX,
|
||||||
|
descendantY,
|
||||||
|
descendantLevel,
|
||||||
|
new Rectangle(),
|
||||||
|
);
|
||||||
|
const ellipsoid = tilingScheme.ellipsoid;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
isEastChild: isEastChild,
|
||||||
|
isNorthChild: isNorthChild,
|
||||||
|
rectangle: upsampleRectangle,
|
||||||
|
ellipsoid: ellipsoid,
|
||||||
|
skirtHeight: upsampleSkirtHeight,
|
||||||
|
parentVertices: thisMesh.vertices,
|
||||||
|
parentIndices: thisMesh.indices,
|
||||||
|
parentVertexCountWithoutSkirts: thisMesh.vertexCountWithoutSkirts,
|
||||||
|
parentIndexCountWithoutSkirts: thisMesh.indexCountWithoutSkirts,
|
||||||
|
parentMinimumHeight: thisMesh.minimumHeight,
|
||||||
|
parentMaximumHeight: thisMesh.maximumHeight,
|
||||||
|
parentEncoding: thisMesh.encoding,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (synchronous) {
|
||||||
|
const upsampledMesh =
|
||||||
|
Cesium3DTilesTerrainGeometryProcessor.upsampleMesh(options);
|
||||||
|
|
||||||
|
const upsampledTerrainData = new Cesium3DTilesUpsampleTerrainData({
|
||||||
|
terrainMesh: upsampledMesh,
|
||||||
|
skirtHeight: upsampleSkirtHeight,
|
||||||
|
credits: credits,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(upsampledTerrainData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsamplePromise = upsampleTaskProcessor.scheduleTask(options);
|
||||||
|
|
||||||
|
if (upsamplePromise === undefined) {
|
||||||
|
// Postponed
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return upsamplePromise.then(function (taskResult) {
|
||||||
|
// Need to re-clone and re-wrap all buffers and complex objects to put them back into their normal state
|
||||||
|
const encoding = TerrainEncoding.clone(
|
||||||
|
taskResult.encoding,
|
||||||
|
new TerrainEncoding(),
|
||||||
|
);
|
||||||
|
const stride = encoding.stride;
|
||||||
|
const vertices = new Float32Array(taskResult.verticesBuffer);
|
||||||
|
const vertexCount = vertices.length / stride;
|
||||||
|
const vertexCountWithoutSkirts = taskResult.vertexCountWithoutSkirts;
|
||||||
|
// For consistency with glTF spec, 16 bit index buffer can't contain 65535
|
||||||
|
const SizedIndexType = vertexCount <= 65535 ? Uint16Array : Uint32Array;
|
||||||
|
const indices = new SizedIndexType(taskResult.indicesBuffer);
|
||||||
|
const westIndices = new SizedIndexType(taskResult.westIndicesBuffer);
|
||||||
|
const eastIndices = new SizedIndexType(taskResult.eastIndicesBuffer);
|
||||||
|
const southIndices = new SizedIndexType(taskResult.southIndicesBuffer);
|
||||||
|
const northIndices = new SizedIndexType(taskResult.northIndicesBuffer);
|
||||||
|
const indexCountWithoutSkirts = taskResult.indexCountWithoutSkirts;
|
||||||
|
const minimumHeight = taskResult.minimumHeight;
|
||||||
|
const maximumHeight = taskResult.maximumHeight;
|
||||||
|
const center = Cartesian3.clone(encoding.center, new Cartesian3());
|
||||||
|
const boundingSphere = BoundingSphere.clone(
|
||||||
|
taskResult.boundingSphere,
|
||||||
|
new BoundingSphere(),
|
||||||
|
);
|
||||||
|
const horizonOcclusionPoint = Cartesian3.clone(
|
||||||
|
taskResult.horizonOcclusionPoint,
|
||||||
|
new Cartesian3(),
|
||||||
|
);
|
||||||
|
const orientedBoundingBox = OrientedBoundingBox.clone(
|
||||||
|
taskResult.orientedBoundingBox,
|
||||||
|
new OrientedBoundingBox(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const upsampledMesh = new TerrainMesh(
|
||||||
|
center,
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
indexCountWithoutSkirts,
|
||||||
|
vertexCountWithoutSkirts,
|
||||||
|
minimumHeight,
|
||||||
|
maximumHeight,
|
||||||
|
upsampleRectangle,
|
||||||
|
boundingSphere,
|
||||||
|
horizonOcclusionPoint,
|
||||||
|
stride,
|
||||||
|
orientedBoundingBox,
|
||||||
|
encoding,
|
||||||
|
westIndices,
|
||||||
|
southIndices,
|
||||||
|
eastIndices,
|
||||||
|
northIndices,
|
||||||
|
);
|
||||||
|
|
||||||
|
const upsampledTerrainData = new Cesium3DTilesUpsampleTerrainData({
|
||||||
|
terrainMesh: upsampledMesh,
|
||||||
|
skirtHeight: upsampleSkirtHeight,
|
||||||
|
credits: credits,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(upsampledTerrainData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const scratchUv0 = new Cartesian2();
|
||||||
|
const scratchUv1 = new Cartesian2();
|
||||||
|
const scratchUv2 = new Cartesian2();
|
||||||
|
const scratchBary = new Cartesian3();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the terrain height at a specified longitude and latitude. Returns 0.0 if the position is outside the mesh.
|
||||||
|
* @private
|
||||||
|
* @param {TerrainMesh} mesh The terrain mesh.
|
||||||
|
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
|
||||||
|
* @param {number} longitude The longitude in radians.
|
||||||
|
* @param {number} latitude The latitude in radians.
|
||||||
|
* @returns {number} The terrain height at the specified position. If the position is outside the rectangle, this method will extrapolate the height, which is likely to be wildly incorrect for positions far outside the rectangle.
|
||||||
|
*/
|
||||||
|
function interpolateMeshHeight(mesh, rectangle, longitude, latitude) {
|
||||||
|
const u = CesiumMath.clamp(
|
||||||
|
(longitude - rectangle.west) / rectangle.width,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const v = CesiumMath.clamp(
|
||||||
|
(latitude - rectangle.south) / rectangle.height,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { vertices, encoding, indices } = mesh;
|
||||||
|
|
||||||
|
for (let i = 0; i < mesh.indexCountWithoutSkirts; i += 3) {
|
||||||
|
const i0 = indices[i];
|
||||||
|
const i1 = indices[i + 1];
|
||||||
|
const i2 = indices[i + 2];
|
||||||
|
|
||||||
|
const uv0 = encoding.decodeTextureCoordinates(vertices, i0, scratchUv0);
|
||||||
|
const uv1 = encoding.decodeTextureCoordinates(vertices, i1, scratchUv1);
|
||||||
|
const uv2 = encoding.decodeTextureCoordinates(vertices, i2, scratchUv2);
|
||||||
|
|
||||||
|
const minU = Math.min(uv0.x, uv1.x, uv2.x);
|
||||||
|
const maxU = Math.max(uv0.x, uv1.x, uv2.x);
|
||||||
|
const minV = Math.min(uv0.y, uv1.y, uv2.y);
|
||||||
|
const maxV = Math.max(uv0.y, uv1.y, uv2.y);
|
||||||
|
|
||||||
|
if (u >= minU && u <= maxU && v >= minV && v <= maxV) {
|
||||||
|
const barycentric = Intersections2D.computeBarycentricCoordinates(
|
||||||
|
u,
|
||||||
|
v,
|
||||||
|
uv0.x,
|
||||||
|
uv0.y,
|
||||||
|
uv1.x,
|
||||||
|
uv1.y,
|
||||||
|
uv2.x,
|
||||||
|
uv2.y,
|
||||||
|
scratchBary,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
barycentric.x >= 0.0 &&
|
||||||
|
barycentric.y >= 0.0 &&
|
||||||
|
barycentric.z >= 0.0
|
||||||
|
) {
|
||||||
|
const h0 = encoding.decodeHeight(vertices, i0);
|
||||||
|
const h1 = encoding.decodeHeight(vertices, i1);
|
||||||
|
const h2 = encoding.decodeHeight(vertices, i2);
|
||||||
|
const height =
|
||||||
|
barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position does not lie in any triangle in this mesh.
|
||||||
|
// This should not happen often since we start by clamping to the rectangle.
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cesium3DTilesTerrainData;
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -210,6 +210,7 @@ GoogleEarthEnterpriseTerrainData.prototype.createMesh = function (options) {
|
||||||
result.vertexCountWithoutSkirts,
|
result.vertexCountWithoutSkirts,
|
||||||
result.minimumHeight,
|
result.minimumHeight,
|
||||||
result.maximumHeight,
|
result.maximumHeight,
|
||||||
|
rectangleScratch,
|
||||||
BoundingSphere.clone(result.boundingSphere3D),
|
BoundingSphere.clone(result.boundingSphere3D),
|
||||||
Cartesian3.clone(result.occludeePointInScaledSpace),
|
Cartesian3.clone(result.occludeePointInScaledSpace),
|
||||||
result.numberOfAttributes,
|
result.numberOfAttributes,
|
||||||
|
|
|
||||||
|
|
@ -94,16 +94,12 @@ import TerrainProvider from "./TerrainProvider.js";
|
||||||
* @see GoogleEarthEnterpriseTerrainData
|
* @see GoogleEarthEnterpriseTerrainData
|
||||||
*/
|
*/
|
||||||
function HeightmapTerrainData(options) {
|
function HeightmapTerrainData(options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
if (!defined(options) || !defined(options.buffer)) {
|
Check.typeOf.object("options.buffer", options.buffer);
|
||||||
throw new DeveloperError("options.buffer is required.");
|
Check.typeOf.number("options.width", options.width);
|
||||||
}
|
Check.typeOf.number("options.height", options.height);
|
||||||
if (!defined(options.width)) {
|
|
||||||
throw new DeveloperError("options.width is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.height)) {
|
|
||||||
throw new DeveloperError("options.height is required.");
|
|
||||||
}
|
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
this._buffer = options.buffer;
|
this._buffer = options.buffer;
|
||||||
|
|
@ -158,7 +154,7 @@ Object.defineProperties(HeightmapTerrainData.prototype, {
|
||||||
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
||||||
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
||||||
* @memberof HeightmapTerrainData.prototype
|
* @memberof HeightmapTerrainData.prototype
|
||||||
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|undefined}
|
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
|
||||||
*/
|
*/
|
||||||
waterMask: {
|
waterMask: {
|
||||||
get: function () {
|
get: function () {
|
||||||
|
|
@ -287,6 +283,7 @@ HeightmapTerrainData.prototype.createMesh = function (options) {
|
||||||
vertexCountWithoutSkirts,
|
vertexCountWithoutSkirts,
|
||||||
result.minimumHeight,
|
result.minimumHeight,
|
||||||
result.maximumHeight,
|
result.maximumHeight,
|
||||||
|
rectangle,
|
||||||
BoundingSphere.clone(result.boundingSphere3D),
|
BoundingSphere.clone(result.boundingSphere3D),
|
||||||
Cartesian3.clone(result.occludeePointInScaledSpace),
|
Cartesian3.clone(result.occludeePointInScaledSpace),
|
||||||
result.numberOfAttributes,
|
result.numberOfAttributes,
|
||||||
|
|
@ -393,6 +390,7 @@ HeightmapTerrainData.prototype._createMeshSync = function (options) {
|
||||||
vertexCountWithoutSkirts,
|
vertexCountWithoutSkirts,
|
||||||
result.minimumHeight,
|
result.minimumHeight,
|
||||||
result.maximumHeight,
|
result.maximumHeight,
|
||||||
|
rectangle,
|
||||||
result.boundingSphere3D,
|
result.boundingSphere3D,
|
||||||
result.occludeePointInScaledSpace,
|
result.occludeePointInScaledSpace,
|
||||||
result.encoding.stride,
|
result.encoding.stride,
|
||||||
|
|
@ -645,18 +643,10 @@ HeightmapTerrainData.prototype.isChildAvailable = function (
|
||||||
childY,
|
childY,
|
||||||
) {
|
) {
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
if (!defined(thisX)) {
|
Check.typeOf.number("thisX", thisX);
|
||||||
throw new DeveloperError("thisX is required.");
|
Check.typeOf.number("thisY", thisY);
|
||||||
}
|
Check.typeOf.number("childX", childX);
|
||||||
if (!defined(thisY)) {
|
Check.typeOf.number("childY", childY);
|
||||||
throw new DeveloperError("thisY is required.");
|
|
||||||
}
|
|
||||||
if (!defined(childX)) {
|
|
||||||
throw new DeveloperError("childX is required.");
|
|
||||||
}
|
|
||||||
if (!defined(childY)) {
|
|
||||||
throw new DeveloperError("childY is required.");
|
|
||||||
}
|
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
let bitNumber = 2; // northwest child
|
let bitNumber = 2; // northwest child
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,86 @@ IntersectionTests.rayEllipsoid = function (ray, ellipsoid) {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scratchRayIntervalX = new Interval();
|
||||||
|
const scratchRayIntervalY = new Interval();
|
||||||
|
const scratchRayIntervalZ = new Interval();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the intersection points of a ray with an axis-aligned bounding box. (axis-aligned in the same space as the ray)
|
||||||
|
*
|
||||||
|
* @param {Ray} ray The ray.
|
||||||
|
* @param {AxisAlignedBoundingBox} box The axis-aligned bounding box.
|
||||||
|
* @param {Interval | undefined} result The interval containing scalar points along the ray or undefined if there are no intersections.
|
||||||
|
*/
|
||||||
|
IntersectionTests.rayAxisAlignedBoundingBox = function (ray, box, result) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
if (!defined(ray)) {
|
||||||
|
throw new DeveloperError("ray is required.");
|
||||||
|
}
|
||||||
|
if (!defined(box)) {
|
||||||
|
throw new DeveloperError("box is required.");
|
||||||
|
}
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
if (!defined(result)) {
|
||||||
|
result = new Interval();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = rayIntervalAlongAABBAxis(
|
||||||
|
ray.origin.x,
|
||||||
|
ray.direction.x,
|
||||||
|
box.minimum.x,
|
||||||
|
box.maximum.x,
|
||||||
|
scratchRayIntervalX,
|
||||||
|
);
|
||||||
|
const ty = rayIntervalAlongAABBAxis(
|
||||||
|
ray.origin.y,
|
||||||
|
ray.direction.y,
|
||||||
|
box.minimum.y,
|
||||||
|
box.maximum.y,
|
||||||
|
scratchRayIntervalY,
|
||||||
|
);
|
||||||
|
const tz = rayIntervalAlongAABBAxis(
|
||||||
|
ray.origin.z,
|
||||||
|
ray.direction.z,
|
||||||
|
box.minimum.z,
|
||||||
|
box.maximum.z,
|
||||||
|
scratchRayIntervalZ,
|
||||||
|
);
|
||||||
|
|
||||||
|
result.start = tx.start > ty.start ? tx.start : ty.start; //Get Greatest Min
|
||||||
|
result.stop = tx.stop < ty.stop ? tx.stop : ty.stop; //Get Smallest Max
|
||||||
|
|
||||||
|
if (tx.start > ty.stop || ty.start > tx.stop) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.start > tz.stop || tz.start > result.stop) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tz.start > result.start) {
|
||||||
|
result.start = tz.start;
|
||||||
|
}
|
||||||
|
if (tz.stop < result.stop) {
|
||||||
|
result.stop = tz.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function rayIntervalAlongAABBAxis(origin, direction, min, max, result) {
|
||||||
|
result.start = (min - origin) / direction;
|
||||||
|
result.stop = (max - origin) / direction;
|
||||||
|
if (result.stop < result.start) {
|
||||||
|
const tmp = result.stop;
|
||||||
|
result.stop = result.start;
|
||||||
|
result.start = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function addWithCancellationCheck(left, right, tolerance) {
|
function addWithCancellationCheck(left, right, tolerance) {
|
||||||
const difference = left + right;
|
const difference = left + right;
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Resource from "./Resource.js";
|
||||||
|
|
||||||
let defaultTokenCredit;
|
let defaultTokenCredit;
|
||||||
const defaultAccessToken =
|
const defaultAccessToken =
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjNjI5ZTViNy0wY2FhLTQ0ZDUtYTIzMi0wMWEyMzZkYWYwYWYiLCJpZCI6MjU5LCJpYXQiOjE3NTkzNDcyNDZ9.xyOPig1igKFQvOTaXfTE0KQ7dU7jyn_c3OQPaQ1hEiI";
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhN2VkNDM5ZS1jMDk0LTQ3NDItOTM5ZS00MzU3M2M1MTc2ZTkiLCJpZCI6MjU5LCJpYXQiOjE3NjIxODg4MDB9.ZZG574sONzeHxsX8HJMaL_ZiGA3dh_HrOxL7DrKRcd4";
|
||||||
/**
|
/**
|
||||||
* Default settings for accessing the Cesium ion API.
|
* Default settings for accessing the Cesium ion API.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -92,52 +92,26 @@ import TerrainMesh from "./TerrainMesh.js";
|
||||||
* @see GoogleEarthEnterpriseTerrainData
|
* @see GoogleEarthEnterpriseTerrainData
|
||||||
*/
|
*/
|
||||||
function QuantizedMeshTerrainData(options) {
|
function QuantizedMeshTerrainData(options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
|
||||||
//>>includeStart('debug', pragmas.debug)
|
//>>includeStart('debug', pragmas.debug)
|
||||||
if (!defined(options) || !defined(options.quantizedVertices)) {
|
Check.typeOf.object("options.quantizedVertices", options.quantizedVertices);
|
||||||
throw new DeveloperError("options.quantizedVertices is required.");
|
Check.typeOf.object("options.indices", options.indices);
|
||||||
}
|
Check.typeOf.number("options.minimumHeight", options.minimumHeight);
|
||||||
if (!defined(options.indices)) {
|
Check.typeOf.number("options.maximumHeight", options.maximumHeight);
|
||||||
throw new DeveloperError("options.indices is required.");
|
Check.typeOf.object("options.boundingSphere", options.boundingSphere);
|
||||||
}
|
Check.typeOf.object(
|
||||||
if (!defined(options.minimumHeight)) {
|
"options.horizonOcclusionPoint",
|
||||||
throw new DeveloperError("options.minimumHeight is required.");
|
options.horizonOcclusionPoint,
|
||||||
}
|
);
|
||||||
if (!defined(options.maximumHeight)) {
|
Check.typeOf.object("options.westIndices", options.westIndices);
|
||||||
throw new DeveloperError("options.maximumHeight is required.");
|
Check.typeOf.object("options.southIndices", options.southIndices);
|
||||||
}
|
Check.typeOf.object("options.eastIndices", options.eastIndices);
|
||||||
if (!defined(options.maximumHeight)) {
|
Check.typeOf.object("options.northIndices", options.northIndices);
|
||||||
throw new DeveloperError("options.maximumHeight is required.");
|
Check.typeOf.number("options.westSkirtHeight", options.westSkirtHeight);
|
||||||
}
|
Check.typeOf.number("options.southSkirtHeight", options.southSkirtHeight);
|
||||||
if (!defined(options.boundingSphere)) {
|
Check.typeOf.number("options.eastSkirtHeight", options.eastSkirtHeight);
|
||||||
throw new DeveloperError("options.boundingSphere is required.");
|
Check.typeOf.number("options.northSkirtHeight", options.northSkirtHeight);
|
||||||
}
|
|
||||||
if (!defined(options.horizonOcclusionPoint)) {
|
|
||||||
throw new DeveloperError("options.horizonOcclusionPoint is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.westIndices)) {
|
|
||||||
throw new DeveloperError("options.westIndices is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.southIndices)) {
|
|
||||||
throw new DeveloperError("options.southIndices is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.eastIndices)) {
|
|
||||||
throw new DeveloperError("options.eastIndices is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.northIndices)) {
|
|
||||||
throw new DeveloperError("options.northIndices is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.westSkirtHeight)) {
|
|
||||||
throw new DeveloperError("options.westSkirtHeight is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.southSkirtHeight)) {
|
|
||||||
throw new DeveloperError("options.southSkirtHeight is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.eastSkirtHeight)) {
|
|
||||||
throw new DeveloperError("options.eastSkirtHeight is required.");
|
|
||||||
}
|
|
||||||
if (!defined(options.northSkirtHeight)) {
|
|
||||||
throw new DeveloperError("options.northSkirtHeight is required.");
|
|
||||||
}
|
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
this._quantizedVertices = options.quantizedVertices;
|
this._quantizedVertices = options.quantizedVertices;
|
||||||
|
|
@ -375,6 +349,7 @@ QuantizedMeshTerrainData.prototype.createMesh = function (options) {
|
||||||
vertexCountWithoutSkirts,
|
vertexCountWithoutSkirts,
|
||||||
minimumHeight,
|
minimumHeight,
|
||||||
maximumHeight,
|
maximumHeight,
|
||||||
|
rectangle,
|
||||||
boundingSphere,
|
boundingSphere,
|
||||||
occludeePointInScaledSpace,
|
occludeePointInScaledSpace,
|
||||||
stride,
|
stride,
|
||||||
|
|
@ -729,18 +704,10 @@ QuantizedMeshTerrainData.prototype.isChildAvailable = function (
|
||||||
childY,
|
childY,
|
||||||
) {
|
) {
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
if (!defined(thisX)) {
|
Check.typeOf.number("thisX", thisX);
|
||||||
throw new DeveloperError("thisX is required.");
|
Check.typeOf.number("thisY", thisY);
|
||||||
}
|
Check.typeOf.number("childX", childX);
|
||||||
if (!defined(thisY)) {
|
Check.typeOf.number("childY", childY);
|
||||||
throw new DeveloperError("thisY is required.");
|
|
||||||
}
|
|
||||||
if (!defined(childX)) {
|
|
||||||
throw new DeveloperError("childX is required.");
|
|
||||||
}
|
|
||||||
if (!defined(childY)) {
|
|
||||||
throw new DeveloperError("childY is required.");
|
|
||||||
}
|
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
let bitNumber = 2; // northwest child
|
let bitNumber = 2; // northwest child
|
||||||
|
|
|
||||||
|
|
@ -2025,6 +2025,12 @@ Resource._Implementations.createImage = function (
|
||||||
* Wrapper for createImageBitmap
|
* Wrapper for createImageBitmap
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Blob} blob The image blob.
|
||||||
|
* @param {object} options An object containing the following properties:
|
||||||
|
* @param {boolean} options.flipY Whether to flip the image Y axis.
|
||||||
|
* @param {boolean} options.premultiplyAlpha Whether to premultiply the alpha channel.
|
||||||
|
* @param {boolean} options.skipColorSpaceConversion Whether to skip color space conversion.
|
||||||
|
* @returns {Promise<ImageBitmap>} A promise that resolves to the created image bitmap.
|
||||||
*/
|
*/
|
||||||
Resource.createImageBitmapFromBlob = function (blob, options) {
|
Resource.createImageBitmapFromBlob = function (blob, options) {
|
||||||
Check.defined("options", options);
|
Check.defined("options", options);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import DeveloperError from "./DeveloperError.js";
|
||||||
* @see HeightmapTerrainData
|
* @see HeightmapTerrainData
|
||||||
* @see QuantizedMeshTerrainData
|
* @see QuantizedMeshTerrainData
|
||||||
* @see GoogleEarthEnterpriseTerrainData
|
* @see GoogleEarthEnterpriseTerrainData
|
||||||
|
* @see Cesium3DTilesTerrainData
|
||||||
*/
|
*/
|
||||||
function TerrainData() {
|
function TerrainData() {
|
||||||
DeveloperError.throwInstantiationError();
|
DeveloperError.throwInstantiationError();
|
||||||
|
|
@ -29,7 +30,7 @@ Object.defineProperties(TerrainData.prototype, {
|
||||||
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
|
||||||
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
|
||||||
* @memberof TerrainData.prototype
|
* @memberof TerrainData.prototype
|
||||||
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|undefined}
|
* @type {Uint8Array|HTMLImageElement|HTMLCanvasElement|ImageBitmap|undefined}
|
||||||
*/
|
*/
|
||||||
waterMask: {
|
waterMask: {
|
||||||
get: DeveloperError.throwInstantiationError,
|
get: DeveloperError.throwInstantiationError,
|
||||||
|
|
|
||||||
|
|
@ -76,32 +76,36 @@ function TerrainEncoding(
|
||||||
quantization = TerrainQuantization.NONE;
|
quantization = TerrainQuantization.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
|
// Scale and bias from [0,1] to [ENU min, ENU max]
|
||||||
|
// Also compute the inverse of the scale and bias
|
||||||
|
let st = Matrix4.fromScale(dimensions, matrix4Scratch);
|
||||||
|
st = Matrix4.setTranslation(st, minimum, st);
|
||||||
|
|
||||||
const translation = Cartesian3.negate(minimum, cartesian3Scratch);
|
let invSt = Matrix4.fromScale(
|
||||||
Matrix4.multiply(
|
Cartesian3.fromElements(
|
||||||
Matrix4.fromTranslation(translation, matrix4Scratch),
|
1.0 / dimensions.x,
|
||||||
toENU,
|
1.0 / dimensions.y,
|
||||||
toENU,
|
1.0 / dimensions.z,
|
||||||
|
cartesian3Scratch,
|
||||||
|
),
|
||||||
|
matrix4Scratch2,
|
||||||
|
);
|
||||||
|
invSt = Matrix4.multiplyByTranslation(
|
||||||
|
invSt,
|
||||||
|
Cartesian3.negate(minimum, cartesian3Scratch),
|
||||||
|
invSt,
|
||||||
);
|
);
|
||||||
|
|
||||||
const scale = cartesian3Scratch;
|
matrix = Matrix4.clone(fromENU, new Matrix4());
|
||||||
scale.x = 1.0 / dimensions.x;
|
let rtcOffset = Matrix4.getTranslation(fromENU, cartesian3Scratch);
|
||||||
scale.y = 1.0 / dimensions.y;
|
rtcOffset = Cartesian3.subtract(rtcOffset, center, cartesian3Scratch);
|
||||||
scale.z = 1.0 / dimensions.z;
|
matrix = Matrix4.setTranslation(matrix, rtcOffset, matrix);
|
||||||
Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch), toENU, toENU);
|
matrix = Matrix4.multiply(matrix, st, matrix);
|
||||||
|
|
||||||
matrix = Matrix4.clone(fromENU);
|
toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
|
||||||
Matrix4.setTranslation(matrix, Cartesian3.ZERO, matrix);
|
toENU = Matrix4.multiply(invSt, toENU, toENU);
|
||||||
|
|
||||||
fromENU = Matrix4.clone(fromENU, new Matrix4());
|
fromENU = Matrix4.multiply(fromENU, st, new Matrix4());
|
||||||
|
|
||||||
const translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch);
|
|
||||||
const scaleMatrix = Matrix4.fromScale(dimensions, matrix4Scratch2);
|
|
||||||
const st = Matrix4.multiply(translationMatrix, scaleMatrix, matrix4Scratch);
|
|
||||||
|
|
||||||
Matrix4.multiply(fromENU, st, fromENU);
|
|
||||||
Matrix4.multiply(matrix, st, matrix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -570,6 +574,23 @@ TerrainEncoding.prototype.getOctEncodedNormal = function (
|
||||||
return Cartesian2.fromElements(x, y, result);
|
return Cartesian2.fromElements(x, y, result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Float32Array} buffer
|
||||||
|
* @param {number} index
|
||||||
|
* @param {Cartesian3} result
|
||||||
|
* @returns {Cartesian3}
|
||||||
|
*/
|
||||||
|
TerrainEncoding.prototype.decodeNormal = function (buffer, index, result) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.typeOf.object("buffer", buffer);
|
||||||
|
Check.typeOf.number("index", index);
|
||||||
|
Check.typeOf.object("result", result);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const bufferIndex = (index = index * this.stride + this._offsetVertexNormal);
|
||||||
|
return AttributeCompression.octDecodeFloat(buffer[bufferIndex], result);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a geodetic surface normal from the vertex buffer.
|
* Decode a geodetic surface normal from the vertex buffer.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,14 @@
|
||||||
|
import SceneMode from "../Scene/SceneMode.js";
|
||||||
|
import Cartesian3 from "./Cartesian3.js";
|
||||||
|
import Cartographic from "./Cartographic.js";
|
||||||
|
import defined from "./defined.js";
|
||||||
|
import Ellipsoid from "./Ellipsoid.js";
|
||||||
|
import Matrix4 from "./Matrix4.js";
|
||||||
|
import OrientedBoundingBox from "./OrientedBoundingBox.js";
|
||||||
|
import TerrainPicker from "./TerrainPicker.js";
|
||||||
|
import Transforms from "./Transforms.js";
|
||||||
|
import VerticalExaggeration from "./VerticalExaggeration.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mesh plus related metadata for a single tile of terrain. Instances of this type are
|
* A mesh plus related metadata for a single tile of terrain. Instances of this type are
|
||||||
* usually created from raw {@link TerrainData}.
|
* usually created from raw {@link TerrainData}.
|
||||||
|
|
@ -15,6 +26,7 @@
|
||||||
* @param {number} vertexCountWithoutSkirts The vertex count of the mesh not including skirts.
|
* @param {number} vertexCountWithoutSkirts The vertex count of the mesh not including skirts.
|
||||||
* @param {number} minimumHeight The lowest height in the tile, in meters above the ellipsoid.
|
* @param {number} minimumHeight The lowest height in the tile, in meters above the ellipsoid.
|
||||||
* @param {number} maximumHeight The highest height in the tile, in meters above the ellipsoid.
|
* @param {number} maximumHeight The highest height in the tile, in meters above the ellipsoid.
|
||||||
|
* @param {Rectangle} rectangle The rectangle, in radians, covered by this tile.
|
||||||
* @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile.
|
* @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile.
|
||||||
* @param {Cartesian3} occludeePointInScaledSpace The occludee point of the tile, represented in ellipsoid-
|
* @param {Cartesian3} occludeePointInScaledSpace The occludee point of the tile, represented in ellipsoid-
|
||||||
* scaled space, and used for horizon culling. If this point is below the horizon,
|
* scaled space, and used for horizon culling. If this point is below the horizon,
|
||||||
|
|
@ -37,6 +49,7 @@ function TerrainMesh(
|
||||||
vertexCountWithoutSkirts,
|
vertexCountWithoutSkirts,
|
||||||
minimumHeight,
|
minimumHeight,
|
||||||
maximumHeight,
|
maximumHeight,
|
||||||
|
rectangle,
|
||||||
boundingSphere3D,
|
boundingSphere3D,
|
||||||
occludeePointInScaledSpace,
|
occludeePointInScaledSpace,
|
||||||
vertexStride,
|
vertexStride,
|
||||||
|
|
@ -101,6 +114,12 @@ function TerrainMesh(
|
||||||
*/
|
*/
|
||||||
this.maximumHeight = maximumHeight;
|
this.maximumHeight = maximumHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rectangle, in radians, covered by this tile.
|
||||||
|
* @type {Rectangle}
|
||||||
|
*/
|
||||||
|
this.rectangle = rectangle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A bounding sphere that completely contains the tile.
|
* A bounding sphere that completely contains the tile.
|
||||||
* @type {BoundingSphere}
|
* @type {BoundingSphere}
|
||||||
|
|
@ -150,5 +169,193 @@ function TerrainMesh(
|
||||||
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
|
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
|
||||||
*/
|
*/
|
||||||
this.northIndicesWestToEast = northIndicesWestToEast;
|
this.northIndicesWestToEast = northIndicesWestToEast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transform from model to world coordinates based on the terrain mesh's oriented bounding box.
|
||||||
|
* In 3D mode, this is computed from the oriented bounding box. In 2D and Columbus View modes,
|
||||||
|
* this is computed from the tile's rectangle's projected coordinates.
|
||||||
|
* @type {Matrix4}
|
||||||
|
*/
|
||||||
|
this._transform = new Matrix4();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the transform needs to be recomputed (due to changes in exaggeration or scene mode).
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this._recomputeTransform = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The terrain picker for this mesh, used for ray intersection tests.
|
||||||
|
* @type {TerrainPicker}
|
||||||
|
*/
|
||||||
|
this._terrainPicker = new TerrainPicker(vertices, indices, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the terrain tile's model-to-world transform matrix for the given scene mode and projection.
|
||||||
|
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @returns {Matrix4} The transform matrix.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TerrainMesh.prototype.getTransform = function (mode, projection) {
|
||||||
|
if (!this._recomputeTransform) {
|
||||||
|
return this._transform;
|
||||||
|
}
|
||||||
|
this._recomputeTransform = false;
|
||||||
|
|
||||||
|
if (!defined(mode) || mode === SceneMode.SCENE3D) {
|
||||||
|
return computeTransform(this, this._transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
return computeTransform2D(this, projection, this._transform);
|
||||||
|
};
|
||||||
|
|
||||||
|
function computeTransform(mesh, result) {
|
||||||
|
const exaggeration = mesh.encoding.exaggeration;
|
||||||
|
const exaggerationRelativeHeight = mesh.encoding.exaggerationRelativeHeight;
|
||||||
|
|
||||||
|
const exaggeratedMinHeight = VerticalExaggeration.getHeight(
|
||||||
|
mesh.minimumHeight,
|
||||||
|
exaggeration,
|
||||||
|
exaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
const exaggeratedMaxHeight = VerticalExaggeration.getHeight(
|
||||||
|
mesh.maximumHeight,
|
||||||
|
exaggeration,
|
||||||
|
exaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
const obb = OrientedBoundingBox.fromRectangle(
|
||||||
|
mesh.rectangle,
|
||||||
|
exaggeratedMinHeight,
|
||||||
|
exaggeratedMaxHeight,
|
||||||
|
Ellipsoid.default,
|
||||||
|
mesh.orientedBoundingBox,
|
||||||
|
);
|
||||||
|
|
||||||
|
return OrientedBoundingBox.computeTransformation(obb, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scratchSWCartesian = new Cartesian3();
|
||||||
|
const scratchNECartesian = new Cartesian3();
|
||||||
|
const scratchSWCartographic = new Cartographic();
|
||||||
|
const scratchNECartographic = new Cartographic();
|
||||||
|
const scratchScale2D = new Cartesian3();
|
||||||
|
const scratchCenter2D = new Cartesian3();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the terrain tile's model-to-world transform matrix for 2D or Columbus View modes.
|
||||||
|
* Assumes tiles in 2D are axis-aligned and still rectangular. (This is true for Web Mercator and Geographic projections.)
|
||||||
|
* @param {TerrainMesh} mesh The terrain mesh.
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @param {Matrix4} result The object in which to store the result.
|
||||||
|
* @returns {Matrix4} The transform matrix.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function computeTransform2D(mesh, projection, result) {
|
||||||
|
const exaggeration = mesh.encoding.exaggeration;
|
||||||
|
const exaggerationRelativeHeight = mesh.encoding.exaggerationRelativeHeight;
|
||||||
|
|
||||||
|
const exaggeratedMinHeight = VerticalExaggeration.getHeight(
|
||||||
|
mesh.minimumHeight,
|
||||||
|
exaggeration,
|
||||||
|
exaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
const exaggeratedMaxHeight = VerticalExaggeration.getHeight(
|
||||||
|
mesh.maximumHeight,
|
||||||
|
exaggeration,
|
||||||
|
exaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
const southwest = projection.project(
|
||||||
|
Cartographic.fromRadians(
|
||||||
|
mesh.rectangle.west,
|
||||||
|
mesh.rectangle.south,
|
||||||
|
0,
|
||||||
|
scratchSWCartographic,
|
||||||
|
),
|
||||||
|
scratchSWCartesian,
|
||||||
|
);
|
||||||
|
|
||||||
|
const northeast = projection.project(
|
||||||
|
Cartographic.fromRadians(
|
||||||
|
mesh.rectangle.east,
|
||||||
|
mesh.rectangle.north,
|
||||||
|
0,
|
||||||
|
scratchNECartographic,
|
||||||
|
),
|
||||||
|
scratchNECartesian,
|
||||||
|
);
|
||||||
|
|
||||||
|
const heightRange = exaggeratedMaxHeight - exaggeratedMinHeight;
|
||||||
|
const scale = Cartesian3.fromElements(
|
||||||
|
northeast.x - southwest.x,
|
||||||
|
northeast.y - southwest.y,
|
||||||
|
heightRange > 0 ? heightRange : 1.0, // Avoid zero scale
|
||||||
|
scratchScale2D,
|
||||||
|
);
|
||||||
|
|
||||||
|
const center = Cartesian3.fromElements(
|
||||||
|
southwest.x + scale.x * 0.5,
|
||||||
|
southwest.y + scale.y * 0.5,
|
||||||
|
exaggeratedMinHeight + scale.z * 0.5,
|
||||||
|
scratchCenter2D,
|
||||||
|
);
|
||||||
|
|
||||||
|
Matrix4.fromTranslation(center, result);
|
||||||
|
Matrix4.setScale(result, scale, result);
|
||||||
|
Matrix4.multiply(Transforms.SWIZZLE_3D_TO_2D_MATRIX, result, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives the point on this terrain tile where the given ray intersects
|
||||||
|
* @param {Ray} ray The ray to test for intersection.
|
||||||
|
* @param {boolean} cullBackFaces Whether to consider back-facing triangles as intersections.
|
||||||
|
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @returns {Cartesian3} The point on the mesh where the ray intersects, or undefined if there is no intersection.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TerrainMesh.prototype.pick = function (ray, cullBackFaces, mode, projection) {
|
||||||
|
return this._terrainPicker.rayIntersect(
|
||||||
|
ray,
|
||||||
|
this.getTransform(mode, projection),
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the terrain mesh to account for changes in vertical exaggeration.
|
||||||
|
* @param {Number} exaggeration A scalar used to exaggerate terrain.
|
||||||
|
* @param {Number} exaggerationRelativeHeight The relative height from which terrain is exaggerated.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TerrainMesh.prototype.updateExaggeration = function (
|
||||||
|
exaggeration,
|
||||||
|
exaggerationRelativeHeight,
|
||||||
|
) {
|
||||||
|
// The encoding stored on the TerrainMesh references the updated exaggeration values already. This is just used
|
||||||
|
// to trigger a rebuild on the terrain picker.
|
||||||
|
this._terrainPicker._vertices = this.vertices;
|
||||||
|
this._terrainPicker.needsRebuild = true;
|
||||||
|
this._recomputeTransform = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the terrain mesh to account for changes in scene mode.
|
||||||
|
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TerrainMesh.prototype.updateSceneMode = function (mode) {
|
||||||
|
this._terrainPicker.needsRebuild = true;
|
||||||
|
this._recomputeTransform = true;
|
||||||
|
};
|
||||||
|
|
||||||
export default TerrainMesh;
|
export default TerrainMesh;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,607 @@
|
||||||
|
import AxisAlignedBoundingBox from "./AxisAlignedBoundingBox.js";
|
||||||
|
import Cartesian3 from "./Cartesian3.js";
|
||||||
|
import defined from "./defined.js";
|
||||||
|
import IntersectionTests from "./IntersectionTests.js";
|
||||||
|
import Matrix4 from "./Matrix4.js";
|
||||||
|
import Ray from "./Ray.js";
|
||||||
|
import TaskProcessor from "./TaskProcessor.js";
|
||||||
|
import Cartographic from "./Cartographic.js";
|
||||||
|
import SceneMode from "../Scene/SceneMode.js";
|
||||||
|
import Interval from "./Interval.js";
|
||||||
|
import Check from "./Check.js";
|
||||||
|
import DeveloperError from "./DeveloperError.js";
|
||||||
|
|
||||||
|
// Terrain picker can be 4 levels deep (0-3)
|
||||||
|
const MAXIMUM_TERRAIN_PICKER_LEVEL = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an object that handles arbitrary ray intersections with a terrain mesh using a spatial acceleration structure.
|
||||||
|
*
|
||||||
|
* @alias TerrainPicker
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param {Float32Array} vertices The terrain mesh's vertex buffer.
|
||||||
|
* @param {Uint8Array|Uint16Array|Uint32Array} indices The terrain mesh's index buffer.
|
||||||
|
* @param {TerrainEncoding} encoding The terrain mesh's vertex encoding.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function TerrainPicker(vertices, indices, encoding) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("vertices", vertices);
|
||||||
|
Check.defined("indices", indices);
|
||||||
|
Check.defined("encoding", encoding);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The terrain mesh's vertex buffer.
|
||||||
|
* @type {Float32Array}
|
||||||
|
*/
|
||||||
|
this._vertices = vertices;
|
||||||
|
/**
|
||||||
|
* The terrain mesh's index buffer.
|
||||||
|
* @type {Uint32Array}
|
||||||
|
*/
|
||||||
|
this._indices = indices;
|
||||||
|
/**
|
||||||
|
* The terrain mesh's vertex encoding.
|
||||||
|
* @type {TerrainEncoding}
|
||||||
|
*/
|
||||||
|
this._encoding = encoding;
|
||||||
|
/**
|
||||||
|
* The inverse of the terrain mesh tile's transform from world space to local space.
|
||||||
|
* @type {Matrix4}
|
||||||
|
*/
|
||||||
|
this._inverseTransform = new Matrix4(); // Compute as-needed on rebuild
|
||||||
|
/**
|
||||||
|
* Whether or not to reset this terrain mesh's picker on the next ray intersection.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this._needsRebuild = true;
|
||||||
|
/**
|
||||||
|
* The root node of the terrain picker's quadtree.
|
||||||
|
* @type {TerrainPickerNode}
|
||||||
|
*/
|
||||||
|
this._rootNode = new TerrainPickerNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
const incrementallyBuildTerrainPickerTaskProcessor = new TaskProcessor(
|
||||||
|
"incrementallyBuildTerrainPicker",
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.defineProperties(TerrainPicker.prototype, {
|
||||||
|
/**
|
||||||
|
* Indicates whether the terrain picker needs to be rebuilt due to changes in the underlying terrain mesh's vertices or indices.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
needsRebuild: {
|
||||||
|
get: function () {
|
||||||
|
return this._needsRebuild;
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
this._needsRebuild = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node in the terrain picker quadtree.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function TerrainPickerNode() {
|
||||||
|
/**
|
||||||
|
* The tree-space x-coordinate of this node.
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
this.x = 0;
|
||||||
|
/**
|
||||||
|
* The tree-space y-coordinate of this node.
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
this.y = 0;
|
||||||
|
/**
|
||||||
|
* The level of this node in the quadtree.
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
this.level = 0;
|
||||||
|
/**
|
||||||
|
* The axis-aligned bounding box of this node (in the tree's local space).
|
||||||
|
* @type {AxisAlignedBoundingBox}
|
||||||
|
*/
|
||||||
|
this.aabb = createAABBForNode(this.x, this.y, this.level);
|
||||||
|
/**
|
||||||
|
* The indices of the triangles that intersect this node.
|
||||||
|
* @type {Uint32Array}
|
||||||
|
*/
|
||||||
|
this.intersectingTriangles = new Uint32Array(0);
|
||||||
|
/**
|
||||||
|
* The child terrain picker nodes of this node.
|
||||||
|
* @type {TerrainPickerNode[]}
|
||||||
|
*/
|
||||||
|
this.children = [];
|
||||||
|
/**
|
||||||
|
* Whether or not this node is currently building its children on a worker.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.buildingChildren = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child node to this node.
|
||||||
|
*
|
||||||
|
* @param {number} childIdx The index of the child to add (0-3).
|
||||||
|
* @memberof TerrainPickerNode
|
||||||
|
*/
|
||||||
|
TerrainPickerNode.prototype.addChild = function (childIdx) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
if (childIdx < 0 || childIdx > 3) {
|
||||||
|
throw new DeveloperError(
|
||||||
|
"TerrainPickerNode child index must be between 0 and 3, inclusive.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const childNode = new TerrainPickerNode();
|
||||||
|
// Use bitwise operations to get child x,y from child index and parent x,y
|
||||||
|
childNode.x = this.x * 2 + (childIdx & 1);
|
||||||
|
childNode.y = this.y * 2 + ((childIdx >> 1) & 1);
|
||||||
|
childNode.level = this.level + 1;
|
||||||
|
childNode.aabb = createAABBForNode(childNode.x, childNode.y, childNode.level);
|
||||||
|
|
||||||
|
this.children[childIdx] = childNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scratchTransformedRay = new Ray();
|
||||||
|
const scratchTrianglePoints = [
|
||||||
|
new Cartesian3(),
|
||||||
|
new Cartesian3(),
|
||||||
|
new Cartesian3(),
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the point on the mesh where the given ray intersects.
|
||||||
|
* @param {Ray} ray The ray to test.
|
||||||
|
* @param {Matrix4} tileTransform The terrain mesh tile's transform from local space to world space.
|
||||||
|
* @param {Boolean} cullBackFaces Whether to consider back-facing triangles as intersections.
|
||||||
|
* @param {SceneMode} mode The scene mode (2D/3D/Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @returns {Cartesian3 | undefined} result The intersection point, or undefined if there is no intersection.
|
||||||
|
* @memberof TerrainPicker
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TerrainPicker.prototype.rayIntersect = function (
|
||||||
|
ray,
|
||||||
|
tileTransform,
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
) {
|
||||||
|
// Lazily (re)create the terrain picker
|
||||||
|
if (this._needsRebuild) {
|
||||||
|
reset(this, tileTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invTransform = this._inverseTransform;
|
||||||
|
|
||||||
|
const transformedRay = scratchTransformedRay;
|
||||||
|
|
||||||
|
transformedRay.origin = Matrix4.multiplyByPoint(
|
||||||
|
invTransform,
|
||||||
|
ray.origin,
|
||||||
|
transformedRay.origin,
|
||||||
|
);
|
||||||
|
transformedRay.direction = Matrix4.multiplyByPointAsVector(
|
||||||
|
invTransform,
|
||||||
|
ray.direction,
|
||||||
|
transformedRay.direction,
|
||||||
|
);
|
||||||
|
|
||||||
|
const intersections = [];
|
||||||
|
getNodesIntersectingRay(this._rootNode, transformedRay, intersections);
|
||||||
|
|
||||||
|
return findClosestPointInClosestNode(
|
||||||
|
this,
|
||||||
|
intersections,
|
||||||
|
ray,
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the terrain picker's quadtree structure to just the root node. Done whenever the underlying terrain mesh changes.
|
||||||
|
* @param terrainPicker The terrain picker to reset.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function reset(terrainPicker, tileTransform) {
|
||||||
|
// PERFORMANCE_IDEA: warm-start the terrain picker by building a level on a worker.
|
||||||
|
// This currently isn't feasible because you can only copy the vertex buffer to a worker (slow) or transfer ownership (can't do picking on main thread in meantime).
|
||||||
|
// SharedArrayBuffers could be used, but most environments do not support them.
|
||||||
|
Matrix4.inverse(tileTransform, terrainPicker._inverseTransform);
|
||||||
|
|
||||||
|
terrainPicker._needsRebuild = false;
|
||||||
|
const triangleCount = terrainPicker._indices.length / 3;
|
||||||
|
const intersectingTriangles = new Uint32Array(triangleCount);
|
||||||
|
|
||||||
|
for (let i = 0; i < triangleCount; ++i) {
|
||||||
|
intersectingTriangles[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
terrainPicker._rootNode.intersectingTriangles = intersectingTriangles;
|
||||||
|
terrainPicker._rootNode.children.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scratchAABBMin = new Cartesian3();
|
||||||
|
const scratchAABBMax = new Cartesian3();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an axis-aligned bounding box for a quadtree node at the given tree-space coordinates and level.
|
||||||
|
* This AABB is in the tree's local space (where the root node of the tree is a unit cube in its own local space).
|
||||||
|
*
|
||||||
|
* @param {number} x The x coordinate of the node.
|
||||||
|
* @param {number} y The y coordinate of the node.
|
||||||
|
* @param {number} level The level of the node.
|
||||||
|
* @returns {AxisAlignedBoundingBox} The axis-aligned bounding box for the node.
|
||||||
|
*/
|
||||||
|
function createAABBForNode(x, y, level) {
|
||||||
|
const sizeAtLevel = 1.0 / Math.pow(2, level);
|
||||||
|
|
||||||
|
const aabbMin = Cartesian3.fromElements(
|
||||||
|
x * sizeAtLevel - 0.5,
|
||||||
|
y * sizeAtLevel - 0.5,
|
||||||
|
-0.5,
|
||||||
|
scratchAABBMin,
|
||||||
|
);
|
||||||
|
|
||||||
|
const aabbMax = Cartesian3.fromElements(
|
||||||
|
(x + 1) * sizeAtLevel - 0.5,
|
||||||
|
(y + 1) * sizeAtLevel - 0.5,
|
||||||
|
0.5,
|
||||||
|
scratchAABBMax,
|
||||||
|
);
|
||||||
|
|
||||||
|
return AxisAlignedBoundingBox.fromCorners(aabbMin, aabbMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs triangle vertex positions and index into provided buffers, for the worker to process.
|
||||||
|
* (The worker does tests to organize triangles into child nodes of the quadtree.)
|
||||||
|
* @param {Float32Array} trianglePositionsBuffer The buffer to pack triangle vertex positions into.
|
||||||
|
* @param {Uint32Array} triangleIndicesBuffer The buffer to pack triangle indices into.
|
||||||
|
* @param {Cartesian3[]} trianglePositions The triangle's vertex positions.
|
||||||
|
* @param {number} triangleIndex The triangle's index in the overall tile's index buffer.
|
||||||
|
* @param {number} bufferIndex The index to use to pack into the buffers.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function packTriangleBuffers(
|
||||||
|
trianglePositionsBuffer,
|
||||||
|
triangleIndicesBuffer,
|
||||||
|
trianglePositions,
|
||||||
|
triangleIndex,
|
||||||
|
bufferIndex,
|
||||||
|
) {
|
||||||
|
Cartesian3.pack(
|
||||||
|
trianglePositions[0],
|
||||||
|
trianglePositionsBuffer,
|
||||||
|
9 * bufferIndex,
|
||||||
|
);
|
||||||
|
Cartesian3.pack(
|
||||||
|
trianglePositions[1],
|
||||||
|
trianglePositionsBuffer,
|
||||||
|
9 * bufferIndex + 3,
|
||||||
|
);
|
||||||
|
Cartesian3.pack(
|
||||||
|
trianglePositions[2],
|
||||||
|
trianglePositionsBuffer,
|
||||||
|
9 * bufferIndex + 6,
|
||||||
|
);
|
||||||
|
triangleIndicesBuffer[bufferIndex] = triangleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} IntersectingNode
|
||||||
|
* @property {TerrainPickerNode} node - The intersecting quadtree node.
|
||||||
|
* @property {Interval} interval - The interval along the ray where the intersection occurs.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
const scratchInterval = new Interval();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively gathers all nodes in the quadtree that intersect the ray.
|
||||||
|
*
|
||||||
|
* @param {TerrainPickerNode} currentNode The current node being tested.
|
||||||
|
* @param {Ray} ray The ray to test.
|
||||||
|
* @param {IntersectingNode[]} intersectingNodes The array to store intersecting nodes in.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function getNodesIntersectingRay(currentNode, ray, intersectingNodes) {
|
||||||
|
const interval = IntersectionTests.rayAxisAlignedBoundingBox(
|
||||||
|
ray,
|
||||||
|
currentNode.aabb,
|
||||||
|
scratchInterval,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!defined(interval)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLeaf = !currentNode.children.length || currentNode.buildingChildren;
|
||||||
|
if (isLeaf) {
|
||||||
|
intersectingNodes.push({
|
||||||
|
node: currentNode,
|
||||||
|
interval: new Interval(interval.start, interval.stop),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < currentNode.children.length; i++) {
|
||||||
|
getNodesIntersectingRay(currentNode.children[i], ray, intersectingNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest intersecting node along the ray, in world space, and the closest point in that node,
|
||||||
|
* by testing all triangles in the closest node against the ray.
|
||||||
|
*
|
||||||
|
* @param {TerrainPicker} terrainPicker The terrain picker.
|
||||||
|
* @param {IntersectingNode[]} intersections The nodes that intersect the ray, along with the intersection intervals along said ray.
|
||||||
|
* @param {Ray} ray The ray to test.
|
||||||
|
* @param {boolean} cullBackFaces Whether to cull back faces.
|
||||||
|
* @param {SceneMode} mode The scene mode (2D/3D/Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @returns The closest point in world space, or undefined if no intersection.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function findClosestPointInClosestNode(
|
||||||
|
terrainPicker,
|
||||||
|
intersections,
|
||||||
|
ray,
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
) {
|
||||||
|
const sortedIntersections = intersections.sort(function (a, b) {
|
||||||
|
return a.interval.start - b.interval.start;
|
||||||
|
});
|
||||||
|
|
||||||
|
let minT = Number.MAX_VALUE;
|
||||||
|
for (let i = 0; i < sortedIntersections.length; i++) {
|
||||||
|
const intersection = sortedIntersections[i];
|
||||||
|
const intersectionResult = getClosestTriangleInNode(
|
||||||
|
terrainPicker,
|
||||||
|
ray,
|
||||||
|
intersection.node,
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
);
|
||||||
|
minT = Math.min(intersectionResult, minT);
|
||||||
|
if (minT !== Number.MAX_VALUE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minT !== Number.MAX_VALUE) {
|
||||||
|
return Ray.getPoint(ray, minT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test all triangles in the given node against the ray, returning the closest intersection t value along the ray.
|
||||||
|
* Additionally, collect the triangles' positions and indices along the way to launch worker process that uses them to build out child nodes.
|
||||||
|
*
|
||||||
|
* @param {TerrainPicker} terrainPicker The terrain picker.
|
||||||
|
* @param {Ray} ray The ray to test.
|
||||||
|
* @param {TerrainPickerNode} node The node to test.
|
||||||
|
* @param {boolean} cullBackFaces Whether to cull back faces.
|
||||||
|
* @param {SceneMode} mode The scene mode (2D/3D/Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @returns {number} The closest intersection t value along the ray, or Number.MAX_VALUE if no intersection.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function getClosestTriangleInNode(
|
||||||
|
terrainPicker,
|
||||||
|
ray,
|
||||||
|
node,
|
||||||
|
cullBackFaces,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
) {
|
||||||
|
let result = Number.MAX_VALUE;
|
||||||
|
const encoding = terrainPicker._encoding;
|
||||||
|
const indices = terrainPicker._indices;
|
||||||
|
const vertices = terrainPicker._vertices;
|
||||||
|
const triangleCount = node.intersectingTriangles.length;
|
||||||
|
const isMaxLevel = node.level >= MAXIMUM_TERRAIN_PICKER_LEVEL;
|
||||||
|
const shouldBuildChildren = !isMaxLevel && !node.buildingChildren;
|
||||||
|
|
||||||
|
let trianglePositions;
|
||||||
|
let triangleIndices;
|
||||||
|
if (shouldBuildChildren) {
|
||||||
|
// If the tree can be built deeper, prepare buffers to store triangle data for child nodes
|
||||||
|
trianglePositions = new Float32Array(triangleCount * 9); // 3 vertices per triangle * 3 floats per vertex
|
||||||
|
triangleIndices = new Uint32Array(triangleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < triangleCount; i++) {
|
||||||
|
const triIndex = node.intersectingTriangles[i];
|
||||||
|
const v0 = getVertexPosition(
|
||||||
|
encoding,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
vertices,
|
||||||
|
indices[3 * triIndex],
|
||||||
|
scratchTrianglePoints[0],
|
||||||
|
);
|
||||||
|
const v1 = getVertexPosition(
|
||||||
|
encoding,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
vertices,
|
||||||
|
indices[3 * triIndex + 1],
|
||||||
|
scratchTrianglePoints[1],
|
||||||
|
);
|
||||||
|
const v2 = getVertexPosition(
|
||||||
|
encoding,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
vertices,
|
||||||
|
indices[3 * triIndex + 2],
|
||||||
|
scratchTrianglePoints[2],
|
||||||
|
);
|
||||||
|
|
||||||
|
const triT = IntersectionTests.rayTriangleParametric(
|
||||||
|
ray,
|
||||||
|
v0,
|
||||||
|
v1,
|
||||||
|
v2,
|
||||||
|
cullBackFaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (defined(triT) && triT < result && triT >= 0) {
|
||||||
|
result = triT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldBuildChildren) {
|
||||||
|
packTriangleBuffers(
|
||||||
|
trianglePositions,
|
||||||
|
triangleIndices,
|
||||||
|
scratchTrianglePoints,
|
||||||
|
triIndex,
|
||||||
|
i,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldBuildChildren) {
|
||||||
|
for (let childIdx = 0; childIdx < 4; childIdx++) {
|
||||||
|
node.addChild(childIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrianglesToChildrenNodes(
|
||||||
|
terrainPicker._inverseTransform,
|
||||||
|
node,
|
||||||
|
triangleIndices,
|
||||||
|
trianglePositions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scratchCartographic = new Cartographic();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a vertex position from the buffer, taking into account the exaggeration and scene mode of the terrain.
|
||||||
|
*
|
||||||
|
* @param {TerrainEncoding} encoding The terrain encoding.
|
||||||
|
* @param {SceneMode} mode The scene mode (2D/3D/Columbus View).
|
||||||
|
* @param {MapProjection} projection The map projection.
|
||||||
|
* @param {Float32Array} vertices The vertex buffer of the terrain mesh.
|
||||||
|
* @param {Number} index The index of the vertex to get.
|
||||||
|
* @param {Cartesian3} result The decoded, exaggerated, and possibly projected vertex position.
|
||||||
|
* @returns {Cartesian3} The result vertex position.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function getVertexPosition(
|
||||||
|
encoding,
|
||||||
|
mode,
|
||||||
|
projection,
|
||||||
|
vertices,
|
||||||
|
index,
|
||||||
|
result,
|
||||||
|
) {
|
||||||
|
let position = encoding.getExaggeratedPosition(vertices, index, result);
|
||||||
|
if (mode === SceneMode.SCENE3D) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ellipsoid = projection.ellipsoid;
|
||||||
|
const positionCartographic = ellipsoid.cartesianToCartographic(
|
||||||
|
position,
|
||||||
|
scratchCartographic,
|
||||||
|
);
|
||||||
|
position = projection.project(positionCartographic, result);
|
||||||
|
// Swizzle because coordinate basis are different in 2D/Columbus View
|
||||||
|
position = Cartesian3.fromElements(
|
||||||
|
position.z,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds triangles to the child nodes of the given node by launching a worker process to do AABB-triangle testing.
|
||||||
|
*
|
||||||
|
* @param {Matrix4} inverseTransform
|
||||||
|
* @param {TerrainNode} node
|
||||||
|
* @param {Uint32Array} triangleIndices
|
||||||
|
* @param {Float32Array} trianglePositions
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the triangles have been added to the child nodes.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function addTrianglesToChildrenNodes(
|
||||||
|
inverseTransform,
|
||||||
|
node,
|
||||||
|
triangleIndices,
|
||||||
|
trianglePositions,
|
||||||
|
) {
|
||||||
|
node.buildingChildren = true;
|
||||||
|
|
||||||
|
// Prepare data to be sent to a worker
|
||||||
|
const inverseTransformPacked = new Float64Array(16);
|
||||||
|
Matrix4.pack(inverseTransform, inverseTransformPacked, 0);
|
||||||
|
|
||||||
|
const aabbArray = new Float64Array(6 * 4); // 6 elements per AABB, 4 children
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
Cartesian3.pack(node.children[i].aabb.minimum, aabbArray, i * 6);
|
||||||
|
Cartesian3.pack(node.children[i].aabb.maximum, aabbArray, i * 6 + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameters = {
|
||||||
|
aabbs: aabbArray,
|
||||||
|
inverseTransform: inverseTransformPacked,
|
||||||
|
triangleIndices: triangleIndices,
|
||||||
|
trianglePositions: trianglePositions,
|
||||||
|
};
|
||||||
|
|
||||||
|
const transferableObjects = [
|
||||||
|
aabbArray.buffer,
|
||||||
|
inverseTransformPacked.buffer,
|
||||||
|
triangleIndices.buffer,
|
||||||
|
trianglePositions.buffer,
|
||||||
|
];
|
||||||
|
|
||||||
|
const incrementallyBuildTerrainPickerPromise =
|
||||||
|
incrementallyBuildTerrainPickerTaskProcessor.scheduleTask(
|
||||||
|
parameters,
|
||||||
|
transferableObjects,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!defined(incrementallyBuildTerrainPickerPromise)) {
|
||||||
|
// Failed to schedule task, retry on next pick
|
||||||
|
node.buildingChildren = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After worker completes, it transfers back a buffer of intersecting triangles for each child node
|
||||||
|
// Assign these to the child nodes
|
||||||
|
const result = await incrementallyBuildTerrainPickerPromise;
|
||||||
|
result.intersectingTrianglesArrays.forEach((buffer, index) => {
|
||||||
|
node.children[index].intersectingTriangles = new Uint32Array(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The node's triangles have been distributed to its children
|
||||||
|
node.intersectingTriangles = new Uint32Array(0);
|
||||||
|
node.buildingChildren = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TerrainPicker;
|
||||||
|
|
@ -15,6 +15,8 @@ import CesiumMath from "./Math.js";
|
||||||
* @see CesiumTerrainProvider
|
* @see CesiumTerrainProvider
|
||||||
* @see VRTheWorldTerrainProvider
|
* @see VRTheWorldTerrainProvider
|
||||||
* @see GoogleEarthEnterpriseTerrainProvider
|
* @see GoogleEarthEnterpriseTerrainProvider
|
||||||
|
* @see ArcGISTiledElevationTerrainProvider
|
||||||
|
* @see Cesium3DTilesTerrainProvider
|
||||||
*/
|
*/
|
||||||
function TerrainProvider() {
|
function TerrainProvider() {
|
||||||
DeveloperError.throwInstantiationError();
|
DeveloperError.throwInstantiationError();
|
||||||
|
|
@ -236,7 +238,80 @@ TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices = function (
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Calculates the number of skirt vertices given the edge indices.
|
||||||
* @private
|
* @private
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth Edge indices along the west side of the tile.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest Edge indices along the south side of the tile.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth Edge indices along the east side of the tile.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast Edge indices along the north side of the tile.
|
||||||
|
* @returns {number} The number of skirt vertices.
|
||||||
|
*/
|
||||||
|
TerrainProvider.getSkirtVertexCount = function (
|
||||||
|
westIndicesSouthToNorth,
|
||||||
|
southIndicesEastToWest,
|
||||||
|
eastIndicesNorthToSouth,
|
||||||
|
northIndicesWestToEast,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
westIndicesSouthToNorth.length +
|
||||||
|
southIndicesEastToWest.length +
|
||||||
|
eastIndicesNorthToSouth.length +
|
||||||
|
northIndicesWestToEast.length
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of skirt indices given the number of skirt vertices.
|
||||||
|
* Consider a 3x3 grid of vertices. There will be 8 skirt vertices around the edge:
|
||||||
|
* - 16 edge triangles
|
||||||
|
* - 48 indices
|
||||||
|
*
|
||||||
|
* |\|\|
|
||||||
|
* |/| |/|
|
||||||
|
* |/| |/|
|
||||||
|
* |\|\|
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} skirtVertexCount
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
TerrainProvider.getSkirtIndexCount = function (skirtVertexCount) {
|
||||||
|
return (skirtVertexCount - 4) * 2 * 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of skirt indices given the number of skirt vertices with filled corners.
|
||||||
|
* Consider a 3x3 grid of vertices. There will be 8 skirt vertices around the edge:
|
||||||
|
* - 16 edge triangles
|
||||||
|
* - 4 cap triangles
|
||||||
|
* - 60 indices
|
||||||
|
*
|
||||||
|
* /|\|\|\
|
||||||
|
* |/| |/|
|
||||||
|
* |/| |/|
|
||||||
|
* \|\|\|/
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} skirtVertexCount
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
TerrainProvider.getSkirtIndexCountWithFilledCorners = function (
|
||||||
|
skirtVertexCount,
|
||||||
|
) {
|
||||||
|
return ((skirtVertexCount - 4) * 2 + 4) * 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds skirt indices.
|
||||||
|
* This does not add filled corners. Use {@link TerrainProvider.addSkirtIndicesWithFilledCorners} to add skirt indices with filled corners.
|
||||||
|
* @private
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth The indices of the vertices on the Western edge of the tile, ordered from South to North.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest The indices of the vertices on the Southern edge of the tile, ordered from East to West.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth The indices of the vertices on the Eastern edge of the tile, ordered from North to South.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast The indices of the vertices on the Northern edge of the tile, ordered from West to East.
|
||||||
|
* @param {number} vertexCount The number of vertices in the tile before adding skirt vertices.
|
||||||
|
* @param {Uint16Array|Uint32Array} indices The array of indices to which skirt indices are added.
|
||||||
|
* @param {number} offset The offset into the indices array at which to start adding skirt indices.
|
||||||
*/
|
*/
|
||||||
TerrainProvider.addSkirtIndices = function (
|
TerrainProvider.addSkirtIndices = function (
|
||||||
westIndicesSouthToNorth,
|
westIndicesSouthToNorth,
|
||||||
|
|
@ -272,6 +347,82 @@ TerrainProvider.addSkirtIndices = function (
|
||||||
addSkirtIndices(northIndicesWestToEast, vertexIndex, indices, offset);
|
addSkirtIndices(northIndicesWestToEast, vertexIndex, indices, offset);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds skirt indices with filled corners.
|
||||||
|
* @private
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth The indices of the vertices on the Western edge of the tile, ordered from South to North.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest The indices of the vertices on the Southern edge of the tile, ordered from East to West.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth The indices of the vertices on the Eastern edge of the tile, ordered from North to South.
|
||||||
|
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast The indices of the vertices on the Northern edge of the tile, ordered from West to East.
|
||||||
|
* @param {number} vertexCount The number of vertices in the tile before adding skirt vertices.
|
||||||
|
* @param {Uint16Array|Uint32Array} indices The array of indices to which skirt indices are added.
|
||||||
|
* @param {number} offset The offset into the indices array at which to start adding skirt indices.
|
||||||
|
*/
|
||||||
|
TerrainProvider.addSkirtIndicesWithFilledCorners = function (
|
||||||
|
westIndicesSouthToNorth,
|
||||||
|
southIndicesEastToWest,
|
||||||
|
eastIndicesNorthToSouth,
|
||||||
|
northIndicesWestToEast,
|
||||||
|
vertexCount,
|
||||||
|
indices,
|
||||||
|
offset,
|
||||||
|
) {
|
||||||
|
// Add skirt indices without filled corners
|
||||||
|
TerrainProvider.addSkirtIndices(
|
||||||
|
westIndicesSouthToNorth,
|
||||||
|
southIndicesEastToWest,
|
||||||
|
eastIndicesNorthToSouth,
|
||||||
|
northIndicesWestToEast,
|
||||||
|
vertexCount,
|
||||||
|
indices,
|
||||||
|
offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
const skirtVertexCount = TerrainProvider.getSkirtVertexCount(
|
||||||
|
westIndicesSouthToNorth,
|
||||||
|
southIndicesEastToWest,
|
||||||
|
eastIndicesNorthToSouth,
|
||||||
|
northIndicesWestToEast,
|
||||||
|
);
|
||||||
|
const skirtIndexCountWithoutCaps =
|
||||||
|
TerrainProvider.getSkirtIndexCount(skirtVertexCount);
|
||||||
|
|
||||||
|
const cornerStartIdx = offset + skirtIndexCountWithoutCaps;
|
||||||
|
|
||||||
|
const cornerSWIndex = westIndicesSouthToNorth[0];
|
||||||
|
const cornerNWIndex = northIndicesWestToEast[0];
|
||||||
|
const cornerNEIndex = eastIndicesNorthToSouth[0];
|
||||||
|
const cornerSEIndex = southIndicesEastToWest[0];
|
||||||
|
|
||||||
|
// Indices based on edge order in addSkirtIndices
|
||||||
|
const westSouthIndex = vertexCount;
|
||||||
|
const westNorthIndex = westSouthIndex + westIndicesSouthToNorth.length - 1;
|
||||||
|
const southEastIndex = westNorthIndex + 1;
|
||||||
|
const southWestIndex = southEastIndex + southIndicesEastToWest.length - 1;
|
||||||
|
const eastNorthIndex = southWestIndex + 1;
|
||||||
|
const eastSouthIndex = eastNorthIndex + eastIndicesNorthToSouth.length - 1;
|
||||||
|
const northWestIndex = eastSouthIndex + 1;
|
||||||
|
const northEastIndex = northWestIndex + northIndicesWestToEast.length - 1;
|
||||||
|
|
||||||
|
// Connect the corner vertices with the skirt vertices extending from the corner
|
||||||
|
|
||||||
|
indices[cornerStartIdx + 0] = cornerSWIndex;
|
||||||
|
indices[cornerStartIdx + 1] = westSouthIndex;
|
||||||
|
indices[cornerStartIdx + 2] = southWestIndex;
|
||||||
|
|
||||||
|
indices[cornerStartIdx + 3] = cornerSEIndex;
|
||||||
|
indices[cornerStartIdx + 4] = southEastIndex;
|
||||||
|
indices[cornerStartIdx + 5] = eastSouthIndex;
|
||||||
|
|
||||||
|
indices[cornerStartIdx + 6] = cornerNEIndex;
|
||||||
|
indices[cornerStartIdx + 7] = eastNorthIndex;
|
||||||
|
indices[cornerStartIdx + 8] = northEastIndex;
|
||||||
|
|
||||||
|
indices[cornerStartIdx + 9] = cornerNWIndex;
|
||||||
|
indices[cornerStartIdx + 10] = northWestIndex;
|
||||||
|
indices[cornerStartIdx + 11] = westNorthIndex;
|
||||||
|
};
|
||||||
|
|
||||||
function getEdgeIndices(width, height) {
|
function getEdgeIndices(width, height) {
|
||||||
const westIndicesSouthToNorth = new Array(height);
|
const westIndicesSouthToNorth = new Array(height);
|
||||||
const southIndicesEastToWest = new Array(width);
|
const southIndicesEastToWest = new Array(width);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,8 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const borderPadding = this._borderPadding;
|
||||||
|
|
||||||
// Vertical split (childNode1 = left half, childNode2 = right half).
|
// Vertical split (childNode1 = left half, childNode2 = right half).
|
||||||
if (widthDifference > heightDifference) {
|
if (widthDifference > heightDifference) {
|
||||||
node.childNode1 = new TextureNode({
|
node.childNode1 = new TextureNode({
|
||||||
|
|
@ -130,12 +132,18 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
||||||
width,
|
width,
|
||||||
height: nodeHeight,
|
height: nodeHeight,
|
||||||
});
|
});
|
||||||
node.childNode2 = new TextureNode({
|
|
||||||
x: rectangle.x + width,
|
// Apply padding only along the vertical "cut".
|
||||||
y: rectangle.y,
|
const widthDifferencePadded = widthDifference - borderPadding;
|
||||||
width: widthDifference,
|
|
||||||
height: nodeHeight,
|
if (widthDifferencePadded > 0) {
|
||||||
});
|
node.childNode2 = new TextureNode({
|
||||||
|
x: rectangle.x + width + borderPadding,
|
||||||
|
y: rectangle.y,
|
||||||
|
width: widthDifferencePadded,
|
||||||
|
height: nodeHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this._findNode(node.childNode1, { width, height });
|
return this._findNode(node.childNode1, { width, height });
|
||||||
}
|
}
|
||||||
|
|
@ -147,12 +155,19 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
||||||
width: nodeWidth,
|
width: nodeWidth,
|
||||||
height,
|
height,
|
||||||
});
|
});
|
||||||
node.childNode2 = new TextureNode({
|
|
||||||
x: rectangle.x,
|
// Apply padding only along the horizontal "cut".
|
||||||
y: rectangle.y + height,
|
const heightDifferencePadded = heightDifference - borderPadding;
|
||||||
width: nodeWidth,
|
|
||||||
height: heightDifference,
|
if (heightDifferencePadded > 0) {
|
||||||
});
|
node.childNode2 = new TextureNode({
|
||||||
|
x: rectangle.x,
|
||||||
|
y: rectangle.y + height + borderPadding,
|
||||||
|
width: nodeWidth,
|
||||||
|
height: heightDifferencePadded,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this._findNode(node.childNode1, { width, height });
|
return this._findNode(node.childNode1, { width, height });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1131,23 +1131,32 @@ Transforms.rotationMatrixFromPositionVelocity = function (
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const swizzleMatrix = new Matrix4(
|
/**
|
||||||
0.0,
|
* An immutable matrix that swaps x, y, z for 2D.
|
||||||
0.0,
|
*
|
||||||
1.0,
|
* @type {Matrix4}
|
||||||
0.0,
|
* @constant
|
||||||
1.0,
|
* @private
|
||||||
0.0,
|
*/
|
||||||
0.0,
|
Transforms.SWIZZLE_3D_TO_2D_MATRIX = Object.freeze(
|
||||||
0.0,
|
new Matrix4(
|
||||||
0.0,
|
0.0,
|
||||||
1.0,
|
0.0,
|
||||||
0.0,
|
1.0,
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
1.0,
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
1.0,
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const scratchCartographic = new Cartographic();
|
const scratchCartographic = new Cartographic();
|
||||||
|
|
@ -1210,7 +1219,7 @@ Transforms.basisTo2D = function (projection, matrix, result) {
|
||||||
const toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);
|
const toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);
|
||||||
const rotation = Matrix4.getMatrix3(matrix, scratchRotation);
|
const rotation = Matrix4.getMatrix3(matrix, scratchRotation);
|
||||||
const local = Matrix4.multiplyByMatrix3(toENU, rotation, result);
|
const local = Matrix4.multiplyByMatrix3(toENU, rotation, result);
|
||||||
Matrix4.multiply(swizzleMatrix, local, result); // Swap x, y, z for 2D
|
Matrix4.multiply(Transforms.SWIZZLE_3D_TO_2D_MATRIX, local, result); // Swap x, y, z for 2D
|
||||||
Matrix4.setTranslation(result, projectedPosition, result); // Use the projected center
|
Matrix4.setTranslation(result, projectedPosition, result); // Use the projected center
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -1260,7 +1269,7 @@ Transforms.ellipsoidTo2DModelMatrix = function (projection, center, result) {
|
||||||
projectedPosition,
|
projectedPosition,
|
||||||
scratchFromENU,
|
scratchFromENU,
|
||||||
);
|
);
|
||||||
Matrix4.multiply(swizzleMatrix, toENU, result);
|
Matrix4.multiply(Transforms.SWIZZLE_3D_TO_2D_MATRIX, toENU, result);
|
||||||
Matrix4.multiply(translation, result, result);
|
Matrix4.multiply(translation, result, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Check from "./Check.js";
|
||||||
* Finds an item in a sorted array.
|
* Finds an item in a sorted array.
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
* @param {Array} array The sorted array to search.
|
* @param {Array|Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} array The sorted array to search.
|
||||||
* @param {*} itemToFind The item to find in the array.
|
* @param {*} itemToFind The item to find in the array.
|
||||||
* @param {binarySearchComparator} comparator The function to use to compare the item to
|
* @param {binarySearchComparator} comparator The function to use to compare the item to
|
||||||
* elements in the array.
|
* elements in the array.
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,18 @@ import defined from "./defined.js";
|
||||||
import Resource from "./Resource.js";
|
import Resource from "./Resource.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Loads an image from a typed array.
|
||||||
|
* @param {Object} options An object containing the following properties:
|
||||||
|
* @param {Uint8Array} options.uint8Array The typed array containing the image data.
|
||||||
|
* @param {string} options.format The MIME format of the image (e.g., "image/png").
|
||||||
|
* @param {Request} [options.request] The request object to use to fetch the image.
|
||||||
|
* @param {boolean} [options.flipY=false] Whether to flip the image vertically.
|
||||||
|
* @param {boolean} [options.skipColorSpaceConversion=false] Whether to skip color space conversion.
|
||||||
|
* @returns {Promise<HTMLImageElement|HTMLCanvasElement|ImageBitmap>|undefined} A promise that resolves to the loaded image. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function loadImageFromTypedArray(options) {
|
function loadImageFromTypedArray(options) {
|
||||||
const uint8Array = options.uint8Array;
|
const { uint8Array, format, request } = options;
|
||||||
const format = options.format;
|
|
||||||
const request = options.request;
|
|
||||||
const flipY = options.flipY ?? false;
|
const flipY = options.flipY ?? false;
|
||||||
const skipColorSpaceConversion = options.skipColorSpaceConversion ?? false;
|
const skipColorSpaceConversion = options.skipColorSpaceConversion ?? false;
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ function sort(array, compare, userDefinedObject, start, end) {
|
||||||
* A stable merge sort.
|
* A stable merge sort.
|
||||||
*
|
*
|
||||||
* @function mergeSort
|
* @function mergeSort
|
||||||
* @param {Array} array The array to sort.
|
* @param {Array|Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} array The array to sort.
|
||||||
* @param {mergeSortComparator} comparator The function to use to compare elements in the array.
|
* @param {mergeSortComparator} comparator The function to use to compare elements in the array.
|
||||||
* @param {*} [userDefinedObject] Any item to pass as the third parameter to <code>comparator</code>.
|
* @param {*} [userDefinedObject] Any item to pass as the third parameter to <code>comparator</code>.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js";
|
||||||
* Initialization options for the BillboardGraphics constructor
|
* Initialization options for the BillboardGraphics constructor
|
||||||
*
|
*
|
||||||
* @property {Property | boolean} [show=true] A boolean Property specifying the visibility of the billboard.
|
* @property {Property | boolean} [show=true] A boolean Property specifying the visibility of the billboard.
|
||||||
* @property {Property | string | HTMLCanvasElement} [image] A Property specifying the Image, URI, or Canvas to use for the billboard.
|
* @property {Property | string | HTMLImageElement | HTMLCanvasElement} [image] A Property specifying the Image, URI, or Canvas to use for the billboard.
|
||||||
* @property {Property | number} [scale=1.0] A numeric Property specifying the scale to apply to the image size.
|
* @property {Property | number} [scale=1.0] A numeric Property specifying the scale to apply to the image size.
|
||||||
* @property {Property | Cartesian2} [pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
|
* @property {Property | Cartesian2} [pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
|
||||||
* @property {Property | Cartesian3} [eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
|
* @property {Property | Cartesian3} [eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,6 @@ BillboardVisualizer.prototype.update = function (time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
billboard.show = show;
|
billboard.show = show;
|
||||||
if (item.textureValue !== textureValue) {
|
|
||||||
billboard.image = textureValue;
|
|
||||||
item.textureValue = textureValue;
|
|
||||||
}
|
|
||||||
billboard.position = position;
|
billboard.position = position;
|
||||||
billboard.color = Property.getValueOrDefault(
|
billboard.color = Property.getValueOrDefault(
|
||||||
billboardGraphics._color,
|
billboardGraphics._color,
|
||||||
|
|
@ -227,6 +223,13 @@ BillboardVisualizer.prototype.update = function (time) {
|
||||||
defaultSplitDirection,
|
defaultSplitDirection,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Apply .image last, so any necessary property values are available
|
||||||
|
// in Billboard#_computeImageTextureSize before adding to the atlas.
|
||||||
|
if (item.textureValue !== textureValue) {
|
||||||
|
billboard.image = textureValue;
|
||||||
|
item.textureValue = textureValue;
|
||||||
|
}
|
||||||
|
|
||||||
const subRegion = Property.getValueOrUndefined(
|
const subRegion = Property.getValueOrUndefined(
|
||||||
billboardGraphics._imageSubRegion,
|
billboardGraphics._imageSubRegion,
|
||||||
time,
|
time,
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,26 @@ function Buffer(options) {
|
||||||
this.vertexArrayDestroyable = true;
|
this.vertexArrayDestroyable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Buffer.createPixelBuffer = function (options) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("options.context", options.context);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
if (!options.context._webgl2) {
|
||||||
|
throw new DeveloperError(
|
||||||
|
"A WebGL 2 context is required to create PixelBuffers.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Buffer({
|
||||||
|
context: options.context,
|
||||||
|
bufferTarget: WebGLConstants.PIXEL_PACK_BUFFER,
|
||||||
|
typedArray: options.typedArray,
|
||||||
|
sizeInBytes: options.sizeInBytes,
|
||||||
|
usage: options.usage,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
|
* Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
|
||||||
* <br /><br />
|
* <br /><br />
|
||||||
|
|
@ -242,6 +262,18 @@ Buffer.prototype._getBuffer = function () {
|
||||||
return this._buffer;
|
return this._buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Buffer.prototype._bind = function () {
|
||||||
|
const gl = this._gl;
|
||||||
|
const target = this._bufferTarget;
|
||||||
|
gl.bindBuffer(target, this._buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
Buffer.prototype._unBind = function () {
|
||||||
|
const gl = this._gl;
|
||||||
|
const target = this._bufferTarget;
|
||||||
|
gl.bindBuffer(target, null);
|
||||||
|
};
|
||||||
|
|
||||||
Buffer.prototype.copyFromArrayView = function (arrayView, offsetInBytes) {
|
Buffer.prototype.copyFromArrayView = function (arrayView, offsetInBytes) {
|
||||||
offsetInBytes = offsetInBytes ?? 0;
|
offsetInBytes = offsetInBytes ?? 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ const BufferUsage = {
|
||||||
STREAM_DRAW: WebGLConstants.STREAM_DRAW,
|
STREAM_DRAW: WebGLConstants.STREAM_DRAW,
|
||||||
STATIC_DRAW: WebGLConstants.STATIC_DRAW,
|
STATIC_DRAW: WebGLConstants.STATIC_DRAW,
|
||||||
DYNAMIC_DRAW: WebGLConstants.DYNAMIC_DRAW,
|
DYNAMIC_DRAW: WebGLConstants.DYNAMIC_DRAW,
|
||||||
|
DYNAMIC_READ: WebGLConstants.DYNAMIC_READ,
|
||||||
|
|
||||||
validate: function (bufferUsage) {
|
validate: function (bufferUsage) {
|
||||||
return (
|
return (
|
||||||
bufferUsage === BufferUsage.STREAM_DRAW ||
|
bufferUsage === BufferUsage.STREAM_DRAW ||
|
||||||
bufferUsage === BufferUsage.STATIC_DRAW ||
|
bufferUsage === BufferUsage.STATIC_DRAW ||
|
||||||
bufferUsage === BufferUsage.DYNAMIC_DRAW
|
bufferUsage === BufferUsage.DYNAMIC_DRAW ||
|
||||||
|
bufferUsage === BufferUsage.DYNAMIC_READ
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Buffer from "./Buffer.js";
|
||||||
import Check from "../Core/Check.js";
|
import Check from "../Core/Check.js";
|
||||||
import Color from "../Core/Color.js";
|
import Color from "../Core/Color.js";
|
||||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||||
|
|
@ -1442,13 +1443,88 @@ Context.prototype.endFrame = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {object} ReadState
|
||||||
|
*
|
||||||
|
* Options defining a rectangle to read pixels from.
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {object} readState An object with the following properties:
|
* @property {number} [x=0] The x offset of the rectangle to read from.
|
||||||
* @param {number} [readState.x=0] The x offset of the rectangle to read from.
|
* @property {number} [y=0] The y offset of the rectangle to read from.
|
||||||
* @param {number} [readState.y=0] The y offset of the rectangle to read from.
|
* @property {number} [width=this.drawingBufferWidth] The width of the rectangle to read from.
|
||||||
* @param {number} [readState.width=this.drawingBufferWidth] The width of the rectangle to read from.
|
* @property {number} [height=this.drawingBufferHeight] The height of the rectangle to read from.
|
||||||
* @param {number} [readState.height=this.drawingBufferHeight] The height of the rectangle to read from.
|
* @property {FrameBuffer|undefined} [framebuffer] The framebuffer to read from. If undefined, the read will be from the default framebuffer.
|
||||||
* @param {Framebuffer} [readState.framebuffer] The framebuffer to read from. If undefined, the read will be from the default framebuffer.
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read pixels from a framebuffer into a Pixel Buffer Object (PBO).
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {ReadState} readState Options defining a rectangle to read pixels from.
|
||||||
|
* @returns {Buffer} A PixelBuffer containing the pixels read from the specified rectangle.
|
||||||
|
*
|
||||||
|
* @exception {DeveloperError} A WebGL 2 context is required to read pixels using a PBO.
|
||||||
|
*/
|
||||||
|
Context.prototype.readPixelsToPBO = function (readState) {
|
||||||
|
const gl = this._gl;
|
||||||
|
|
||||||
|
readState = readState ?? Frozen.EMPTY_OBJECT;
|
||||||
|
const x = Math.max(readState.x ?? 0, 0);
|
||||||
|
const y = Math.max(readState.y ?? 0, 0);
|
||||||
|
const width = readState.width ?? this.drawingBufferWidth;
|
||||||
|
const height = readState.height ?? this.drawingBufferHeight;
|
||||||
|
const framebuffer = readState.framebuffer;
|
||||||
|
|
||||||
|
if (!this._webgl2) {
|
||||||
|
throw new DeveloperError(
|
||||||
|
"A WebGL 2 context is required to read pixels using a PBO.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.typeOf.number.greaterThan("readState.width", width, 0);
|
||||||
|
Check.typeOf.number.greaterThan("readState.height", height, 0);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
|
||||||
|
let pixelFormat = PixelFormat.RGBA;
|
||||||
|
if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
|
||||||
|
pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
|
||||||
|
pixelFormat = framebuffer.getColorTexture(0).pixelFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixels = Buffer.createPixelBuffer({
|
||||||
|
context: this,
|
||||||
|
sizeInBytes: PixelFormat.textureSizeInBytes(
|
||||||
|
pixelFormat,
|
||||||
|
pixelDatatype,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
),
|
||||||
|
usage: BufferUsage.DYNAMIC_READ,
|
||||||
|
});
|
||||||
|
|
||||||
|
bindFramebuffer(this, framebuffer);
|
||||||
|
|
||||||
|
pixels._bind();
|
||||||
|
gl.readPixels(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixelFormat,
|
||||||
|
PixelDatatype.toWebGLConstant(pixelDatatype, this),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
pixels._unBind();
|
||||||
|
|
||||||
|
return pixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read pixels from a framebuffer into a typed array.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {ReadState} readState Options defining a rectangle to read pixels from.
|
||||||
* @returns {Uint8Array|Uint16Array|Float32Array|Uint32Array} The pixels in the specified rectangle.
|
* @returns {Uint8Array|Uint16Array|Float32Array|Uint32Array} The pixels in the specified rectangle.
|
||||||
*/
|
*/
|
||||||
Context.prototype.readPixels = function (readState) {
|
Context.prototype.readPixels = function (readState) {
|
||||||
|
|
@ -1467,12 +1543,14 @@ Context.prototype.readPixels = function (readState) {
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
|
let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
|
||||||
|
let pixelFormat = PixelFormat.RGBA;
|
||||||
if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
|
if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
|
||||||
pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
|
pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
|
||||||
|
pixelFormat = framebuffer.getColorTexture(0).pixelFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixels = PixelFormat.createTypedArray(
|
const pixels = PixelFormat.createTypedArray(
|
||||||
PixelFormat.RGBA,
|
pixelFormat,
|
||||||
pixelDatatype,
|
pixelDatatype,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import Check from "../Core/Check.js";
|
||||||
|
import destroyObject from "../Core/destroyObject.js";
|
||||||
|
import DeveloperError from "../Core/DeveloperError.js";
|
||||||
|
import Frozen from "../Core/Frozen.js";
|
||||||
|
import RuntimeError from "../Core/RuntimeError.js";
|
||||||
|
import WebGLConstants from "../Core/WebGLConstants.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The WebGLSync interface is part of the WebGL 2 API and is used to synchronize activities between the GPU and the application.
|
||||||
|
*
|
||||||
|
* @param {object} options Object with the following properties:
|
||||||
|
* @param {Context} context
|
||||||
|
*
|
||||||
|
* @exception {DeveloperError} A WebGL 2 context is required to use Sync operations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Sync(options) {
|
||||||
|
options = options ?? Frozen.EMPTY_OBJECT;
|
||||||
|
const context = options.context;
|
||||||
|
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("options.context", context);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
if (!context._webgl2) {
|
||||||
|
throw new DeveloperError(
|
||||||
|
"A WebGL 2 context is required to use Sync operations.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gl = context._gl;
|
||||||
|
const sync = gl.fenceSync(WebGLConstants.SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
|
||||||
|
this._gl = gl;
|
||||||
|
this._sync = sync;
|
||||||
|
}
|
||||||
|
Sync.create = function (options) {
|
||||||
|
return new Sync(options);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Query the sync status of this Sync object.
|
||||||
|
*
|
||||||
|
* @returns {number} Returns a WebGLConstants indicating the status of the sync object (WebGLConstants.SIGNALED or WebGLConstants.UNSIGNALED).
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Sync.prototype.getStatus = function () {
|
||||||
|
const status = this._gl.getSyncParameter(
|
||||||
|
this._sync,
|
||||||
|
WebGLConstants.SYNC_STATUS,
|
||||||
|
);
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
Sync.prototype.isDestroyed = function () {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
Sync.prototype.destroy = function () {
|
||||||
|
this._gl.deleteSync(this._sync);
|
||||||
|
return destroyObject(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incremantally polls the status of the Sync object until signaled then resolves.
|
||||||
|
* Usually polling should be done once per frame.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* try {
|
||||||
|
* await sync.waitForSignal(function (next) {
|
||||||
|
* setTimeout(next, 100);
|
||||||
|
* });
|
||||||
|
*} catch (e) {
|
||||||
|
* throw "Signal timeout";
|
||||||
|
*} finally {
|
||||||
|
* sync.destroy();
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
* @param {function} scheduleFunction Function for scheduling the next poll. Receives a callback as its only parameter.
|
||||||
|
* @param {number} [ttl=10] Max number of iterations to poll until timeout.
|
||||||
|
*
|
||||||
|
* @exception {RuntimeError} Wait for signal timeout.
|
||||||
|
*/
|
||||||
|
Sync.prototype.waitForSignal = async function (scheduleFunction, ttl) {
|
||||||
|
const self = this;
|
||||||
|
ttl = ttl ?? 10;
|
||||||
|
function waitForSignal0(resolve, reject, ttl) {
|
||||||
|
return () => {
|
||||||
|
const syncStatus = self.getStatus();
|
||||||
|
const signaled = syncStatus === WebGLConstants.SIGNALED;
|
||||||
|
if (signaled) {
|
||||||
|
resolve();
|
||||||
|
} else if (ttl <= 0) {
|
||||||
|
reject(new RuntimeError("Wait for signal timeout"));
|
||||||
|
} else {
|
||||||
|
scheduleFunction(waitForSignal0(resolve, reject, ttl - 1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
scheduleFunction(waitForSignal0(resolve, reject, ttl));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default Sync;
|
||||||
|
|
@ -59,12 +59,16 @@ function TextureAtlas(options) {
|
||||||
this._initialSize = initialSize;
|
this._initialSize = initialSize;
|
||||||
|
|
||||||
this._texturePacker = undefined;
|
this._texturePacker = undefined;
|
||||||
|
/** @type {BoundingRectangle[]} */
|
||||||
this._rectangles = [];
|
this._rectangles = [];
|
||||||
|
/** @type {Map<number, number>} */
|
||||||
this._subRegions = new Map();
|
this._subRegions = new Map();
|
||||||
this._guid = createGuid();
|
this._guid = createGuid();
|
||||||
|
|
||||||
this._imagesToAddQueue = [];
|
this._imagesToAddQueue = [];
|
||||||
|
/** @type {Map<string, number>} */
|
||||||
this._indexById = new Map();
|
this._indexById = new Map();
|
||||||
|
/** @type {Map<string, Promise<number>>} */
|
||||||
this._indexPromiseById = new Map();
|
this._indexPromiseById = new Map();
|
||||||
this._nextIndex = 0;
|
this._nextIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -644,34 +648,51 @@ async function resolveImage(image, id) {
|
||||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||||
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
|
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
|
||||||
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
|
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
|
||||||
* @returns {Promise<number>} A Promise that resolves to the image region index. -1 is returned if resouces are in the process of being destroyed.
|
* @param {number} width A number specifying the width of the texture. If undefined, the image width will be used.
|
||||||
|
* @param {number} height A number specifying the height of the texture. If undefined, the image height will be used.
|
||||||
|
* @returns {Promise<number>} A Promise that resolves to the image region index, or -1 if resources are in the process of being destroyed.
|
||||||
*/
|
*/
|
||||||
TextureAtlas.prototype.addImage = function (id, image) {
|
TextureAtlas.prototype.addImage = function (id, image, width, height) {
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
Check.typeOf.string("id", id);
|
Check.typeOf.string("id", id);
|
||||||
Check.defined("image", image);
|
Check.defined("image", image);
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
let promise = this._indexPromiseById.get(id);
|
let promise = this._indexPromiseById.get(id);
|
||||||
|
let index = this._indexById.get(id);
|
||||||
if (defined(promise)) {
|
if (defined(promise)) {
|
||||||
// This image has already been added
|
// This image is already being added
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
if (defined(index)) {
|
||||||
|
// This image has already been added and resolved
|
||||||
|
return Promise.resolve(index);
|
||||||
|
}
|
||||||
|
|
||||||
const index = this._nextIndex++;
|
index = this._nextIndex++;
|
||||||
this._indexById.set(id, index);
|
this._indexById.set(id, index);
|
||||||
|
|
||||||
const resolveAndAddImage = async () => {
|
const resolveAndAddImage = async () => {
|
||||||
image = await resolveImage(image, id);
|
const resolvedImage = await resolveImage(image, id);
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
Check.defined("image", image);
|
Check.defined("image", resolvedImage);
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
if (this.isDestroyed() || !defined(image)) {
|
if (this.isDestroyed() || !defined(resolvedImage)) {
|
||||||
|
this._indexPromiseById.delete(id);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._addImage(index, image);
|
if (defined(width)) {
|
||||||
|
resolvedImage.width = width;
|
||||||
|
}
|
||||||
|
if (defined(height)) {
|
||||||
|
resolvedImage.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIndex = await this._addImage(index, resolvedImage);
|
||||||
|
this._indexPromiseById.delete(id);
|
||||||
|
return imageIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
promise = resolveAndAddImage();
|
promise = resolveAndAddImage();
|
||||||
|
|
@ -679,46 +700,65 @@ TextureAtlas.prototype.addImage = function (id, image) {
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an existing sub-region of an existing atlas image as additional image indices.
|
||||||
|
* @private
|
||||||
|
* @param {string} id The identifier of the existing image.
|
||||||
|
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||||
|
* @param {number} imageIndex The index of the image.
|
||||||
|
* @returns {Promise<number> | number | undefined} The existing subRegion index, or undefined if not yet added.
|
||||||
|
*/
|
||||||
|
TextureAtlas.prototype.getCachedImageSubRegion = function (
|
||||||
|
id,
|
||||||
|
subRegion,
|
||||||
|
imageIndex,
|
||||||
|
) {
|
||||||
|
const imagePromise = this._indexPromiseById.get(id);
|
||||||
|
for (const [index, parentIndex] of this._subRegions.entries()) {
|
||||||
|
if (imageIndex === parentIndex) {
|
||||||
|
const boundingRegion = this._rectangles[index];
|
||||||
|
if (boundingRegion.equals(subRegion)) {
|
||||||
|
// The subregion is already being tracked
|
||||||
|
if (imagePromise) {
|
||||||
|
return imagePromise.then((resolvedImageIndex) =>
|
||||||
|
resolvedImageIndex === -1 ? -1 : index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a sub-region of an existing atlas image as additional image indices.
|
* Add a sub-region of an existing atlas image as additional image indices.
|
||||||
* @private
|
* @private
|
||||||
* @param {string} id The identifier of the existing image.
|
* @param {string} id The identifier of the existing image.
|
||||||
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||||
* @returns {Promise<number>} A Promise that resolves to the image region index. -1 is returned if resouces are in the process of being destroyed.
|
* @returns {number | Promise<number>} The resolved image region index, or a Promise that resolves to it. -1 is returned if resources are in the process of being destroyed.
|
||||||
*/
|
*/
|
||||||
TextureAtlas.prototype.addImageSubRegion = function (id, subRegion) {
|
TextureAtlas.prototype.addImageSubRegion = function (id, subRegion) {
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
Check.typeOf.string("id", id);
|
Check.typeOf.string("id", id);
|
||||||
Check.defined("subRegion", subRegion);
|
Check.defined("subRegion", subRegion);
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
const imageIndex = this._indexById.get(id);
|
const imageIndex = this._indexById.get(id);
|
||||||
if (!defined(imageIndex)) {
|
if (!defined(imageIndex)) {
|
||||||
throw new RuntimeError(`image with id "${id}" not found in the atlas.`);
|
throw new RuntimeError(`image with id "${id}" not found in the atlas.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexPromise = this._indexPromiseById.get(id);
|
let index = this.getCachedImageSubRegion(id, subRegion, imageIndex);
|
||||||
for (const [index, parentIndex] of this._subRegions.entries()) {
|
if (defined(index)) {
|
||||||
if (imageIndex === parentIndex) {
|
return index;
|
||||||
const boundingRegion = this._rectangles[index];
|
|
||||||
if (boundingRegion.equals(subRegion)) {
|
|
||||||
// The subregion is already being tracked
|
|
||||||
return indexPromise.then((resolvedImageIndex) => {
|
|
||||||
if (resolvedImageIndex === -1) {
|
|
||||||
// The atlas has been destroyed
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = this._nextIndex++;
|
index = this._nextIndex++;
|
||||||
this._subRegions.set(index, imageIndex);
|
this._subRegions.set(index, imageIndex);
|
||||||
this._rectangles[index] = subRegion.clone();
|
this._rectangles[index] = subRegion.clone();
|
||||||
|
|
||||||
|
const indexPromise =
|
||||||
|
this._indexPromiseById.get(id) ?? Promise.resolve(imageIndex);
|
||||||
|
|
||||||
return indexPromise.then((imageIndex) => {
|
return indexPromise.then((imageIndex) => {
|
||||||
if (imageIndex === -1) {
|
if (imageIndex === -1) {
|
||||||
// The atlas has been destroyed
|
// The atlas has been destroyed
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Resource from "../Core/Resource.js";
|
||||||
|
|
||||||
let defaultTokenCredit;
|
let defaultTokenCredit;
|
||||||
const defaultAccessToken =
|
const defaultAccessToken =
|
||||||
"AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmQqEeLjCLZBBnNm_y_K-Cs-tuPcsgUmlxDWvPdTmGIEhrihfZMTG77j2nmCeqeYCPAenHGG1YfJqJaeRLuy5YjARJcFLP2I_judomiDS-8A_LVZxWUObwIQNE5wcsQKxJl7RHiKm-81XRDuUJZXGqy9B4PwPxWkS-N9PZ7NmTT-sP6BOGn5ouiAN8dkxwNx3tA.AT1_BU0Co4D8";
|
"AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmTlKbWoQXtdb-m2VclxMHo8eEPu_iyh95DPKVzeyNrKkb2zc_ICfJxY7tPEl4aVdWCyU_P18q9k6D26yO7pAVtbr3I-gxRJb412PB_FydyuNKYsVRiVLqJGaYOCYhJeY3Tv8USWG4TQVu6i2mMZ8CBE68HAd3DByIRkBcjQRnker5_UyWjO86QZxpbnykaqDtY.AT1_zMTIWhVd";
|
||||||
/**
|
/**
|
||||||
* Default options for accessing the ArcGIS image tile service.
|
* Default options for accessing the ArcGIS image tile service.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Check from "../Core/Check.js";
|
import Check from "../Core/Check.js";
|
||||||
|
import Frozen from "../Core/Frozen.js";
|
||||||
import Credit from "../Core/Credit.js";
|
import Credit from "../Core/Credit.js";
|
||||||
import defined from "../Core/defined.js";
|
import defined from "../Core/defined.js";
|
||||||
import Resource from "../Core/Resource.js";
|
import Resource from "../Core/Resource.js";
|
||||||
|
|
@ -29,7 +30,6 @@ const trailingSlashRegex = /\/$/;
|
||||||
*
|
*
|
||||||
* @alias Azure2DImageryProvider
|
* @alias Azure2DImageryProvider
|
||||||
* @constructor
|
* @constructor
|
||||||
* @private
|
|
||||||
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
|
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -41,16 +41,18 @@ const trailingSlashRegex = /\/$/;
|
||||||
*/
|
*/
|
||||||
function Azure2DImageryProvider(options) {
|
function Azure2DImageryProvider(options) {
|
||||||
options = options ?? {};
|
options = options ?? {};
|
||||||
const maximumLevel = options.maximumLevel ?? 22;
|
|
||||||
const minimumLevel = options.minimumLevel ?? 0;
|
|
||||||
const tilesetId = options.tilesetId ?? "microsoft.imagery";
|
const tilesetId = options.tilesetId ?? "microsoft.imagery";
|
||||||
|
this._maximumLevel = options.maximumLevel ?? 22;
|
||||||
|
this._minimumLevel = options.minimumLevel ?? 0;
|
||||||
|
|
||||||
const subscriptionKey =
|
this._subscriptionKey =
|
||||||
options.subscriptionKey ?? options["subscription-key"];
|
options.subscriptionKey ?? options["subscription-key"];
|
||||||
//>>includeStart('debug', pragmas.debug);
|
//>>includeStart('debug', pragmas.debug);
|
||||||
Check.defined("options.subscriptionKey", subscriptionKey);
|
Check.defined("options.subscriptionKey", this._subscriptionKey);
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
this._tilesetId = options.tilesetId;
|
||||||
|
|
||||||
const resource =
|
const resource =
|
||||||
options.url instanceof IonResource
|
options.url instanceof IonResource
|
||||||
? options.url
|
? options.url
|
||||||
|
|
@ -60,19 +62,23 @@ function Azure2DImageryProvider(options) {
|
||||||
if (!trailingSlashRegex.test(templateUrl)) {
|
if (!trailingSlashRegex.test(templateUrl)) {
|
||||||
templateUrl += "/";
|
templateUrl += "/";
|
||||||
}
|
}
|
||||||
templateUrl += `map/tile`;
|
|
||||||
|
|
||||||
resource.url = templateUrl;
|
const tilesUrl = `${templateUrl}map/tile`;
|
||||||
|
this._viewportUrl = `${templateUrl}map/attribution`;
|
||||||
|
|
||||||
|
resource.url = tilesUrl;
|
||||||
|
|
||||||
resource.setQueryParameters({
|
resource.setQueryParameters({
|
||||||
"api-version": "2024-04-01",
|
"api-version": "2024-04-01",
|
||||||
tilesetId: tilesetId,
|
tilesetId: tilesetId,
|
||||||
|
"subscription-key": this._subscriptionKey,
|
||||||
zoom: `{z}`,
|
zoom: `{z}`,
|
||||||
x: `{x}`,
|
x: `{x}`,
|
||||||
y: `{y}`,
|
y: `{y}`,
|
||||||
"subscription-key": subscriptionKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._resource = resource;
|
||||||
|
|
||||||
let credit;
|
let credit;
|
||||||
if (defined(options.credit)) {
|
if (defined(options.credit)) {
|
||||||
credit = options.credit;
|
credit = options.credit;
|
||||||
|
|
@ -83,8 +89,8 @@ function Azure2DImageryProvider(options) {
|
||||||
|
|
||||||
const provider = new UrlTemplateImageryProvider({
|
const provider = new UrlTemplateImageryProvider({
|
||||||
...options,
|
...options,
|
||||||
maximumLevel,
|
maximumLevel: this._maximumLevel,
|
||||||
minimumLevel,
|
minimumLevel: this._minimumLevel,
|
||||||
url: resource,
|
url: resource,
|
||||||
credit: credit,
|
credit: credit,
|
||||||
});
|
});
|
||||||
|
|
@ -93,6 +99,7 @@ function Azure2DImageryProvider(options) {
|
||||||
|
|
||||||
// This will be defined for ion resources
|
// This will be defined for ion resources
|
||||||
this._tileCredits = resource.credits;
|
this._tileCredits = resource.credits;
|
||||||
|
this._attributionsByLevel = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(Azure2DImageryProvider.prototype, {
|
Object.defineProperties(Azure2DImageryProvider.prototype, {
|
||||||
|
|
@ -263,7 +270,18 @@ Object.defineProperties(Azure2DImageryProvider.prototype, {
|
||||||
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
|
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
|
||||||
*/
|
*/
|
||||||
Azure2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
Azure2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
||||||
return this._imageryProvider.getTileCredits(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);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -282,7 +300,21 @@ Azure2DImageryProvider.prototype.requestImage = function (
|
||||||
level,
|
level,
|
||||||
request,
|
request,
|
||||||
) {
|
) {
|
||||||
return this._imageryProvider.requestImage(x, y, level, request);
|
const promise = this._imageryProvider.requestImage(x, y, level, request);
|
||||||
|
|
||||||
|
// If the requestImage call returns undefined, it couldn't be scheduled this frame. Make sure to return undefined so this can be handled upstream.
|
||||||
|
if (!defined(promise)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronously request and populate _attributionsByLevel if it hasn't been already. We do this here so that the promise can be properly awaited.
|
||||||
|
if (promise && !defined(this._attributionsByLevel)) {
|
||||||
|
return Promise.all([promise, this.getViewportCredits()]).then(
|
||||||
|
(results) => results[0],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -306,5 +338,57 @@ Azure2DImageryProvider.prototype.pickFeatures = function (
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get attribution for imagery from Azure Maps to display in the credits
|
||||||
|
* @private
|
||||||
|
* @return {Promise<Map<Credit[]>>} The list of attribution sources to display in the credits.
|
||||||
|
*/
|
||||||
|
Azure2DImageryProvider.prototype.getViewportCredits = async function () {
|
||||||
|
const maximumLevel = this._maximumLevel;
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||||
|
promises.push(
|
||||||
|
fetchViewportAttribution(
|
||||||
|
this._resource,
|
||||||
|
this._viewportUrl,
|
||||||
|
this._subscriptionKey,
|
||||||
|
this._tilesetId,
|
||||||
|
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].join(",");
|
||||||
|
if (attributions) {
|
||||||
|
const levelCredits = new Credit(attributions);
|
||||||
|
credits.push(levelCredits);
|
||||||
|
}
|
||||||
|
attributionsByLevel.set(level, credits);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._attributionsByLevel = attributionsByLevel;
|
||||||
|
|
||||||
|
return attributionsByLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchViewportAttribution(resource, url, key, tilesetId, level) {
|
||||||
|
const viewportResource = resource.getDerivedResource({
|
||||||
|
url,
|
||||||
|
queryParameters: {
|
||||||
|
zoom: level,
|
||||||
|
bounds: "-180,-90,180,90",
|
||||||
|
},
|
||||||
|
data: JSON.stringify(Frozen.EMPTY_OBJECT),
|
||||||
|
});
|
||||||
|
|
||||||
|
const viewportJson = await viewportResource.fetchJson();
|
||||||
|
return viewportJson.copyrights;
|
||||||
|
}
|
||||||
|
|
||||||
// Exposed for tests
|
// Exposed for tests
|
||||||
export default Azure2DImageryProvider;
|
export default Azure2DImageryProvider;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import SceneMode from "./SceneMode.js";
|
||||||
import SceneTransforms from "./SceneTransforms.js";
|
import SceneTransforms from "./SceneTransforms.js";
|
||||||
import VerticalOrigin from "./VerticalOrigin.js";
|
import VerticalOrigin from "./VerticalOrigin.js";
|
||||||
import SplitDirection from "./SplitDirection.js";
|
import SplitDirection from "./SplitDirection.js";
|
||||||
|
import getExtensionFromUri from "../Core/getExtensionFromUri.js";
|
||||||
|
import isDataUri from "../Core/isDataUri.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} Billboard.ConstructorOptions
|
* @typedef {object} Billboard.ConstructorOptions
|
||||||
|
|
@ -32,7 +34,7 @@ import SplitDirection from "./SplitDirection.js";
|
||||||
* @property {Cartesian3} position The cartesian position of the billboard.
|
* @property {Cartesian3} position The cartesian position of the billboard.
|
||||||
* @property {*} [id] A user-defined object to return when the billboard is picked with {@link Scene#pick}.
|
* @property {*} [id] A user-defined object to return when the billboard is picked with {@link Scene#pick}.
|
||||||
* @property {boolean} [show=true] Determines if this billboard will be shown.
|
* @property {boolean} [show=true] Determines if this billboard will be shown.
|
||||||
* @property {string | HTMLCanvasElement} [image] A loaded HTMLImageElement, ImageData, or a url to an image to use for the billboard.
|
* @property {string | HTMLImageElement | HTMLCanvasElement} [image] A loaded HTMLImageElement, ImageData, or a url to an image to use for the billboard.
|
||||||
* @property {number} [scale=1.0] A number specifying the uniform scale that is multiplied with the billboard's image size in pixels.
|
* @property {number} [scale=1.0] A number specifying the uniform scale that is multiplied with the billboard's image size in pixels.
|
||||||
* @property {Cartesian2} [pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Specifying the pixel offset in screen space from the origin of this billboard.
|
* @property {Cartesian2} [pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Specifying the pixel offset in screen space from the origin of this billboard.
|
||||||
* @property {Cartesian3} [eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Specifying the 3D Cartesian offset applied to this billboard in eye coordinates.
|
* @property {Cartesian3} [eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Specifying the 3D Cartesian offset applied to this billboard in eye coordinates.
|
||||||
|
|
@ -189,31 +191,27 @@ function Billboard(options, billboardCollection) {
|
||||||
this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
|
this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
|
||||||
|
|
||||||
this._imageTexture = new BillboardTexture(billboardCollection);
|
this._imageTexture = new BillboardTexture(billboardCollection);
|
||||||
|
|
||||||
|
this._imageId = options.imageId;
|
||||||
this._imageWidth = undefined;
|
this._imageWidth = undefined;
|
||||||
this._imageHeight = undefined;
|
this._imageHeight = undefined;
|
||||||
|
|
||||||
this._labelDimensions = undefined;
|
this._labelDimensions = undefined;
|
||||||
this._labelHorizontalOrigin = undefined;
|
this._labelHorizontalOrigin = undefined;
|
||||||
this._labelTranslate = undefined;
|
this._labelTranslate = undefined;
|
||||||
|
|
||||||
const image = options.image;
|
const image = options.image;
|
||||||
let imageId = options.imageId;
|
|
||||||
if (defined(image)) {
|
if (defined(image)) {
|
||||||
if (!defined(imageId)) {
|
this._computeImageTextureProperties(options.imageId, image);
|
||||||
if (typeof image === "string") {
|
this._imageTexture.loadImage(
|
||||||
imageId = image;
|
this._imageId,
|
||||||
} else if (defined(image.src)) {
|
image,
|
||||||
imageId = image.src;
|
this._imageWidth,
|
||||||
} else {
|
this._imageHeight,
|
||||||
imageId = createGuid();
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._imageTexture.loadImage(imageId, image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined(options.imageSubRegion)) {
|
if (defined(options.imageSubRegion)) {
|
||||||
this._imageTexture.addImageSubRegion(imageId, options.imageSubRegion);
|
this._imageTexture.addImageSubRegion(this._imageId, options.imageSubRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._actualClampedPosition = undefined;
|
this._actualClampedPosition = undefined;
|
||||||
|
|
@ -947,18 +945,13 @@ Object.defineProperties(Billboard.prototype, {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let id;
|
this._computeImageTextureProperties(undefined, value);
|
||||||
if (typeof value === "string") {
|
this._imageTexture.loadImage(
|
||||||
id = value;
|
this._imageId,
|
||||||
} else if (value instanceof Resource) {
|
value,
|
||||||
id = value._url;
|
this._imageWidth,
|
||||||
} else if (defined(value.src)) {
|
this._imageHeight,
|
||||||
id = value.src;
|
);
|
||||||
} else {
|
|
||||||
id = createGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._imageTexture.loadImage(id, value);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1252,7 +1245,13 @@ Billboard.prototype.setImage = function (id, image) {
|
||||||
Check.defined("image", image);
|
Check.defined("image", image);
|
||||||
//>>includeEnd('debug');
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
this._imageTexture.loadImage(id, image);
|
this._computeImageTextureProperties(id, image);
|
||||||
|
this._imageTexture.loadImage(
|
||||||
|
this._imageId,
|
||||||
|
image,
|
||||||
|
this._imageWidth,
|
||||||
|
this._imageHeight,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1268,6 +1267,54 @@ Billboard.prototype.setImageTexture = function (billboardTexture) {
|
||||||
BillboardTexture.clone(billboardTexture, this._imageTexture);
|
BillboardTexture.clone(billboardTexture, this._imageTexture);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Arbitrary limit on allocated SVG size, in pixels. Raster images use image resolution. */
|
||||||
|
const SVG_MAX_SIZE_PX = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes billboard texture ID, width, and height. For raster images, width and height are left
|
||||||
|
* undefined, defaulting to image resolution. For SVG, use billboard pixel width and height.
|
||||||
|
* @param {string | undefined} id The id of the image.
|
||||||
|
* @param {string | HTMLImageElement | HTMLCanvasElement | undefined} image A loaded HTMLImageElement, ImageData, or a url to an image to use for the billboard.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Billboard.prototype._computeImageTextureProperties = function (id, image) {
|
||||||
|
this._imageWidth = undefined;
|
||||||
|
this._imageHeight = undefined;
|
||||||
|
|
||||||
|
if (!defined(image)) {
|
||||||
|
this._imageId = createGuid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageUri;
|
||||||
|
if (typeof image === "string") {
|
||||||
|
imageUri = image;
|
||||||
|
} else if (image instanceof Resource) {
|
||||||
|
imageUri = image._url;
|
||||||
|
} else if (defined(image.src)) {
|
||||||
|
imageUri = image.src;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._imageId = id ?? imageUri ?? createGuid();
|
||||||
|
|
||||||
|
const hasSizeInPixels =
|
||||||
|
defined(this._width) && defined(this._height) && !this._sizeInMeters;
|
||||||
|
|
||||||
|
if (hasSizeInPixels && isSvgUri(imageUri)) {
|
||||||
|
this._imageWidth = Math.min(this._width, SVG_MAX_SIZE_PX);
|
||||||
|
this._imageHeight = Math.min(this._height, SVG_MAX_SIZE_PX);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSvgUri(uri) {
|
||||||
|
if (!defined(uri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isDataUri(uri)
|
||||||
|
? uri.startsWith("data:image/svg+xml")
|
||||||
|
: getExtensionFromUri(uri) === "svg";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses a sub-region of the image with the given id as the image for this billboard,
|
* Uses a sub-region of the image with the given id as the image for this billboard,
|
||||||
* measured in pixels from the bottom-left.
|
* measured in pixels from the bottom-left.
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,15 @@ BillboardTexture.prototype.unload = async function () {
|
||||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||||
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
|
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
|
||||||
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
|
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
|
||||||
|
* @param {number} width A number specifying the width of the texture. If undefined, the image width will be used.
|
||||||
|
* @param {number} height A number specifying the height of the texture. If undefined, the image height will be used.
|
||||||
*/
|
*/
|
||||||
BillboardTexture.prototype.loadImage = async function (id, image) {
|
BillboardTexture.prototype.loadImage = async function (
|
||||||
|
id,
|
||||||
|
image,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
) {
|
||||||
if (this._id === id) {
|
if (this._id === id) {
|
||||||
// This image has already been loaded
|
// This image has already been loaded
|
||||||
return;
|
return;
|
||||||
|
|
@ -192,7 +199,7 @@ BillboardTexture.prototype.loadImage = async function (id, image) {
|
||||||
let index;
|
let index;
|
||||||
const atlas = this._billboardCollection.textureAtlas;
|
const atlas = this._billboardCollection.textureAtlas;
|
||||||
try {
|
try {
|
||||||
index = await atlas.addImage(id, image);
|
index = await atlas.addImage(id, image, width, height);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// There was an error loading the image
|
// There was an error loading the image
|
||||||
billboardTexture._loadState = BillboardLoadState.ERROR;
|
billboardTexture._loadState = BillboardLoadState.ERROR;
|
||||||
|
|
@ -251,16 +258,38 @@ BillboardTexture.prototype.loadImage = async function (id, image) {
|
||||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||||
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||||
*/
|
*/
|
||||||
BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
|
BillboardTexture.prototype.addImageSubRegion = function (id, subRegion) {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._loadState = BillboardLoadState.LOADING;
|
|
||||||
this._loadError = undefined;
|
this._loadError = undefined;
|
||||||
this._hasSubregion = true;
|
this._hasSubregion = true;
|
||||||
|
|
||||||
let index;
|
|
||||||
const atlas = this._billboardCollection.textureAtlas;
|
const atlas = this._billboardCollection.textureAtlas;
|
||||||
|
const indexOrPromise = atlas.addImageSubRegion(id, subRegion);
|
||||||
|
|
||||||
|
if (typeof indexOrPromise === "number") {
|
||||||
|
this.setImageSubRegion(indexOrPromise, subRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadImageSubRegion(id, subRegion, indexOrPromise);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see {TextureAtlas#addImageSubRegion}
|
||||||
|
* @private
|
||||||
|
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||||
|
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||||
|
* @param {Promise<number>} indexPromise A promise that resolves to the image region index.
|
||||||
|
*/
|
||||||
|
BillboardTexture.prototype.loadImageSubRegion = async function (
|
||||||
|
id,
|
||||||
|
subRegion,
|
||||||
|
indexPromise,
|
||||||
|
) {
|
||||||
|
let index;
|
||||||
try {
|
try {
|
||||||
index = await atlas.addImageSubRegion(id, subRegion);
|
this._loadState = BillboardLoadState.LOADING;
|
||||||
|
index = await indexPromise;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// There was an error loading the referenced image
|
// There was an error loading the referenced image
|
||||||
this._loadState = BillboardLoadState.ERROR;
|
this._loadState = BillboardLoadState.ERROR;
|
||||||
|
|
@ -268,6 +297,27 @@ BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._id !== id) {
|
||||||
|
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadState = BillboardLoadState.LOADED;
|
||||||
|
|
||||||
|
this.setImageSubRegion(index, subRegion);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see {TextureAtlas#addImageSubRegion}
|
||||||
|
* @private
|
||||||
|
* @param {number} index The resolved index in the {@link TextureAtlas}
|
||||||
|
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||||
|
*/
|
||||||
|
BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
|
||||||
|
if (this._index === index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!defined(index) || index === -1) {
|
if (!defined(index) || index === -1) {
|
||||||
this._loadState = BillboardLoadState.FAILED;
|
this._loadState = BillboardLoadState.FAILED;
|
||||||
this._index = -1;
|
this._index = -1;
|
||||||
|
|
@ -280,7 +330,6 @@ BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
|
||||||
this._height = subRegion.height;
|
this._height = subRegion.height;
|
||||||
|
|
||||||
this._index = index;
|
this._index = index;
|
||||||
this._loadState = BillboardLoadState.LOADED;
|
|
||||||
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,6 @@ GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt = function (
|
||||||
tileset,
|
tileset,
|
||||||
) {
|
) {
|
||||||
let hasGaussianSplatExtension = false;
|
let hasGaussianSplatExtension = false;
|
||||||
let hasLegacyGaussianSplatExtension = false;
|
|
||||||
if (tileset.isGltfExtensionRequired instanceof Function) {
|
if (tileset.isGltfExtensionRequired instanceof Function) {
|
||||||
hasGaussianSplatExtension =
|
hasGaussianSplatExtension =
|
||||||
tileset.isGltfExtensionRequired("KHR_gaussian_splatting") &&
|
tileset.isGltfExtensionRequired("KHR_gaussian_splatting") &&
|
||||||
|
|
@ -133,22 +132,20 @@ GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt = function (
|
||||||
"KHR_gaussian_splatting_compression_spz_2",
|
"KHR_gaussian_splatting_compression_spz_2",
|
||||||
);
|
);
|
||||||
|
|
||||||
hasLegacyGaussianSplatExtension = tileset.isGltfExtensionRequired(
|
if (
|
||||||
"KHR_spz_gaussian_splats_compression",
|
tileset.isGltfExtensionRequired("KHR_spz_gaussian_splats_compression")
|
||||||
);
|
) {
|
||||||
|
deprecationWarning(
|
||||||
|
"KHR_spz_gaussian_splats_compression",
|
||||||
|
"Support for the original KHR_spz_gaussian_splats_compression extension has been removed in favor " +
|
||||||
|
"of the up to date KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 extensions" +
|
||||||
|
"\n\nPlease retile your tileset with the KHR_gaussian_splatting and " +
|
||||||
|
"KHR_gaussian_splatting_compression_spz_2 extensions.",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLegacyGaussianSplatExtension) {
|
return hasGaussianSplatExtension;
|
||||||
deprecationWarning(
|
|
||||||
"KHR_spz_gaussian_splats_compression",
|
|
||||||
"Support for the original KHR_spz_gaussian_splats_compression extension has been deprecated in favor " +
|
|
||||||
"of the up to date KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 extensions and will be " +
|
|
||||||
"removed in CesiumJS 1.135.\n\nPlease retile your tileset with the KHR_gaussian_splatting and " +
|
|
||||||
"KHR_gaussian_splatting_compression_spz_2 extensions.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasGaussianSplatExtension || hasLegacyGaussianSplatExtension;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperties(GaussianSplat3DTileContent.prototype, {
|
Object.defineProperties(GaussianSplat3DTileContent.prototype, {
|
||||||
|
|
|
||||||
|
|
@ -943,7 +943,9 @@ Globe.prototype.getHeight = function (cartographic) {
|
||||||
|
|
||||||
const intersection = tile.data.pick(
|
const intersection = tile.data.pick(
|
||||||
ray,
|
ray,
|
||||||
undefined,
|
// Globe height is the same at a given cartographic regardless of the scene mode,
|
||||||
|
// but the ray is constructed via a surface normal (which assumes 3D), so pick in 3D mode.
|
||||||
|
SceneMode.SCENE3D,
|
||||||
projection,
|
projection,
|
||||||
false,
|
false,
|
||||||
scratchGetHeightIntersection,
|
scratchGetHeightIntersection,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import BoundingSphere from "../Core/BoundingSphere.js";
|
import BoundingSphere from "../Core/BoundingSphere.js";
|
||||||
import Cartesian3 from "../Core/Cartesian3.js";
|
import Cartesian3 from "../Core/Cartesian3.js";
|
||||||
import Cartesian4 from "../Core/Cartesian4.js";
|
import Cartesian4 from "../Core/Cartesian4.js";
|
||||||
import Cartographic from "../Core/Cartographic.js";
|
|
||||||
import defined from "../Core/defined.js";
|
import defined from "../Core/defined.js";
|
||||||
import IndexDatatype from "../Core/IndexDatatype.js";
|
import IndexDatatype from "../Core/IndexDatatype.js";
|
||||||
import IntersectionTests from "../Core/IntersectionTests.js";
|
|
||||||
import PixelFormat from "../Core/PixelFormat.js";
|
import PixelFormat from "../Core/PixelFormat.js";
|
||||||
import Ray from "../Core/Ray.js";
|
|
||||||
import Request from "../Core/Request.js";
|
import Request from "../Core/Request.js";
|
||||||
import RequestState from "../Core/RequestState.js";
|
import RequestState from "../Core/RequestState.js";
|
||||||
import RequestType from "../Core/RequestType.js";
|
import RequestType from "../Core/RequestType.js";
|
||||||
|
|
@ -23,7 +20,6 @@ import TextureWrap from "../Renderer/TextureWrap.js";
|
||||||
import VertexArray from "../Renderer/VertexArray.js";
|
import VertexArray from "../Renderer/VertexArray.js";
|
||||||
import ImageryState from "./ImageryState.js";
|
import ImageryState from "./ImageryState.js";
|
||||||
import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
|
import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
|
||||||
import SceneMode from "./SceneMode.js";
|
|
||||||
import TerrainState from "./TerrainState.js";
|
import TerrainState from "./TerrainState.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -126,33 +122,6 @@ Object.defineProperties(GlobeSurfaceTile.prototype, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const scratchCartographic = new Cartographic();
|
|
||||||
|
|
||||||
function getPosition(encoding, mode, projection, vertices, index, result) {
|
|
||||||
let position = encoding.getExaggeratedPosition(vertices, index, result);
|
|
||||||
|
|
||||||
if (defined(mode) && mode !== SceneMode.SCENE3D) {
|
|
||||||
const ellipsoid = projection.ellipsoid;
|
|
||||||
const positionCartographic = ellipsoid.cartesianToCartographic(
|
|
||||||
position,
|
|
||||||
scratchCartographic,
|
|
||||||
);
|
|
||||||
position = projection.project(positionCartographic, result);
|
|
||||||
position = Cartesian3.fromElements(
|
|
||||||
position.z,
|
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
result,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scratchV0 = new Cartesian3();
|
|
||||||
const scratchV1 = new Cartesian3();
|
|
||||||
const scratchV2 = new Cartesian3();
|
|
||||||
|
|
||||||
GlobeSurfaceTile.prototype.pick = function (
|
GlobeSurfaceTile.prototype.pick = function (
|
||||||
ray,
|
ray,
|
||||||
mode,
|
mode,
|
||||||
|
|
@ -160,42 +129,11 @@ GlobeSurfaceTile.prototype.pick = function (
|
||||||
cullBackFaces,
|
cullBackFaces,
|
||||||
result,
|
result,
|
||||||
) {
|
) {
|
||||||
const mesh = this.renderedMesh;
|
if (!defined(this.renderedMesh)) {
|
||||||
if (!defined(mesh)) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
const value = this.renderedMesh.pick(ray, cullBackFaces, mode, projection);
|
||||||
const vertices = mesh.vertices;
|
return Cartesian3.clone(value, result);
|
||||||
const indices = mesh.indices;
|
|
||||||
const encoding = mesh.encoding;
|
|
||||||
const indicesLength = indices.length;
|
|
||||||
|
|
||||||
let minT = Number.MAX_VALUE;
|
|
||||||
|
|
||||||
for (let i = 0; i < indicesLength; i += 3) {
|
|
||||||
const i0 = indices[i];
|
|
||||||
const i1 = indices[i + 1];
|
|
||||||
const i2 = indices[i + 2];
|
|
||||||
|
|
||||||
const v0 = getPosition(encoding, mode, projection, vertices, i0, scratchV0);
|
|
||||||
const v1 = getPosition(encoding, mode, projection, vertices, i1, scratchV1);
|
|
||||||
const v2 = getPosition(encoding, mode, projection, vertices, i2, scratchV2);
|
|
||||||
|
|
||||||
const t = IntersectionTests.rayTriangleParametric(
|
|
||||||
ray,
|
|
||||||
v0,
|
|
||||||
v1,
|
|
||||||
v2,
|
|
||||||
cullBackFaces,
|
|
||||||
);
|
|
||||||
if (defined(t) && t < minT && t >= 0.0) {
|
|
||||||
minT = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minT !== Number.MAX_VALUE
|
|
||||||
? Ray.getPoint(ray, minT, result)
|
|
||||||
: undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GlobeSurfaceTile.prototype.freeResources = function () {
|
GlobeSurfaceTile.prototype.freeResources = function () {
|
||||||
|
|
@ -473,8 +411,10 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
|
||||||
encoding.exaggeration !== exaggeration;
|
encoding.exaggeration !== exaggeration;
|
||||||
const encodingRelativeHeightChanged =
|
const encodingRelativeHeightChanged =
|
||||||
encoding.exaggerationRelativeHeight !== exaggerationRelativeHeight;
|
encoding.exaggerationRelativeHeight !== exaggerationRelativeHeight;
|
||||||
|
const exaggerationChanged =
|
||||||
|
encodingExaggerationScaleChanged || encodingRelativeHeightChanged;
|
||||||
|
|
||||||
if (encodingExaggerationScaleChanged || encodingRelativeHeightChanged) {
|
if (exaggerationChanged) {
|
||||||
// Turning exaggeration scale on/off requires adding or removing geodetic surface normals
|
// Turning exaggeration scale on/off requires adding or removing geodetic surface normals
|
||||||
// Relative height only translates, so it has no effect on normals
|
// Relative height only translates, so it has no effect on normals
|
||||||
if (encodingExaggerationScaleChanged) {
|
if (encodingExaggerationScaleChanged) {
|
||||||
|
|
@ -498,9 +438,21 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
|
||||||
data.level = -1;
|
data.level = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mesh.updateExaggeration(exaggeration, exaggerationRelativeHeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
GlobeSurfaceTile.prototype.updateSceneMode = function (mode) {
|
||||||
|
const surfaceTile = this;
|
||||||
|
const mesh = surfaceTile.renderedMesh;
|
||||||
|
if (mesh === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.updateSceneMode(mode);
|
||||||
|
};
|
||||||
|
|
||||||
function prepareNewTile(tile, terrainProvider, imageryLayerCollection) {
|
function prepareNewTile(tile, terrainProvider, imageryLayerCollection) {
|
||||||
let available = terrainProvider.getTileDataAvailable(
|
let available = terrainProvider.getTileDataAvailable(
|
||||||
tile.x,
|
tile.x,
|
||||||
|
|
@ -929,7 +881,15 @@ function createWaterMaskTextureIfNeeded(context, surfaceTile) {
|
||||||
let texture;
|
let texture;
|
||||||
|
|
||||||
const waterMaskLength = waterMask.length;
|
const waterMaskLength = waterMask.length;
|
||||||
if (waterMaskLength === 1) {
|
if (waterMask instanceof ImageBitmap) {
|
||||||
|
texture = Texture.create({
|
||||||
|
context: context,
|
||||||
|
source: waterMask,
|
||||||
|
sampler: waterMaskData.sampler,
|
||||||
|
flipY: false,
|
||||||
|
skipColorSpaceConversion: true,
|
||||||
|
});
|
||||||
|
} else if (waterMaskLength === 1) {
|
||||||
// Length 1 means the tile is entirely land or entirely water.
|
// Length 1 means the tile is entirely land or entirely water.
|
||||||
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
|
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
|
||||||
if (waterMask[0] !== 0) {
|
if (waterMask[0] !== 0) {
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@ function GlobeSurfaceTileProvider(options) {
|
||||||
|
|
||||||
this._oldVerticalExaggeration = undefined;
|
this._oldVerticalExaggeration = undefined;
|
||||||
this._oldVerticalExaggerationRelativeHeight = undefined;
|
this._oldVerticalExaggerationRelativeHeight = undefined;
|
||||||
|
this._oldSceneMode = SceneMode.SCENE3D;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(GlobeSurfaceTileProvider.prototype, {
|
Object.defineProperties(GlobeSurfaceTileProvider.prototype, {
|
||||||
|
|
@ -514,6 +515,16 @@ GlobeSurfaceTileProvider.prototype.endUpdate = function (frameState) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sceneModeChanged = this._oldSceneMode !== frameState.mode;
|
||||||
|
this._oldSceneMode = frameState.mode;
|
||||||
|
|
||||||
|
if (sceneModeChanged) {
|
||||||
|
quadtree.forEachLoadedTile(function (tile) {
|
||||||
|
const surfaceTile = tile.data;
|
||||||
|
surfaceTile.updateSceneMode(frameState.mode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add the tile render commands to the command list, sorted by texture count.
|
// Add the tile render commands to the command list, sorted by texture count.
|
||||||
const tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
|
const tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
|
||||||
for (
|
for (
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,12 @@ Object.defineProperties(GltfBufferViewLoader.prototype, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the resources associated with the loader.
|
||||||
|
* @private
|
||||||
|
* @param {GltfBufferViewLoader} loader
|
||||||
|
* @returns {Promise<GltfBufferViewLoader>}
|
||||||
|
*/
|
||||||
async function loadResources(loader) {
|
async function loadResources(loader) {
|
||||||
try {
|
try {
|
||||||
const bufferLoader = getBufferLoader(loader);
|
const bufferLoader = getBufferLoader(loader);
|
||||||
|
|
@ -190,9 +196,18 @@ GltfBufferViewLoader.prototype.load = async function () {
|
||||||
return this._promise;
|
return this._promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the buffer loader for the specified buffer view loader.
|
||||||
|
* Attempts to retrieve from the resource cache first. If a buffer loader is
|
||||||
|
* not found, creates a new buffer loader and adds it to the resource cache.
|
||||||
|
* @private
|
||||||
|
* @param {GltfBufferViewLoader} bufferViewLoader The loader.
|
||||||
|
* @returns {BufferLoader} The buffer loader.
|
||||||
|
*/
|
||||||
function getBufferLoader(bufferViewLoader) {
|
function getBufferLoader(bufferViewLoader) {
|
||||||
const resourceCache = bufferViewLoader._resourceCache;
|
const resourceCache = bufferViewLoader._resourceCache;
|
||||||
const buffer = bufferViewLoader._buffer;
|
const buffer = bufferViewLoader._buffer;
|
||||||
|
|
||||||
if (defined(buffer.uri)) {
|
if (defined(buffer.uri)) {
|
||||||
const baseResource = bufferViewLoader._baseResource;
|
const baseResource = bufferViewLoader._baseResource;
|
||||||
const resource = baseResource.getDerivedResource({
|
const resource = baseResource.getDerivedResource({
|
||||||
|
|
@ -202,9 +217,12 @@ function getBufferLoader(bufferViewLoader) {
|
||||||
resource: resource,
|
resource: resource,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = buffer.extras?._pipeline?.source;
|
||||||
return resourceCache.getEmbeddedBufferLoader({
|
return resourceCache.getEmbeddedBufferLoader({
|
||||||
parentResource: bufferViewLoader._gltfResource,
|
parentResource: bufferViewLoader._gltfResource,
|
||||||
bufferId: bufferViewLoader._bufferId,
|
bufferId: bufferViewLoader._bufferId,
|
||||||
|
typedArray: source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1999,11 +1999,6 @@ function fetchSpzExtensionFrom(extensions) {
|
||||||
return spz;
|
return spz;
|
||||||
}
|
}
|
||||||
|
|
||||||
const legacySpz = extensions?.KHR_spz_gaussian_splats_compression;
|
|
||||||
if (defined(legacySpz)) {
|
|
||||||
return legacySpz;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ function Google2DImageryProvider(options) {
|
||||||
key: encodeURIComponent(options.key),
|
key: encodeURIComponent(options.key),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._resource = resource.clone();
|
||||||
|
|
||||||
let credit;
|
let credit;
|
||||||
if (defined(options.credit)) {
|
if (defined(options.credit)) {
|
||||||
credit = options.credit;
|
credit = options.credit;
|
||||||
|
|
@ -312,7 +314,7 @@ Object.defineProperties(Google2DImageryProvider.prototype, {
|
||||||
* });
|
* });
|
||||||
* @example
|
* @example
|
||||||
* // Google 2D roadmap overlay with custom styles
|
* // Google 2D roadmap overlay with custom styles
|
||||||
* const googleTileProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
|
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
|
||||||
* assetId: 3830184,
|
* assetId: 3830184,
|
||||||
* overlayLayerType: "layerRoadmap",
|
* overlayLayerType: "layerRoadmap",
|
||||||
* styles: [
|
* styles: [
|
||||||
|
|
@ -403,7 +405,7 @@ Google2DImageryProvider.fromIonAssetId = async function (options) {
|
||||||
* // Google 2D roadmap overlay with custom styles
|
* // Google 2D roadmap overlay with custom styles
|
||||||
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
|
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
|
||||||
*
|
*
|
||||||
* const googleTileProvider = Cesium.Google2DImageryProvider.fromUrl({
|
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromUrl({
|
||||||
* overlayLayerType: "layerRoadmap",
|
* overlayLayerType: "layerRoadmap",
|
||||||
* styles: [
|
* styles: [
|
||||||
* {
|
* {
|
||||||
|
|
@ -533,12 +535,7 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let level = 0; level < maximumLevel + 1; level++) {
|
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||||
promises.push(
|
promises.push(
|
||||||
fetchViewportAttribution(
|
fetchViewportAttribution(this._resource, this._viewportUrl, level),
|
||||||
this._viewportUrl,
|
|
||||||
this._key,
|
|
||||||
this._session,
|
|
||||||
level,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const results = await Promise.all(promises);
|
const results = await Promise.all(promises);
|
||||||
|
|
@ -559,12 +556,10 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
|
||||||
return attributionsByLevel;
|
return attributionsByLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetchViewportAttribution(url, key, session, level) {
|
async function fetchViewportAttribution(resource, url, level) {
|
||||||
const viewport = await Resource.fetch({
|
const viewportResource = resource.getDerivedResource({
|
||||||
url: url,
|
url,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
key,
|
|
||||||
session,
|
|
||||||
zoom: level,
|
zoom: level,
|
||||||
north: 90,
|
north: 90,
|
||||||
south: -90,
|
south: -90,
|
||||||
|
|
@ -573,7 +568,7 @@ async function fetchViewportAttribution(url, key, session, level) {
|
||||||
},
|
},
|
||||||
data: JSON.stringify(Frozen.EMPTY_OBJECT),
|
data: JSON.stringify(Frozen.EMPTY_OBJECT),
|
||||||
});
|
});
|
||||||
const viewportJson = JSON.parse(viewport);
|
const viewportJson = await viewportResource.fetchJson();
|
||||||
return viewportJson.copyright;
|
return viewportJson.copyright;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,6 @@ ModelUtility.supportedExtensions = {
|
||||||
KHR_texture_transform: true,
|
KHR_texture_transform: true,
|
||||||
KHR_gaussian_splatting: true,
|
KHR_gaussian_splatting: true,
|
||||||
KHR_gaussian_splatting_compression_spz_2: true,
|
KHR_gaussian_splatting_compression_spz_2: true,
|
||||||
KHR_spz_gaussian_splats_compression: true,
|
|
||||||
WEB3D_quantized_attributes: true,
|
WEB3D_quantized_attributes: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ import defined from "../Core/defined.js";
|
||||||
import destroyObject from "../Core/destroyObject.js";
|
import destroyObject from "../Core/destroyObject.js";
|
||||||
import FramebufferManager from "../Renderer/FramebufferManager.js";
|
import FramebufferManager from "../Renderer/FramebufferManager.js";
|
||||||
import PassState from "../Renderer/PassState.js";
|
import PassState from "../Renderer/PassState.js";
|
||||||
|
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||||
|
import PixelFormat from "../Core/PixelFormat.js";
|
||||||
|
import Sync from "../Renderer/Sync.js";
|
||||||
|
import RuntimeError from "../Core/RuntimeError.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|
@ -27,47 +31,18 @@ function PickFramebuffer(context) {
|
||||||
this._height = 0;
|
this._height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) {
|
|
||||||
const context = this._context;
|
|
||||||
const { width, height } = viewport;
|
|
||||||
|
|
||||||
BoundingRectangle.clone(
|
|
||||||
screenSpaceRectangle,
|
|
||||||
this._passState.scissorTest.rectangle,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create or recreate renderbuffers and framebuffer used for picking
|
|
||||||
this._width = width;
|
|
||||||
this._height = height;
|
|
||||||
this._fb.update(context, width, height);
|
|
||||||
this._passState.framebuffer = this._fb.framebuffer;
|
|
||||||
|
|
||||||
this._passState.viewport.width = width;
|
|
||||||
this._passState.viewport.height = height;
|
|
||||||
|
|
||||||
return this._passState;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the picked objects rendered within a given rectangle.
|
* Return the picked object(s) rendered within a given rectangle.
|
||||||
*
|
*
|
||||||
* @param {BoundingRectangle} screenSpaceRectangle
|
* @private
|
||||||
|
* @param {object} context The active context.
|
||||||
|
* @param {Uint8Array|Uint16Array|Float32Array|Uint32Array} pixels The pixels in the specified rectangle.
|
||||||
|
* @param {number} width The rectangle width.
|
||||||
|
* @param {number} height The rectangle height.
|
||||||
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
||||||
* @returns {object[]} A list of rendered objects, ordered by distance to the middle of the rectangle.
|
* @returns {object[]} A list of rendered objects, ordered by distance to the middle of the rectangle.
|
||||||
*/
|
*/
|
||||||
PickFramebuffer.prototype.end = function (screenSpaceRectangle, limit = 1) {
|
function pickObjectsFromPixels(context, pixels, width, height, limit = 1) {
|
||||||
const width = screenSpaceRectangle.width ?? 1.0;
|
|
||||||
const height = screenSpaceRectangle.height ?? 1.0;
|
|
||||||
|
|
||||||
const context = this._context;
|
|
||||||
const pixels = context.readPixels({
|
|
||||||
x: screenSpaceRectangle.x,
|
|
||||||
y: screenSpaceRectangle.y,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
framebuffer: this._fb.framebuffer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const max = Math.max(width, height);
|
const max = Math.max(width, height);
|
||||||
const length = max * max;
|
const length = max * max;
|
||||||
const halfWidth = Math.floor(width * 0.5);
|
const halfWidth = Math.floor(width * 0.5);
|
||||||
|
|
@ -121,6 +96,119 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle, limit = 1) {
|
||||||
y += dy;
|
y += dy;
|
||||||
}
|
}
|
||||||
return [...objects];
|
return [...objects];
|
||||||
|
}
|
||||||
|
|
||||||
|
PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) {
|
||||||
|
const context = this._context;
|
||||||
|
const { width, height } = viewport;
|
||||||
|
|
||||||
|
BoundingRectangle.clone(
|
||||||
|
screenSpaceRectangle,
|
||||||
|
this._passState.scissorTest.rectangle,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create or recreate renderbuffers and framebuffer used for picking
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._fb.update(context, width, height);
|
||||||
|
this._passState.framebuffer = this._fb.framebuffer;
|
||||||
|
|
||||||
|
this._passState.viewport.width = width;
|
||||||
|
this._passState.viewport.height = height;
|
||||||
|
|
||||||
|
return this._passState;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the picked objects rendered within a given rectangle using asynchronously without stalling the GPU.
|
||||||
|
* Requires WebGL2.
|
||||||
|
*
|
||||||
|
* @param {BoundingRectangle} screenSpaceRectangle
|
||||||
|
* @param {FrameState} frameState
|
||||||
|
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
||||||
|
* @returns {Promise<object[]>} A list of rendered objects, ordered by distance to the middle of the rectangle.
|
||||||
|
*
|
||||||
|
* @exception {RuntimeError} Async Picking Request Timeout.
|
||||||
|
* @exception {DeveloperError} A WebGL 2 context is required.
|
||||||
|
*/
|
||||||
|
PickFramebuffer.prototype.endAsync = async function (
|
||||||
|
screenSpaceRectangle,
|
||||||
|
frameState,
|
||||||
|
limit = 1,
|
||||||
|
) {
|
||||||
|
const width = screenSpaceRectangle.width ?? 1.0;
|
||||||
|
const height = screenSpaceRectangle.height ?? 1.0;
|
||||||
|
|
||||||
|
const context = this._context;
|
||||||
|
const framebuffer = this._fb.framebuffer;
|
||||||
|
|
||||||
|
let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
|
||||||
|
let pixelFormat = PixelFormat.RGBA;
|
||||||
|
|
||||||
|
if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
|
||||||
|
pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
|
||||||
|
pixelFormat = framebuffer.getColorTexture(0).pixelFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pbo = context.readPixelsToPBO({
|
||||||
|
x: screenSpaceRectangle.x,
|
||||||
|
y: screenSpaceRectangle.y,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
framebuffer: framebuffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the GPU to signal that it is ready to readback the PBO data
|
||||||
|
try {
|
||||||
|
await sync.waitForSignal((next) => frameState.afterRender.push(next));
|
||||||
|
const pixels = PixelFormat.createTypedArray(
|
||||||
|
pixelFormat,
|
||||||
|
pixelDatatype,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
pbo.getBufferData(pixels);
|
||||||
|
const pickedObjects = pickObjectsFromPixels(
|
||||||
|
context,
|
||||||
|
pixels,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
limit,
|
||||||
|
);
|
||||||
|
return pickedObjects;
|
||||||
|
} catch (e) {
|
||||||
|
throw new RuntimeError("Async Picking Request Timeout");
|
||||||
|
} finally {
|
||||||
|
sync.destroy();
|
||||||
|
pbo.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the picked objects rendered within a given rectangle.
|
||||||
|
*
|
||||||
|
* @param {BoundingRectangle} screenSpaceRectangle
|
||||||
|
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
||||||
|
* @returns {object[]} A list of rendered objects, ordered by distance to the middle of the rectangle.
|
||||||
|
*/
|
||||||
|
PickFramebuffer.prototype.end = function (screenSpaceRectangle, limit = 1) {
|
||||||
|
const width = screenSpaceRectangle.width ?? 1.0;
|
||||||
|
const height = screenSpaceRectangle.height ?? 1.0;
|
||||||
|
|
||||||
|
const context = this._context;
|
||||||
|
const pixels = context.readPixels({
|
||||||
|
x: screenSpaceRectangle.x,
|
||||||
|
y: screenSpaceRectangle.y,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
framebuffer: this._fb.framebuffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
return pickObjectsFromPixels(context, pixels, width, height, limit);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import Color from "../Core/Color.js";
|
||||||
import defined from "../Core/defined.js";
|
import defined from "../Core/defined.js";
|
||||||
import DeveloperError from "../Core/DeveloperError.js";
|
import DeveloperError from "../Core/DeveloperError.js";
|
||||||
import Matrix4 from "../Core/Matrix4.js";
|
import Matrix4 from "../Core/Matrix4.js";
|
||||||
|
import oneTimeWarning from "../Core/oneTimeWarning.js";
|
||||||
import OrthographicFrustum from "../Core/OrthographicFrustum.js";
|
import OrthographicFrustum from "../Core/OrthographicFrustum.js";
|
||||||
import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
|
import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
|
||||||
import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
|
import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
|
||||||
|
|
@ -262,30 +263,21 @@ function computePickingDrawingBufferRectangle(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of objects with a <code>primitive</code> property that contains the first (top) primitives
|
* Setup needed before picking.
|
||||||
* in the scene at a particular window coordinate. Other properties may potentially be set depending on the
|
*
|
||||||
* type of primitive and may be used to further identify the picked object.
|
|
||||||
* <p>
|
|
||||||
* When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
|
|
||||||
* </p>
|
|
||||||
* @param {Scene} scene
|
* @param {Scene} scene
|
||||||
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
|
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
|
||||||
|
* @param {BoundingRectangle} drawingBufferRectangle The output drawing buffer recangle.
|
||||||
* @param {number} [width=3] Width of the pick rectangle.
|
* @param {number} [width=3] Width of the pick rectangle.
|
||||||
* @param {number} [height=3] Height of the pick rectangle.
|
* @param {number} [height=3] Height of the pick rectangle.
|
||||||
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
|
||||||
* @returns {object[]} List of objects containing the picked primitives.
|
|
||||||
*/
|
*/
|
||||||
Picking.prototype.pick = function (
|
function pickBegin(
|
||||||
scene,
|
scene,
|
||||||
windowPosition,
|
windowPosition,
|
||||||
|
drawingBufferRectangle,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
limit = 1,
|
|
||||||
) {
|
) {
|
||||||
//>>includeStart('debug', pragmas.debug);
|
|
||||||
Check.defined("windowPosition", windowPosition);
|
|
||||||
//>>includeEnd('debug');
|
|
||||||
|
|
||||||
const { context, frameState, defaultView } = scene;
|
const { context, frameState, defaultView } = scene;
|
||||||
const { viewport, pickFramebuffer } = defaultView;
|
const { viewport, pickFramebuffer } = defaultView;
|
||||||
|
|
||||||
|
|
@ -304,12 +296,12 @@ Picking.prototype.pick = function (
|
||||||
windowPosition,
|
windowPosition,
|
||||||
scratchPosition,
|
scratchPosition,
|
||||||
);
|
);
|
||||||
const drawingBufferRectangle = computePickingDrawingBufferRectangle(
|
computePickingDrawingBufferRectangle(
|
||||||
context.drawingBufferHeight,
|
context.drawingBufferHeight,
|
||||||
drawingBufferPosition,
|
drawingBufferPosition,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
scratchRectangle,
|
drawingBufferRectangle,
|
||||||
);
|
);
|
||||||
|
|
||||||
scene.jobScheduler.disableThisFrame();
|
scene.jobScheduler.disableThisFrame();
|
||||||
|
|
@ -334,9 +326,99 @@ Picking.prototype.pick = function (
|
||||||
|
|
||||||
scene.updateAndExecuteCommands(passState, scratchColorZero);
|
scene.updateAndExecuteCommands(passState, scratchColorZero);
|
||||||
scene.resolveFramebuffers(passState);
|
scene.resolveFramebuffers(passState);
|
||||||
|
}
|
||||||
|
|
||||||
const pickedObjects = pickFramebuffer.end(drawingBufferRectangle, limit);
|
/**
|
||||||
|
* Teardown needed after picking.
|
||||||
|
*
|
||||||
|
* @param {Scene} scene
|
||||||
|
*/
|
||||||
|
function pickEnd(scene) {
|
||||||
|
const { context } = scene;
|
||||||
context.endFrame();
|
context.endFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same operation as {@link Picking#pick}, but returns a Promise that resolves asynchronously without blocking the main render thread.
|
||||||
|
* Requires WebGL2 else using synchronous fallback.
|
||||||
|
*
|
||||||
|
* @see Picking#pick
|
||||||
|
*
|
||||||
|
* @param {Scene} scene
|
||||||
|
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
|
||||||
|
* @param {number} [width=3] Width of the pick rectangle.
|
||||||
|
* @param {number} [height=3] Height of the pick rectangle.
|
||||||
|
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
||||||
|
* @returns {Promise<object[]>} List of objects containing the picked primitives.
|
||||||
|
*
|
||||||
|
* @exception {RuntimeError} Async Picking Request Timeout.
|
||||||
|
*/
|
||||||
|
Picking.prototype.pickAsync = async function (
|
||||||
|
scene,
|
||||||
|
windowPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
limit = 1,
|
||||||
|
) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("windowPosition", windowPosition);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const { context, frameState, defaultView } = scene;
|
||||||
|
const { pickFramebuffer } = defaultView;
|
||||||
|
const drawingBufferRectangle = scratchRectangle;
|
||||||
|
pickBegin(scene, windowPosition, drawingBufferRectangle, width, height);
|
||||||
|
let pickedObjects;
|
||||||
|
if (context.webgl2) {
|
||||||
|
pickedObjects = pickFramebuffer.endAsync(
|
||||||
|
drawingBufferRectangle,
|
||||||
|
frameState,
|
||||||
|
limit,
|
||||||
|
); // Promise<Object[]>
|
||||||
|
} else {
|
||||||
|
pickedObjects = pickFramebuffer.end(drawingBufferRectangle, limit); // Object[]
|
||||||
|
pickedObjects = Promise.resolve(pickedObjects); // Promise<Object[]> Wrap as Promise
|
||||||
|
oneTimeWarning(
|
||||||
|
"picking-async-fallback",
|
||||||
|
"Fallback to synchronous picking because async operation requires WebGL2 context.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pickEnd(scene);
|
||||||
|
return pickedObjects;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of objects with a <code>primitive</code> property that contains the first (top) primitives
|
||||||
|
* in the scene at a particular window coordinate. Other properties may potentially be set depending on the
|
||||||
|
* type of primitive and may be used to further identify the picked object.
|
||||||
|
* <p>
|
||||||
|
* When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
|
||||||
|
* </p>
|
||||||
|
* @param {Scene} scene
|
||||||
|
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
|
||||||
|
* @param {number} [width=3] Width of the pick rectangle.
|
||||||
|
* @param {number} [height=3] Height of the pick rectangle.
|
||||||
|
* @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
|
||||||
|
* @returns {object[]} List of objects containing the picked primitives.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Picking.prototype.pick = function (
|
||||||
|
scene,
|
||||||
|
windowPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
limit = 1,
|
||||||
|
) {
|
||||||
|
//>>includeStart('debug', pragmas.debug);
|
||||||
|
Check.defined("windowPosition", windowPosition);
|
||||||
|
//>>includeEnd('debug');
|
||||||
|
|
||||||
|
const { defaultView } = scene;
|
||||||
|
const { pickFramebuffer } = defaultView;
|
||||||
|
const drawingBufferRectangle = scratchRectangle;
|
||||||
|
pickBegin(scene, windowPosition, drawingBufferRectangle, width, height);
|
||||||
|
const pickedObjects = pickFramebuffer.end(drawingBufferRectangle, limit); // Object[]
|
||||||
|
pickEnd(scene);
|
||||||
return pickedObjects;
|
return pickedObjects;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3932,14 +3932,15 @@ function callAfterRenderFunctions(scene) {
|
||||||
// Functions are queued up during primitive update and executed here in case
|
// Functions are queued up during primitive update and executed here in case
|
||||||
// the function modifies scene state that should remain constant over the frame.
|
// the function modifies scene state that should remain constant over the frame.
|
||||||
const functions = scene._frameState.afterRender;
|
const functions = scene._frameState.afterRender;
|
||||||
for (let i = 0; i < functions.length; ++i) {
|
const functionsCpy = functions.slice(); // Snapshot before iterate allows callbacks to add functions for next frame
|
||||||
const shouldRequestRender = functions[i]();
|
functions.length = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < functionsCpy.length; ++i) {
|
||||||
|
const shouldRequestRender = functionsCpy[i]();
|
||||||
if (shouldRequestRender) {
|
if (shouldRequestRender) {
|
||||||
scene.requestRender();
|
scene.requestRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
functions.length = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGlobeHeight(scene) {
|
function getGlobeHeight(scene) {
|
||||||
|
|
@ -4517,6 +4518,37 @@ Scene.prototype.pick = function (windowPosition, width, height) {
|
||||||
return this._picking.pick(this, windowPosition, width, height, 1)[0];
|
return this._picking.pick(this, windowPosition, width, height, 1)[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the same operation as Scene.pick but asynchonosly without blocking the main render thread.
|
||||||
|
* Requires WebGL2 else using fallback.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // On mouse over, color the feature yellow.
|
||||||
|
* handler.setInputAction(function(movement) {
|
||||||
|
* const feature = scene.pickAsync(movement.endPosition).then(function(feature) {
|
||||||
|
* if (feature instanceof Cesium.Cesium3DTileFeature) {
|
||||||
|
* feature.color = Cesium.Color.YELLOW;
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||||
|
*
|
||||||
|
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
|
||||||
|
* @param {number} [width=3] Width of the pick rectangle.
|
||||||
|
* @param {number} [height=3] Height of the pick rectangle.
|
||||||
|
* @returns {Promise<Object | undefined>} Object containing the picked primitive or <code>undefined</code> if nothing is at the location.
|
||||||
|
*
|
||||||
|
* @see Scene#pick
|
||||||
|
*/
|
||||||
|
Scene.prototype.pickAsync = async function (windowPosition, width, height) {
|
||||||
|
const result = await this._picking.pickAsync(
|
||||||
|
this,
|
||||||
|
windowPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
return result[0];
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Returns a {@link VoxelCell} for the voxel sample rendered at a particular window coordinate,
|
* Returns a {@link VoxelCell} for the voxel sample rendered at a particular window coordinate,
|
||||||
* or <code>undefined</code> if no voxel is rendered at that position.
|
* or <code>undefined</code> if no voxel is rendered at that position.
|
||||||
|
|
|
||||||
|
|
@ -1274,6 +1274,7 @@ function createFillMesh(tileProvider, frameState, tile, vertexArraysToDestroy) {
|
||||||
vertexCount,
|
vertexCount,
|
||||||
minimumHeight,
|
minimumHeight,
|
||||||
maximumHeight,
|
maximumHeight,
|
||||||
|
rectangle,
|
||||||
BoundingSphere.fromOrientedBoundingBox(obb),
|
BoundingSphere.fromOrientedBoundingBox(obb),
|
||||||
computeOccludeePoint(
|
computeOccludeePoint(
|
||||||
tileProvider,
|
tileProvider,
|
||||||
|
|
|
||||||
|
|
@ -172,38 +172,6 @@ function VoxelPrimitive(options) {
|
||||||
*/
|
*/
|
||||||
this._maxBoundsOld = new Cartesian3();
|
this._maxBoundsOld = new Cartesian3();
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum bounds with vertical exaggeration applied
|
|
||||||
*
|
|
||||||
* @type {Cartesian3}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._exaggeratedMinBounds = new Cartesian3();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to detect if the shape is dirty.
|
|
||||||
*
|
|
||||||
* @type {Cartesian3}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._exaggeratedMinBoundsOld = new Cartesian3();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum bounds with vertical exaggeration applied
|
|
||||||
*
|
|
||||||
* @type {Cartesian3}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._exaggeratedMaxBounds = new Cartesian3();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to detect if the shape is dirty.
|
|
||||||
*
|
|
||||||
* @type {Cartesian3}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._exaggeratedMaxBoundsOld = new Cartesian3();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This member is not known until the provider is ready.
|
* This member is not known until the provider is ready.
|
||||||
*
|
*
|
||||||
|
|
@ -238,6 +206,22 @@ function VoxelPrimitive(options) {
|
||||||
*/
|
*/
|
||||||
this._maxClippingBoundsOld = new Cartesian3();
|
this._maxClippingBoundsOld = new Cartesian3();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical exaggeration applied to the voxel shape
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._verticalExaggeration = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height relative to which the shape is exaggerated.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._verticalExaggerationRelativeHeight = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clipping planes on the primitive
|
* Clipping planes on the primitive
|
||||||
*
|
*
|
||||||
|
|
@ -271,30 +255,12 @@ function VoxelPrimitive(options) {
|
||||||
this._modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
|
this._modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model matrix with vertical exaggeration applied. Only used for BOX shape type.
|
* Used to detect if the model matrix is dirty.
|
||||||
*
|
*
|
||||||
* @type {Matrix4}
|
* @type {Matrix4}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._exaggeratedModelMatrix = Matrix4.clone(this._modelMatrix);
|
this._modelMatrixOld = Matrix4.clone(this._modelMatrix);
|
||||||
|
|
||||||
/**
|
|
||||||
* The primitive's model matrix multiplied by the provider's model matrix.
|
|
||||||
* This member is not known until the provider is ready.
|
|
||||||
*
|
|
||||||
* @type {Matrix4}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._compoundModelMatrix = new Matrix4();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to detect if the shape is dirty.
|
|
||||||
* This member is not known until the provider is ready.
|
|
||||||
*
|
|
||||||
* @type {Matrix4}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._compoundModelMatrixOld = new Matrix4();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {CustomShader}
|
* @type {CustomShader}
|
||||||
|
|
@ -622,21 +588,7 @@ function initialize(primitive, provider) {
|
||||||
primitive.minClippingBounds = minBounds.clone();
|
primitive.minClippingBounds = minBounds.clone();
|
||||||
primitive.maxClippingBounds = maxBounds.clone();
|
primitive.maxClippingBounds = maxBounds.clone();
|
||||||
|
|
||||||
// Initialize the exaggerated versions of bounds and model matrix
|
checkTransformAndBounds(primitive);
|
||||||
primitive._exaggeratedMinBounds = Cartesian3.clone(
|
|
||||||
primitive._minBounds,
|
|
||||||
primitive._exaggeratedMinBounds,
|
|
||||||
);
|
|
||||||
primitive._exaggeratedMaxBounds = Cartesian3.clone(
|
|
||||||
primitive._maxBounds,
|
|
||||||
primitive._exaggeratedMaxBounds,
|
|
||||||
);
|
|
||||||
primitive._exaggeratedModelMatrix = Matrix4.clone(
|
|
||||||
primitive._modelMatrix,
|
|
||||||
primitive._exaggeratedModelMatrix,
|
|
||||||
);
|
|
||||||
|
|
||||||
checkTransformAndBounds(primitive, provider);
|
|
||||||
|
|
||||||
// Create the shape object, and update it so it is valid for VoxelTraversal
|
// Create the shape object, and update it so it is valid for VoxelTraversal
|
||||||
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
|
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
|
||||||
|
|
@ -1176,13 +1128,12 @@ VoxelPrimitive.prototype.update = function (frameState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVerticalExaggeration(this, frameState);
|
|
||||||
|
|
||||||
// Check if the shape is dirty before updating it. This needs to happen every
|
// Check if the shape is dirty before updating it. This needs to happen every
|
||||||
// frame because the member variables can be modified externally via the
|
// frame because the member variables can be modified externally via the
|
||||||
// getters.
|
// getters.
|
||||||
const shapeDirty = checkTransformAndBounds(this, provider);
|
const shapeDirty = checkTransformAndBounds(this);
|
||||||
if (shapeDirty) {
|
const exaggerationChanged = updateVerticalExaggeration(this, frameState);
|
||||||
|
if (shapeDirty || exaggerationChanged) {
|
||||||
this._shapeVisible = updateShapeAndTransforms(this);
|
this._shapeVisible = updateShapeAndTransforms(this);
|
||||||
if (checkShapeDefines(this)) {
|
if (checkShapeDefines(this)) {
|
||||||
this._shaderDirty = true;
|
this._shaderDirty = true;
|
||||||
|
|
@ -1383,102 +1334,30 @@ function getTileCoordinates(primitive, positionLocal, result) {
|
||||||
const scratchExaggerationScale = new Cartesian3();
|
const scratchExaggerationScale = new Cartesian3();
|
||||||
const scratchExaggerationCenter = new Cartesian3();
|
const scratchExaggerationCenter = new Cartesian3();
|
||||||
const scratchCartographicCenter = new Cartographic();
|
const scratchCartographicCenter = new Cartographic();
|
||||||
const scratchExaggerationTranslation = new Cartesian3();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the exaggerated bounds of a primitive to account for vertical exaggeration
|
* Check for changes in the vertical exaggeration of the primitive
|
||||||
* @param {VoxelPrimitive} primitive
|
* @param {VoxelPrimitive} primitive The primitive to update
|
||||||
* @param {FrameState} frameState
|
* @param {FrameState} frameState The current frame state
|
||||||
|
* @returns {boolean} <code>true</code> if the exaggeration was changed
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function updateVerticalExaggeration(primitive, frameState) {
|
function updateVerticalExaggeration(primitive, frameState) {
|
||||||
primitive._exaggeratedMinBounds = Cartesian3.clone(
|
const { verticalExaggeration, verticalExaggerationRelativeHeight } =
|
||||||
primitive._minBounds,
|
frameState;
|
||||||
primitive._exaggeratedMinBounds,
|
|
||||||
);
|
|
||||||
primitive._exaggeratedMaxBounds = Cartesian3.clone(
|
|
||||||
primitive._maxBounds,
|
|
||||||
primitive._exaggeratedMaxBounds,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (primitive.shape === VoxelShapeType.ELLIPSOID) {
|
if (
|
||||||
// Apply the exaggeration by stretching the height bounds
|
primitive._verticalExaggeration === verticalExaggeration &&
|
||||||
const relativeHeight = frameState.verticalExaggerationRelativeHeight;
|
primitive._verticalExaggerationRelativeHeight ===
|
||||||
const exaggeration = frameState.verticalExaggeration;
|
verticalExaggerationRelativeHeight
|
||||||
primitive._exaggeratedMinBounds.z =
|
) {
|
||||||
(primitive._minBounds.z - relativeHeight) * exaggeration + relativeHeight;
|
return false;
|
||||||
primitive._exaggeratedMaxBounds.z =
|
|
||||||
(primitive._maxBounds.z - relativeHeight) * exaggeration + relativeHeight;
|
|
||||||
} else {
|
|
||||||
// Apply the exaggeration via the model matrix
|
|
||||||
const exaggerationScale = Cartesian3.fromElements(
|
|
||||||
1.0,
|
|
||||||
1.0,
|
|
||||||
frameState.verticalExaggeration,
|
|
||||||
scratchExaggerationScale,
|
|
||||||
);
|
|
||||||
primitive._exaggeratedModelMatrix = Matrix4.multiplyByScale(
|
|
||||||
primitive._modelMatrix,
|
|
||||||
exaggerationScale,
|
|
||||||
primitive._exaggeratedModelMatrix,
|
|
||||||
);
|
|
||||||
primitive._exaggeratedModelMatrix = Matrix4.multiplyByTranslation(
|
|
||||||
primitive._exaggeratedModelMatrix,
|
|
||||||
computeBoxExaggerationTranslation(primitive, frameState),
|
|
||||||
primitive._exaggeratedModelMatrix,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeBoxExaggerationTranslation(primitive, frameState) {
|
|
||||||
// Compute translation based on box center, relative height, and exaggeration
|
|
||||||
const {
|
|
||||||
shapeTransform = Matrix4.IDENTITY,
|
|
||||||
globalTransform = Matrix4.IDENTITY,
|
|
||||||
} = primitive._provider;
|
|
||||||
|
|
||||||
// Find the Cartesian position of the center of the OBB
|
|
||||||
const initialCenter = Matrix4.getTranslation(
|
|
||||||
shapeTransform,
|
|
||||||
scratchExaggerationCenter,
|
|
||||||
);
|
|
||||||
const intermediateCenter = Matrix4.multiplyByPoint(
|
|
||||||
primitive._modelMatrix,
|
|
||||||
initialCenter,
|
|
||||||
scratchExaggerationCenter,
|
|
||||||
);
|
|
||||||
const transformedCenter = Matrix4.multiplyByPoint(
|
|
||||||
globalTransform,
|
|
||||||
intermediateCenter,
|
|
||||||
scratchExaggerationCenter,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the cartographic height
|
|
||||||
const ellipsoid = Ellipsoid.WGS84;
|
|
||||||
const centerCartographic = ellipsoid.cartesianToCartographic(
|
|
||||||
transformedCenter,
|
|
||||||
scratchCartographicCenter,
|
|
||||||
);
|
|
||||||
|
|
||||||
let centerHeight = 0.0;
|
|
||||||
if (defined(centerCartographic)) {
|
|
||||||
centerHeight = centerCartographic.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the shift that will put the center in the right position relative
|
primitive._verticalExaggeration = verticalExaggeration;
|
||||||
// to relativeHeight, after it is scaled by verticalExaggeration
|
primitive._verticalExaggerationRelativeHeight =
|
||||||
const exaggeratedHeight = VerticalExaggeration.getHeight(
|
verticalExaggerationRelativeHeight;
|
||||||
centerHeight,
|
return true;
|
||||||
frameState.verticalExaggeration,
|
|
||||||
frameState.verticalExaggerationRelativeHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Cartesian3.fromElements(
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
(exaggeratedHeight - centerHeight) / frameState.verticalExaggeration,
|
|
||||||
scratchExaggerationTranslation,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1575,39 +1454,14 @@ function initFromProvider(primitive, provider, context) {
|
||||||
/**
|
/**
|
||||||
* Track changes in provider transform and primitive bounds
|
* Track changes in provider transform and primitive bounds
|
||||||
* @param {VoxelPrimitive} primitive
|
* @param {VoxelPrimitive} primitive
|
||||||
* @param {VoxelProvider} provider
|
|
||||||
* @returns {boolean} Whether any of the transform or bounds changed
|
* @returns {boolean} Whether any of the transform or bounds changed
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function checkTransformAndBounds(primitive, provider) {
|
function checkTransformAndBounds(primitive) {
|
||||||
const shapeTransform = provider.shapeTransform ?? Matrix4.IDENTITY;
|
|
||||||
const globalTransform = provider.globalTransform ?? Matrix4.IDENTITY;
|
|
||||||
|
|
||||||
// Compound model matrix = global transform * model matrix * shape transform
|
|
||||||
Matrix4.multiplyTransformation(
|
|
||||||
globalTransform,
|
|
||||||
primitive._exaggeratedModelMatrix,
|
|
||||||
primitive._compoundModelMatrix,
|
|
||||||
);
|
|
||||||
Matrix4.multiplyTransformation(
|
|
||||||
primitive._compoundModelMatrix,
|
|
||||||
shapeTransform,
|
|
||||||
primitive._compoundModelMatrix,
|
|
||||||
);
|
|
||||||
const numChanges =
|
const numChanges =
|
||||||
updateBound(primitive, "_compoundModelMatrix", "_compoundModelMatrixOld") +
|
updateBound(primitive, "_modelMatrix", "_modelMatrixOld") +
|
||||||
updateBound(primitive, "_minBounds", "_minBoundsOld") +
|
updateBound(primitive, "_minBounds", "_minBoundsOld") +
|
||||||
updateBound(primitive, "_maxBounds", "_maxBoundsOld") +
|
updateBound(primitive, "_maxBounds", "_maxBoundsOld") +
|
||||||
updateBound(
|
|
||||||
primitive,
|
|
||||||
"_exaggeratedMinBounds",
|
|
||||||
"_exaggeratedMinBoundsOld",
|
|
||||||
) +
|
|
||||||
updateBound(
|
|
||||||
primitive,
|
|
||||||
"_exaggeratedMaxBounds",
|
|
||||||
"_exaggeratedMaxBoundsOld",
|
|
||||||
) +
|
|
||||||
updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") +
|
updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") +
|
||||||
updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld");
|
updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld");
|
||||||
return numChanges > 0;
|
return numChanges > 0;
|
||||||
|
|
@ -1633,6 +1487,13 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
|
||||||
return changed ? 1 : 0;
|
return changed ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scratchExaggeratedMinBounds = new Cartesian3();
|
||||||
|
const scratchExaggeratedMaxBounds = new Cartesian3();
|
||||||
|
const scratchExaggeratedMinClippingBounds = new Cartesian3();
|
||||||
|
const scratchExaggeratedMaxClippingBounds = new Cartesian3();
|
||||||
|
const scratchExaggeratedModelMatrix = new Matrix4();
|
||||||
|
const scratchCompoundModelMatrix = new Matrix4();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the shape and related transforms
|
* Update the shape and related transforms
|
||||||
* @param {VoxelPrimitive} primitive
|
* @param {VoxelPrimitive} primitive
|
||||||
|
|
@ -1640,13 +1501,95 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function updateShapeAndTransforms(primitive) {
|
function updateShapeAndTransforms(primitive) {
|
||||||
|
const verticalExaggeration = primitive._verticalExaggeration;
|
||||||
|
const verticalExaggerationRelativeHeight =
|
||||||
|
primitive._verticalExaggerationRelativeHeight;
|
||||||
|
const exaggeratedMinBounds = Cartesian3.clone(
|
||||||
|
primitive._minBounds,
|
||||||
|
scratchExaggeratedMinBounds,
|
||||||
|
);
|
||||||
|
const exaggeratedMaxBounds = Cartesian3.clone(
|
||||||
|
primitive._maxBounds,
|
||||||
|
scratchExaggeratedMaxBounds,
|
||||||
|
);
|
||||||
|
const exaggeratedMinClippingBounds = Cartesian3.clone(
|
||||||
|
primitive._minClippingBounds,
|
||||||
|
scratchExaggeratedMinClippingBounds,
|
||||||
|
);
|
||||||
|
const exaggeratedMaxClippingBounds = Cartesian3.clone(
|
||||||
|
primitive._maxClippingBounds,
|
||||||
|
scratchExaggeratedMaxClippingBounds,
|
||||||
|
);
|
||||||
|
const exaggeratedModelMatrix = Matrix4.clone(
|
||||||
|
primitive._modelMatrix,
|
||||||
|
scratchExaggeratedModelMatrix,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (primitive.shape === VoxelShapeType.ELLIPSOID) {
|
||||||
|
// Apply the exaggeration by stretching the height bounds
|
||||||
|
exaggeratedMinBounds.z = VerticalExaggeration.getHeight(
|
||||||
|
primitive._minBounds.z,
|
||||||
|
verticalExaggeration,
|
||||||
|
verticalExaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
exaggeratedMaxBounds.z = VerticalExaggeration.getHeight(
|
||||||
|
primitive._maxBounds.z,
|
||||||
|
verticalExaggeration,
|
||||||
|
verticalExaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
exaggeratedMinClippingBounds.z = VerticalExaggeration.getHeight(
|
||||||
|
primitive._minClippingBounds.z,
|
||||||
|
verticalExaggeration,
|
||||||
|
verticalExaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
exaggeratedMaxClippingBounds.z = VerticalExaggeration.getHeight(
|
||||||
|
primitive._maxClippingBounds.z,
|
||||||
|
verticalExaggeration,
|
||||||
|
verticalExaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Apply the exaggeration via the model matrix
|
||||||
|
const exaggerationScale = Cartesian3.fromElements(
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
verticalExaggeration,
|
||||||
|
scratchExaggerationScale,
|
||||||
|
);
|
||||||
|
Matrix4.multiplyByScale(
|
||||||
|
exaggeratedModelMatrix,
|
||||||
|
exaggerationScale,
|
||||||
|
exaggeratedModelMatrix,
|
||||||
|
);
|
||||||
|
Matrix4.multiplyByTranslation(
|
||||||
|
exaggeratedModelMatrix,
|
||||||
|
computeBoxExaggerationTranslation(primitive),
|
||||||
|
exaggeratedModelMatrix,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = primitive._provider;
|
||||||
|
const shapeTransform = provider.shapeTransform ?? Matrix4.IDENTITY;
|
||||||
|
const globalTransform = provider.globalTransform ?? Matrix4.IDENTITY;
|
||||||
|
|
||||||
|
// Compound model matrix = global transform * model matrix * shape transform
|
||||||
|
const compoundModelMatrix = Matrix4.multiplyTransformation(
|
||||||
|
globalTransform,
|
||||||
|
exaggeratedModelMatrix,
|
||||||
|
scratchCompoundModelMatrix,
|
||||||
|
);
|
||||||
|
Matrix4.multiplyTransformation(
|
||||||
|
compoundModelMatrix,
|
||||||
|
shapeTransform,
|
||||||
|
compoundModelMatrix,
|
||||||
|
);
|
||||||
|
|
||||||
const shape = primitive._shape;
|
const shape = primitive._shape;
|
||||||
const visible = shape.update(
|
const visible = shape.update(
|
||||||
primitive._compoundModelMatrix,
|
compoundModelMatrix,
|
||||||
primitive._exaggeratedMinBounds,
|
exaggeratedMinBounds,
|
||||||
primitive._exaggeratedMaxBounds,
|
exaggeratedMaxBounds,
|
||||||
primitive.minClippingBounds,
|
exaggeratedMinClippingBounds,
|
||||||
primitive.maxClippingBounds,
|
exaggeratedMaxClippingBounds,
|
||||||
);
|
);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1668,6 +1611,70 @@ function updateShapeAndTransforms(primitive) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scratchExaggerationTranslation = new Cartesian3();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the translation to apply to box shapes to account for vertical exaggeration
|
||||||
|
*
|
||||||
|
* @param {VoxelPrimitive} primitive
|
||||||
|
* @returns {Cartesian3} The translation to apply to the box to account for vertical exaggeration
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function computeBoxExaggerationTranslation(primitive) {
|
||||||
|
const verticalExaggeration = primitive._verticalExaggeration;
|
||||||
|
const verticalExaggerationRelativeHeight =
|
||||||
|
primitive._verticalExaggerationRelativeHeight;
|
||||||
|
|
||||||
|
// Compute translation based on box center, relative height, and exaggeration
|
||||||
|
const {
|
||||||
|
shapeTransform = Matrix4.IDENTITY,
|
||||||
|
globalTransform = Matrix4.IDENTITY,
|
||||||
|
} = primitive._provider;
|
||||||
|
|
||||||
|
// Find the Cartesian position of the center of the OBB
|
||||||
|
const initialCenter = Matrix4.getTranslation(
|
||||||
|
shapeTransform,
|
||||||
|
scratchExaggerationCenter,
|
||||||
|
);
|
||||||
|
const intermediateCenter = Matrix4.multiplyByPoint(
|
||||||
|
primitive._modelMatrix,
|
||||||
|
initialCenter,
|
||||||
|
scratchExaggerationCenter,
|
||||||
|
);
|
||||||
|
const transformedCenter = Matrix4.multiplyByPoint(
|
||||||
|
globalTransform,
|
||||||
|
intermediateCenter,
|
||||||
|
scratchExaggerationCenter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the cartographic height
|
||||||
|
const ellipsoid = Ellipsoid.WGS84;
|
||||||
|
const centerCartographic = ellipsoid.cartesianToCartographic(
|
||||||
|
transformedCenter,
|
||||||
|
scratchCartographicCenter,
|
||||||
|
);
|
||||||
|
|
||||||
|
let centerHeight = 0.0;
|
||||||
|
if (defined(centerCartographic)) {
|
||||||
|
centerHeight = centerCartographic.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the shift that will put the center in the right position relative
|
||||||
|
// to relativeHeight, after it is scaled by verticalExaggeration
|
||||||
|
const exaggeratedHeight = VerticalExaggeration.getHeight(
|
||||||
|
centerHeight,
|
||||||
|
verticalExaggeration,
|
||||||
|
verticalExaggerationRelativeHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Cartesian3.fromElements(
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
(exaggeratedHeight - centerHeight) / verticalExaggeration,
|
||||||
|
scratchExaggerationTranslation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set uniforms that come from the traversal.
|
* Set uniforms that come from the traversal.
|
||||||
* @param {VoxelTraversal} traversal
|
* @param {VoxelTraversal} traversal
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ Object.defineProperties(VoxelShape.prototype, {
|
||||||
* @param {Matrix4} modelMatrix The model matrix.
|
* @param {Matrix4} modelMatrix The model matrix.
|
||||||
* @param {Cartesian3} minBounds The minimum bounds.
|
* @param {Cartesian3} minBounds The minimum bounds.
|
||||||
* @param {Cartesian3} maxBounds The maximum bounds.
|
* @param {Cartesian3} maxBounds The maximum bounds.
|
||||||
|
* @param {Cartesian3} [clipMinBounds] The minimum clip bounds.
|
||||||
|
* @param {Cartesian3} [clipMaxBounds] The maximum clip bounds.
|
||||||
* @returns {boolean} Whether the shape is visible.
|
* @returns {boolean} Whether the shape is visible.
|
||||||
*/
|
*/
|
||||||
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
|
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ void main()
|
||||||
#elif defined(INCLUDE_WEB_MERCATOR_Y)
|
#elif defined(INCLUDE_WEB_MERCATOR_Y)
|
||||||
float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;
|
float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;
|
||||||
float encodedNormal = 0.0;
|
float encodedNormal = 0.0;
|
||||||
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)
|
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL) || defined(APPLY_MATERIAL)
|
||||||
float webMercatorT = textureCoordinates.y;
|
float webMercatorT = textureCoordinates.y;
|
||||||
float encodedNormal = compressed0.w;
|
float encodedNormal = compressed0.w;
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
|
||||||
|
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {Cesium3DTilesTerrainGeometryProcessor.CreateMeshOptions} options An object describing options for mesh creation.
|
||||||
|
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
|
||||||
|
* @returns {Promise<object>} A promise that resolves to an object containing selected info from the created TerrainMesh.
|
||||||
|
*/
|
||||||
|
function createVerticesFromCesium3DTilesTerrain(options, transferableObjects) {
|
||||||
|
const meshPromise = Cesium3DTilesTerrainGeometryProcessor.createMesh(options);
|
||||||
|
return meshPromise.then(function (mesh) {
|
||||||
|
const verticesBuffer = mesh.vertices.buffer;
|
||||||
|
const indicesBuffer = mesh.indices.buffer;
|
||||||
|
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
|
||||||
|
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
|
||||||
|
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
|
||||||
|
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
|
||||||
|
|
||||||
|
transferableObjects.push(
|
||||||
|
verticesBuffer,
|
||||||
|
indicesBuffer,
|
||||||
|
westIndicesBuffer,
|
||||||
|
southIndicesBuffer,
|
||||||
|
eastIndicesBuffer,
|
||||||
|
northIndicesBuffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
verticesBuffer: verticesBuffer,
|
||||||
|
indicesBuffer: indicesBuffer,
|
||||||
|
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
|
||||||
|
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
|
||||||
|
encoding: mesh.encoding,
|
||||||
|
westIndicesBuffer: westIndicesBuffer,
|
||||||
|
southIndicesBuffer: southIndicesBuffer,
|
||||||
|
eastIndicesBuffer: eastIndicesBuffer,
|
||||||
|
northIndicesBuffer: northIndicesBuffer,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createTaskProcessorWorker(
|
||||||
|
createVerticesFromCesium3DTilesTerrain,
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
|
||||||
|
import Matrix4 from "../Core/Matrix4.js";
|
||||||
|
import Cartesian3 from "../Core/Cartesian3.js";
|
||||||
|
import AxisAlignedBoundingBox from "../Core/AxisAlignedBoundingBox.js";
|
||||||
|
|
||||||
|
const scratchAABBCornerMin = new Cartesian3();
|
||||||
|
const scratchAABBCornerMax = new Cartesian3();
|
||||||
|
const scratchTrianglePoints = [
|
||||||
|
new Cartesian3(),
|
||||||
|
new Cartesian3(),
|
||||||
|
new Cartesian3(),
|
||||||
|
];
|
||||||
|
const scratchTriangleAABB = new AxisAlignedBoundingBox();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the next layer of the terrain picker's quadtree by determining which triangles intersect
|
||||||
|
* each of the four child nodes. (Essentially distributing the parent's triangles to its children.)
|
||||||
|
*
|
||||||
|
* Takes in the AABBs of the four child nodes in the tree's local space, an inverse transform
|
||||||
|
* to convert triangle positions to the tree's local space, and the parent node's triangle indices and positions.
|
||||||
|
*
|
||||||
|
* Returns an four arrays - one for each child node - containing the indices of the triangles that intersect each node.
|
||||||
|
*/
|
||||||
|
function incrementallyBuildTerrainPicker(parameters, transferableObjects) {
|
||||||
|
// Rehydrate worker inputs
|
||||||
|
const aabbs = new Float64Array(parameters.aabbs);
|
||||||
|
const nodeAABBs = Array.from({ length: 4 }, (_, i) => {
|
||||||
|
const min = Cartesian3.unpack(aabbs, i * 6, scratchAABBCornerMin);
|
||||||
|
const max = Cartesian3.unpack(aabbs, i * 6 + 3, scratchAABBCornerMax);
|
||||||
|
return AxisAlignedBoundingBox.fromCorners(
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
new AxisAlignedBoundingBox(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const inverseTransformArray = new Float64Array(parameters.inverseTransform);
|
||||||
|
const inverseTransform = Matrix4.unpack(
|
||||||
|
inverseTransformArray,
|
||||||
|
0,
|
||||||
|
new Matrix4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const triangleIndices = new Uint32Array(parameters.triangleIndices);
|
||||||
|
const trianglePositions = new Float32Array(parameters.trianglePositions);
|
||||||
|
const intersectingTrianglesArrays = Array.from({ length: 4 }, () => []);
|
||||||
|
|
||||||
|
for (let j = 0; j < triangleIndices.length; j++) {
|
||||||
|
Cartesian3.unpack(trianglePositions, j * 9, scratchTrianglePoints[0]);
|
||||||
|
Cartesian3.unpack(trianglePositions, j * 9 + 3, scratchTrianglePoints[1]);
|
||||||
|
Cartesian3.unpack(trianglePositions, j * 9 + 6, scratchTrianglePoints[2]);
|
||||||
|
|
||||||
|
const triangleAABB = createAABBFromTriangle(
|
||||||
|
inverseTransform,
|
||||||
|
scratchTrianglePoints,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const aabbsIntersect =
|
||||||
|
nodeAABBs[i].intersectAxisAlignedBoundingBox(triangleAABB);
|
||||||
|
if (!aabbsIntersect) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
intersectingTrianglesArrays[i].push(triangleIndices[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersectingTrianglesTypedArrays = intersectingTrianglesArrays.map(
|
||||||
|
(array) => {
|
||||||
|
const uintArray = new Uint32Array(array);
|
||||||
|
transferableObjects.push(uintArray.buffer);
|
||||||
|
return uintArray.buffer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
intersectingTrianglesArrays: intersectingTrianglesTypedArrays,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tree-space axis-aligned bounding box from the given triangle points and inverse transform (from world to tree space).
|
||||||
|
* @param {Matrix4} inverseTransform transform from world space to tree local space
|
||||||
|
* @param {Cartesian3[]} trianglePoints array of 3 Cartesian3 points representing the triangle
|
||||||
|
* @returns {AxisAlignedBoundingBox} the axis-aligned bounding box enclosing the triangle in tree local space
|
||||||
|
*/
|
||||||
|
function createAABBFromTriangle(inverseTransform, trianglePoints) {
|
||||||
|
Matrix4.multiplyByPoint(
|
||||||
|
inverseTransform,
|
||||||
|
trianglePoints[0],
|
||||||
|
trianglePoints[0],
|
||||||
|
);
|
||||||
|
Matrix4.multiplyByPoint(
|
||||||
|
inverseTransform,
|
||||||
|
trianglePoints[1],
|
||||||
|
trianglePoints[1],
|
||||||
|
);
|
||||||
|
Matrix4.multiplyByPoint(
|
||||||
|
inverseTransform,
|
||||||
|
trianglePoints[2],
|
||||||
|
trianglePoints[2],
|
||||||
|
);
|
||||||
|
|
||||||
|
return AxisAlignedBoundingBox.fromPoints(trianglePoints, scratchTriangleAABB);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createTaskProcessorWorker(incrementallyBuildTerrainPicker);
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
|
||||||
|
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Cesium3DTilesTerrainGeometryProcessor.UpsampleMeshOptions} options An object describing options for mesh upsampling.
|
||||||
|
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
|
||||||
|
* @returns {TerrainMeshProxy} An object containing selected info from the upsampled TerrainMesh.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function upsampleVerticesFromCesium3DTilesTerrain(
|
||||||
|
options,
|
||||||
|
transferableObjects,
|
||||||
|
) {
|
||||||
|
const mesh = Cesium3DTilesTerrainGeometryProcessor.upsampleMesh(options);
|
||||||
|
|
||||||
|
const verticesBuffer = mesh.vertices.buffer;
|
||||||
|
const indicesBuffer = mesh.indices.buffer;
|
||||||
|
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
|
||||||
|
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
|
||||||
|
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
|
||||||
|
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
|
||||||
|
|
||||||
|
transferableObjects.push(
|
||||||
|
verticesBuffer,
|
||||||
|
indicesBuffer,
|
||||||
|
westIndicesBuffer,
|
||||||
|
southIndicesBuffer,
|
||||||
|
eastIndicesBuffer,
|
||||||
|
northIndicesBuffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @type {TerrainMeshProxy} */
|
||||||
|
const result = {
|
||||||
|
verticesBuffer: verticesBuffer,
|
||||||
|
indicesBuffer: indicesBuffer,
|
||||||
|
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
|
||||||
|
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
|
||||||
|
encoding: mesh.encoding,
|
||||||
|
westIndicesBuffer: westIndicesBuffer,
|
||||||
|
southIndicesBuffer: southIndicesBuffer,
|
||||||
|
eastIndicesBuffer: eastIndicesBuffer,
|
||||||
|
northIndicesBuffer: northIndicesBuffer,
|
||||||
|
minimumHeight: mesh.minimumHeight,
|
||||||
|
maximumHeight: mesh.maximumHeight,
|
||||||
|
boundingSphere: mesh.boundingSphere3D,
|
||||||
|
orientedBoundingBox: mesh.orientedBoundingBox,
|
||||||
|
horizonOcclusionPoint: mesh.horizonOcclusionPoint,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createTaskProcessorWorker(
|
||||||
|
upsampleVerticesFromCesium3DTilesTerrain,
|
||||||
|
);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,91 @@
|
||||||
|
import {
|
||||||
|
Cesium3DTilesTerrainProvider,
|
||||||
|
Resource,
|
||||||
|
TerrainProvider,
|
||||||
|
} from "../../index.js";
|
||||||
|
|
||||||
|
describe("Core/Cesium3DTilesTerrainProvider", function () {
|
||||||
|
it("conforms to TerrainProvider interface", function () {
|
||||||
|
expect(Cesium3DTilesTerrainProvider).toConformToInterface(TerrainProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl throws if url is not provided", async function () {
|
||||||
|
await expectAsync(
|
||||||
|
Cesium3DTilesTerrainProvider.fromUrl(),
|
||||||
|
).toBeRejectedWithDeveloperError(
|
||||||
|
"url is required, actual value was undefined",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl rejects when url rejects", async function () {
|
||||||
|
const error = new Error();
|
||||||
|
await expectAsync(
|
||||||
|
Cesium3DTilesTerrainProvider.fromUrl(Promise.reject(error)),
|
||||||
|
).toBeRejectedWithError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl rejects when url is invalid", async function () {
|
||||||
|
const path = "made/up/url";
|
||||||
|
await expectAsync(
|
||||||
|
Cesium3DTilesTerrainProvider.fromUrl(path),
|
||||||
|
).toBeRejectedWithError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl works with path", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path);
|
||||||
|
|
||||||
|
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl when url promise is used", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(
|
||||||
|
Promise.resolve(path),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromUrl works with Resource", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
|
||||||
|
const resource = new Resource(path);
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(resource);
|
||||||
|
|
||||||
|
expect(provider).toBeInstanceOf(Cesium3DTilesTerrainProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logo is undefined if credit is not provided", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path);
|
||||||
|
|
||||||
|
expect(provider.credit).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logo is defined if credit is provided", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
const credit = "test";
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path, {
|
||||||
|
credit: credit,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(provider.credit).toBeDefined();
|
||||||
|
expect(provider.credit.html).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a water mask when requested", async function () {
|
||||||
|
const path = "Data/Cesium3DTiles/Terrain/Test/tileset.json";
|
||||||
|
|
||||||
|
const provider = await Cesium3DTilesTerrainProvider.fromUrl(path, {
|
||||||
|
requestWaterMask: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(provider.hasWaterMask).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
import createScene from "../../../../Specs/createScene.js";
|
||||||
|
import {
|
||||||
|
Cartesian3,
|
||||||
|
createWorldTerrainAsync,
|
||||||
|
Ellipsoid,
|
||||||
|
EllipsoidTerrainProvider,
|
||||||
|
ImageryLayerCollection,
|
||||||
|
QuadtreeTile,
|
||||||
|
GeographicTilingScheme,
|
||||||
|
Ray,
|
||||||
|
} from "../../index.js";
|
||||||
|
|
||||||
|
import MockTerrainProvider from "../../../../Specs/MockTerrainProvider.js";
|
||||||
|
import TerrainTileProcessor from "../../../../Specs/TerrainTileProcessor.js";
|
||||||
|
import GeographicProjection from "../../Source/Core/GeographicProjection.js";
|
||||||
|
import { SceneMode } from "@cesium/engine";
|
||||||
|
|
||||||
|
describe(
|
||||||
|
"Core/TerrainPicker",
|
||||||
|
function () {
|
||||||
|
let frameState;
|
||||||
|
let tilingScheme;
|
||||||
|
let rootTiles;
|
||||||
|
let imageryLayerCollection;
|
||||||
|
let mockTerrain;
|
||||||
|
let processor;
|
||||||
|
let scene;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
frameState = {
|
||||||
|
context: {
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
tilingScheme = new GeographicTilingScheme();
|
||||||
|
rootTiles = QuadtreeTile.createLevelZeroTiles(tilingScheme);
|
||||||
|
imageryLayerCollection = new ImageryLayerCollection();
|
||||||
|
|
||||||
|
mockTerrain = new MockTerrainProvider();
|
||||||
|
|
||||||
|
processor = new TerrainTileProcessor(
|
||||||
|
frameState,
|
||||||
|
mockTerrain,
|
||||||
|
imageryLayerCollection,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
for (let i = 0; i < rootTiles.length; ++i) {
|
||||||
|
rootTiles[i].freeResources();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
scene = createScene();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(function () {
|
||||||
|
scene.destroyForSpecs();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the correct point even when the mesh includes normals", async function () {
|
||||||
|
const terrainProvider = await createWorldTerrainAsync({
|
||||||
|
requestVertexNormals: true,
|
||||||
|
requestWaterMask: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 11,
|
||||||
|
x: 3788,
|
||||||
|
y: 1336,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
await processor.process([tile]);
|
||||||
|
const ray = new Ray(
|
||||||
|
new Cartesian3(
|
||||||
|
-5052039.459789615,
|
||||||
|
2561172.040315167,
|
||||||
|
-2936276.999965875,
|
||||||
|
),
|
||||||
|
new Cartesian3(
|
||||||
|
0.5036332963145244,
|
||||||
|
0.6648033332898124,
|
||||||
|
0.5517155343926082,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const pickResult = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
const cartographic = Ellipsoid.WGS84.cartesianToCartographic(pickResult);
|
||||||
|
expect(cartographic.height).toBeGreaterThan(-500.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the correct point when a closer triangle is processed after a farther triangle", function () {
|
||||||
|
// Pick root tile (level=0, x=0, y=0) from the east side towards the west.
|
||||||
|
// Based on heightmap triangle processing order the west triangle will be tested first, followed
|
||||||
|
// by the east triangle. But since the east triangle is closer we expect it to be the pick result.
|
||||||
|
const terrainProvider = new EllipsoidTerrainProvider();
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 0,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
return processor.process([tile]).then(function () {
|
||||||
|
const origin = new Cartesian3(50000000.0, -1.0, 0.0);
|
||||||
|
const direction = new Cartesian3(-1.0, 0.0, 0.0);
|
||||||
|
const ray = new Ray(origin, direction);
|
||||||
|
const cullBackFaces = false;
|
||||||
|
const pickResult = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
scene.mode,
|
||||||
|
undefined,
|
||||||
|
cullBackFaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(pickResult.x).toBeGreaterThan(0.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores triangles that are behind the ray when picking", function () {
|
||||||
|
// Pick root tile (level=0, x=0, y=0) from the center towards the east side (+X).
|
||||||
|
const terrainProvider = new EllipsoidTerrainProvider();
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 0,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
return processor.process([tile]).then(function () {
|
||||||
|
const origin = new Cartesian3(0.0, -1.0, 0.0);
|
||||||
|
const direction = new Cartesian3(1.0, 0.0, 0.0);
|
||||||
|
const ray = new Ray(origin, direction);
|
||||||
|
const cullBackFaces = false;
|
||||||
|
const pickResult = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
scene.mode,
|
||||||
|
undefined,
|
||||||
|
cullBackFaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(pickResult.x).toBeGreaterThan(0.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the same point repeatedly given the same ray", async function () {
|
||||||
|
const terrainProvider = await createWorldTerrainAsync();
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 11,
|
||||||
|
x: 3788,
|
||||||
|
y: 1336,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
await processor.process([tile]);
|
||||||
|
const ray = new Ray(
|
||||||
|
new Cartesian3(
|
||||||
|
-5052039.459789615,
|
||||||
|
2561172.040315167,
|
||||||
|
-2936276.999965875,
|
||||||
|
),
|
||||||
|
new Cartesian3(
|
||||||
|
0.5036332963145244,
|
||||||
|
0.6648033332898124,
|
||||||
|
0.5517155343926082,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test the same point 4x to match the maximum depth of the TerrainPicker's quadtree
|
||||||
|
const pickResult1 = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
const pickResult2 = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
const pickResult3 = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
const pickResult4 = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
|
||||||
|
expect(Cartesian3.equals(pickResult1, pickResult2)).toBe(true);
|
||||||
|
expect(Cartesian3.equals(pickResult1, pickResult3)).toBe(true);
|
||||||
|
expect(Cartesian3.equals(pickResult1, pickResult4)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the correct point in 2D mode", async function () {
|
||||||
|
const terrainProvider = await createWorldTerrainAsync();
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 11,
|
||||||
|
x: 3788,
|
||||||
|
y: 1336,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
await processor.process([tile]);
|
||||||
|
const ray = new Ray(
|
||||||
|
new Cartesian3(
|
||||||
|
2225176.403907301,
|
||||||
|
-16901892.821517847,
|
||||||
|
11980956.030684207,
|
||||||
|
),
|
||||||
|
new Cartesian3(
|
||||||
|
-0.16643384121919264,
|
||||||
|
0.8118600130186374,
|
||||||
|
-0.5596276402737828,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const projection = new GeographicProjection(Ellipsoid.WGS84);
|
||||||
|
const pickResult3D = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
SceneMode.SCENE3D,
|
||||||
|
projection,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
tile.data.updateSceneMode(SceneMode.SCENE2D);
|
||||||
|
const pickResult2D = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
SceneMode.SCENE2D,
|
||||||
|
projection,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(Cartesian3.equals(pickResult3D, pickResult2D)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the correct point in CV mode", async function () {
|
||||||
|
const terrainProvider = await createWorldTerrainAsync();
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 11,
|
||||||
|
x: 3788,
|
||||||
|
y: 1336,
|
||||||
|
});
|
||||||
|
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
await processor.process([tile]);
|
||||||
|
const ray = new Ray(
|
||||||
|
new Cartesian3(
|
||||||
|
2225176.403907301,
|
||||||
|
-16901892.821517847,
|
||||||
|
11980956.030684207,
|
||||||
|
),
|
||||||
|
new Cartesian3(
|
||||||
|
-0.16643384121919264,
|
||||||
|
0.8118600130186374,
|
||||||
|
-0.5596276402737828,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const projection = new GeographicProjection(Ellipsoid.WGS84);
|
||||||
|
const pickResult3D = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
SceneMode.SCENE3D,
|
||||||
|
projection,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
tile.data.updateSceneMode(SceneMode.COLUMBUS_VIEW);
|
||||||
|
const pickResultCV = tile.data.pick(
|
||||||
|
ray,
|
||||||
|
SceneMode.COLUMBUS_VIEW,
|
||||||
|
projection,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(Cartesian3.equals(pickResult3D, pickResultCV)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks the correct point after vertical exaggeration is applied", async function () {
|
||||||
|
const terrainProvider = await createWorldTerrainAsync({
|
||||||
|
requestVertexNormals: true,
|
||||||
|
requestWaterMask: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile = new QuadtreeTile({
|
||||||
|
tilingScheme: new GeographicTilingScheme(),
|
||||||
|
level: 11,
|
||||||
|
x: 3788,
|
||||||
|
y: 1336,
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.frameState.verticalExaggeration = 2.0;
|
||||||
|
processor.frameState = scene.frameState;
|
||||||
|
processor.terrainProvider = terrainProvider;
|
||||||
|
|
||||||
|
await processor.process([tile]);
|
||||||
|
const ray = new Ray(
|
||||||
|
new Cartesian3(
|
||||||
|
-5052039.459789615,
|
||||||
|
2561172.040315167,
|
||||||
|
-2936276.999965875,
|
||||||
|
),
|
||||||
|
new Cartesian3(
|
||||||
|
0.5036332963145244,
|
||||||
|
0.6648033332898124,
|
||||||
|
0.5517155343926082,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const pickResult = tile.data.pick(ray, scene.mode, undefined, true);
|
||||||
|
const cartographic = Ellipsoid.WGS84.cartesianToCartographic(pickResult);
|
||||||
|
expect(cartographic.height).toBeGreaterThan(100.0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
"WebGL",
|
||||||
|
);
|
||||||
|
|
@ -178,9 +178,10 @@ describe(
|
||||||
const bb = billboardCollection.get(0);
|
const bb = billboardCollection.get(0);
|
||||||
|
|
||||||
await pollToPromise(function () {
|
await pollToPromise(function () {
|
||||||
|
entityCluster.update(scene.frameState);
|
||||||
scene.renderForSpecs();
|
scene.renderForSpecs();
|
||||||
visualizer.update(time);
|
visualizer.update(time);
|
||||||
return bb.show;
|
return bb.ready;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(bb.position).toEqual(testObject.position.getValue(time));
|
expect(bb.position).toEqual(testObject.position.getValue(time));
|
||||||
|
|
|
||||||
|
|
@ -690,6 +690,160 @@ describe(
|
||||||
b.destroy();
|
b.destroy();
|
||||||
}).toThrowDeveloperError();
|
}).toThrowDeveloperError();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer with no context`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
sizeInBytes: 4,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer with an invalid typed array`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
typedArray: {},
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer with both a typed array and size in bytes`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
typedArray: new Float32Array([0, 0, 0, 1]),
|
||||||
|
sizeInBytes: 16,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer without a typed array or size in bytes`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer with invalid usage`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: 16,
|
||||||
|
usage: 0,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a pixel buffer with size of zero`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: 0,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`creates pixel buffer`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: 16,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(buffer.sizeInBytes).toEqual(16);
|
||||||
|
expect(buffer.usage).toEqual(BufferUsage.STATIC_DRAW);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`copies array to a pixel buffer`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT;
|
||||||
|
const vertices = new ArrayBuffer(sizeInBytes);
|
||||||
|
const positions = new Float32Array(vertices);
|
||||||
|
positions[0] = 1.0;
|
||||||
|
positions[1] = 2.0;
|
||||||
|
positions[2] = 3.0;
|
||||||
|
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: sizeInBytes,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
buffer.copyFromArrayView(vertices);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`can create a pixel buffer from a typed array`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const typedArray = new Float32Array(3);
|
||||||
|
typedArray[0] = 1.0;
|
||||||
|
typedArray[1] = 2.0;
|
||||||
|
typedArray[2] = 3.0;
|
||||||
|
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
typedArray: typedArray,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
expect(buffer.sizeInBytes).toEqual(typedArray.byteLength);
|
||||||
|
expect(buffer.usage).toEqual(BufferUsage.STATIC_DRAW);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`can create a pixel buffer from a size in bytes`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: 4,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
expect(buffer.sizeInBytes).toEqual(4);
|
||||||
|
expect(buffer.usage).toEqual(BufferUsage.STATIC_DRAW);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`create a pixel buffer throws without WebGL 2`, function () {
|
||||||
|
if (context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
buffer = Buffer.createPixelBuffer({
|
||||||
|
context: context,
|
||||||
|
sizeInBytes: 4,
|
||||||
|
usage: BufferUsage.STATIC_DRAW,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"WebGL",
|
"WebGL",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import {
|
||||||
BufferUsage,
|
BufferUsage,
|
||||||
Context,
|
Context,
|
||||||
ContextLimits,
|
ContextLimits,
|
||||||
|
ClearCommand,
|
||||||
|
PixelFormat,
|
||||||
|
PixelDatatype,
|
||||||
} from "../../index.js";
|
} from "../../index.js";
|
||||||
|
|
||||||
import createContext from "../../../../Specs/createContext.js";
|
import createContext from "../../../../Specs/createContext.js";
|
||||||
|
|
@ -340,6 +343,65 @@ describe(
|
||||||
expect(c2._webgl2).toBe(true);
|
expect(c2._webgl2).toBe(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("readPixels", function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const c = createContext();
|
||||||
|
const command = new ClearCommand({
|
||||||
|
color: Color.WHITE,
|
||||||
|
});
|
||||||
|
command.execute(c);
|
||||||
|
const pixels = c.readPixels();
|
||||||
|
expect(pixels).toBeDefined();
|
||||||
|
expect(pixels).toEqual([255, 255, 255, 255]);
|
||||||
|
c.destroyForSpecs();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("readPixels using PBO", function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const c = createContext();
|
||||||
|
if (!c.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const command = new ClearCommand({
|
||||||
|
color: Color.WHITE,
|
||||||
|
});
|
||||||
|
command.execute(c);
|
||||||
|
const pixelBuffer = c.readPixelsToPBO({
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
});
|
||||||
|
const pixels = PixelFormat.createTypedArray(
|
||||||
|
PixelFormat.RGBA,
|
||||||
|
PixelDatatype.UNSIGNED_BYTE,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
pixelBuffer.getBufferData(pixels);
|
||||||
|
pixelBuffer.destroy();
|
||||||
|
expect(pixels).toBeDefined();
|
||||||
|
expect(pixels).toEqual([255, 255, 255, 255]);
|
||||||
|
c.destroyForSpecs();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`readPixels using PBO throws without WebGL 2`, function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const c = createContext({
|
||||||
|
requestWebgl1: true,
|
||||||
|
});
|
||||||
|
expect(function () {
|
||||||
|
c.readPixelsToPBO();
|
||||||
|
}).toThrowDeveloperError(
|
||||||
|
"A WebGL 2 context is required to read pixels using a PBO.",
|
||||||
|
);
|
||||||
|
c.destroyForSpecs();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
"WebGL",
|
"WebGL",
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { Sync, WebGLConstants } from "../../index.js";
|
||||||
|
|
||||||
|
import createContext from "../../../../Specs/createContext.js";
|
||||||
|
import createWebglVersionHelper from "../createWebglVersionHelper.js";
|
||||||
|
import { RuntimeError } from "@cesium/engine";
|
||||||
|
|
||||||
|
describe(
|
||||||
|
"Renderer/Sync",
|
||||||
|
function () {
|
||||||
|
createWebglVersionHelper(createBufferSpecs);
|
||||||
|
|
||||||
|
function createBufferSpecs(contextOptions) {
|
||||||
|
let context;
|
||||||
|
let sync;
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
context = createContext(contextOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(function () {
|
||||||
|
context.destroyForSpecs();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sync = sync && sync.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws when creating a sync with no context`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
sync = Sync.create({});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates", function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
expect(sync).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("destroys", function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
expect(sync.isDestroyed()).toEqual(false);
|
||||||
|
sync.destroy();
|
||||||
|
expect(sync.isDestroyed()).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when fails to destroy", function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
sync.destroy();
|
||||||
|
|
||||||
|
expect(function () {
|
||||||
|
sync.destroy();
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`throws without WebGL 2`, function () {
|
||||||
|
if (context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(function () {
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
}).toThrowDeveloperError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`returns status unsignaled after create`, function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
const status = sync.getStatus();
|
||||||
|
expect(status).toEqual(WebGLConstants.UNSIGNALED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`waitForSignal to resolve`, async function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = 0;
|
||||||
|
spyOn(Sync.prototype, "getStatus").and.callFake(function () {
|
||||||
|
if (i++ < 3) {
|
||||||
|
return WebGLConstants.UNSIGNALED;
|
||||||
|
}
|
||||||
|
return WebGLConstants.SIGNALED;
|
||||||
|
});
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
await expectAsync(
|
||||||
|
sync.waitForSignal(function (next) {
|
||||||
|
setTimeout(next, 100);
|
||||||
|
}),
|
||||||
|
).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`waitForSignal throws on timeout`, async function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spyOn(Sync.prototype, "getStatus").and.callFake(function () {
|
||||||
|
return WebGLConstants.UNSIGNALED; // simulate never being signaled
|
||||||
|
});
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
await expectAsync(
|
||||||
|
sync.waitForSignal(function (next) {
|
||||||
|
setTimeout(next, 100);
|
||||||
|
}),
|
||||||
|
).toBeRejectedWithError(RuntimeError, "Wait for signal timeout");
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`waitForSignal throws on custom timeout`, async function () {
|
||||||
|
if (!context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = 0;
|
||||||
|
spyOn(Sync.prototype, "getStatus").and.callFake(function () {
|
||||||
|
if (i++ < 6) {
|
||||||
|
return WebGLConstants.UNSIGNALED;
|
||||||
|
}
|
||||||
|
return WebGLConstants.SIGNALED;
|
||||||
|
});
|
||||||
|
sync = Sync.create({
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
|
await expectAsync(
|
||||||
|
sync.waitForSignal(function (next) {
|
||||||
|
setTimeout(next, 100);
|
||||||
|
}, 5),
|
||||||
|
).toBeRejectedWithError(RuntimeError, "Wait for signal timeout");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WebGL",
|
||||||
|
);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -40,8 +40,10 @@ describe("Scene/Azure2DImageryProvider", function () {
|
||||||
tilesetId: "a-tileset-id",
|
tilesetId: "a-tileset-id",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
provider._attributionsByLevel = {};
|
||||||
|
|
||||||
expect(provider.url).toEqual(
|
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",
|
"https://atlas.microsoft.com/map/tile?api-version=2024-04-01&tilesetId=a-tileset-id&subscription-key=test-subscriptionKey&zoom={z}&x={x}&y={y}",
|
||||||
);
|
);
|
||||||
expect(provider.tileWidth).toEqual(256);
|
expect(provider.tileWidth).toEqual(256);
|
||||||
expect(provider.tileHeight).toEqual(256);
|
expect(provider.tileHeight).toEqual(256);
|
||||||
|
|
@ -74,6 +76,8 @@ describe("Scene/Azure2DImageryProvider", function () {
|
||||||
rectangle: rectangle,
|
rectangle: rectangle,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
provider._attributionsByLevel = {};
|
||||||
|
|
||||||
expect(provider.tileWidth).toEqual(256);
|
expect(provider.tileWidth).toEqual(256);
|
||||||
expect(provider.tileHeight).toEqual(256);
|
expect(provider.tileHeight).toEqual(256);
|
||||||
expect(provider.maximumLevel).toBe(22);
|
expect(provider.maximumLevel).toBe(22);
|
||||||
|
|
@ -133,6 +137,7 @@ describe("Scene/Azure2DImageryProvider", function () {
|
||||||
subscriptionKey: "test-subscriptionKey",
|
subscriptionKey: "test-subscriptionKey",
|
||||||
tilesetId: "a-tileset-id",
|
tilesetId: "a-tileset-id",
|
||||||
});
|
});
|
||||||
|
provider._attributionsByLevel = {};
|
||||||
|
|
||||||
const layer = new ImageryLayer(provider);
|
const layer = new ImageryLayer(provider);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,40 @@ describe("Scene/BillboardCollection", function () {
|
||||||
expect(b.image.length).toBe(guidLength);
|
expect(b.image.length).toBe(guidLength);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("image property setter computes SVG width and height", function () {
|
||||||
|
const b = billboards.add();
|
||||||
|
|
||||||
|
// Don't override image size if billboard size is unspecified.
|
||||||
|
b.image = "icon.svg";
|
||||||
|
expect(b.image).toBe("icon.svg");
|
||||||
|
expect(b._imageWidth).toBeUndefined();
|
||||||
|
expect(b._imageHeight).toBeUndefined();
|
||||||
|
|
||||||
|
// Don't override image size for raster images.
|
||||||
|
b.width = b.height = 50;
|
||||||
|
b.image = "icon.png";
|
||||||
|
expect(b._imageWidth).toBeUndefined();
|
||||||
|
expect(b._imageHeight).toBeUndefined();
|
||||||
|
|
||||||
|
// Override image size with billboard size for SVGs.
|
||||||
|
b.width = b.height = 50;
|
||||||
|
b.image = "icon.svg";
|
||||||
|
expect(b._imageWidth).toBe(50);
|
||||||
|
expect(b._imageHeight).toBe(50);
|
||||||
|
|
||||||
|
// Limit billboard-derived SVG size to 512px.
|
||||||
|
b.width = b.height = 10000;
|
||||||
|
b.image = "icon-lg.svg";
|
||||||
|
expect(b._imageWidth).toBe(512);
|
||||||
|
expect(b._imageHeight).toBe(512);
|
||||||
|
|
||||||
|
// Don't override image size if billboard is not sized in pixels.
|
||||||
|
b.sizeInMeters = true;
|
||||||
|
b.image = "icon.svg";
|
||||||
|
expect(b._imageWidth).toBeUndefined();
|
||||||
|
expect(b._imageHeight).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("is not destroyed", function () {
|
it("is not destroyed", function () {
|
||||||
expect(billboards.isDestroyed()).toEqual(false);
|
expect(billboards.isDestroyed()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -184,112 +184,3 @@ describe(
|
||||||
},
|
},
|
||||||
"WebGL",
|
"WebGL",
|
||||||
);
|
);
|
||||||
|
|
||||||
describe(
|
|
||||||
"Scene/GaussianSplat3DTileContent_Legacy",
|
|
||||||
function () {
|
|
||||||
const tilesetUrl =
|
|
||||||
"./Data/Cesium3DTiles/GaussianSplats/tower_legacy/tileset.json";
|
|
||||||
|
|
||||||
let scene;
|
|
||||||
let options;
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
scene = createScene();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(function () {
|
|
||||||
scene.destroyForSpecs();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
RequestScheduler.clearForSpecs();
|
|
||||||
scene.morphTo3D(0.0);
|
|
||||||
|
|
||||||
const camera = scene.camera;
|
|
||||||
camera.frustum = new PerspectiveFrustum();
|
|
||||||
camera.frustum.aspectRatio =
|
|
||||||
scene.drawingBufferWidth / scene.drawingBufferHeight;
|
|
||||||
camera.frustum.fov = CesiumMath.toRadians(60.0);
|
|
||||||
|
|
||||||
options = {
|
|
||||||
cullRequestsWhileMoving: false,
|
|
||||||
maximumScreenSpaceError: 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
scene.primitives.removeAll();
|
|
||||||
ResourceCache.clearForSpecs();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("loads Gaussian Splat content", function () {
|
|
||||||
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, options).then(
|
|
||||||
function (tileset) {
|
|
||||||
scene.camera.lookAt(
|
|
||||||
tileset.boundingSphere.center,
|
|
||||||
new HeadingPitchRange(0.0, -1.57, tileset.boundingSphere.radius),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Cesium3DTilesTester.waitForTileContentReady(
|
|
||||||
scene,
|
|
||||||
tileset.root,
|
|
||||||
).then(function (tile) {
|
|
||||||
const content = tile.content;
|
|
||||||
expect(content).toBeDefined();
|
|
||||||
expect(content instanceof GaussianSplat3DTileContent).toBe(true);
|
|
||||||
|
|
||||||
const gltfPrimitive = content.gltfPrimitive;
|
|
||||||
expect(gltfPrimitive).toBeDefined();
|
|
||||||
expect(gltfPrimitive.attributes.length).toBeGreaterThan(0);
|
|
||||||
const positions = ModelUtility.getAttributeBySemantic(
|
|
||||||
gltfPrimitive,
|
|
||||||
VertexAttributeSemantic.POSITION,
|
|
||||||
).typedArray;
|
|
||||||
|
|
||||||
const rotations = ModelUtility.getAttributeBySemantic(
|
|
||||||
gltfPrimitive,
|
|
||||||
VertexAttributeSemantic.ROTATION,
|
|
||||||
).typedArray;
|
|
||||||
|
|
||||||
const scales = ModelUtility.getAttributeBySemantic(
|
|
||||||
gltfPrimitive,
|
|
||||||
VertexAttributeSemantic.SCALE,
|
|
||||||
).typedArray;
|
|
||||||
|
|
||||||
const colors = ModelUtility.getAttributeBySemantic(
|
|
||||||
gltfPrimitive,
|
|
||||||
VertexAttributeSemantic.COLOR,
|
|
||||||
).typedArray;
|
|
||||||
|
|
||||||
expect(positions.length).toBeGreaterThan(0);
|
|
||||||
expect(rotations.length).toBeGreaterThan(0);
|
|
||||||
expect(scales.length).toBeGreaterThan(0);
|
|
||||||
expect(colors.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("Create and destroy GaussianSplat3DTileContent", 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 tile = await Cesium3DTilesTester.waitForTileContentReady(
|
|
||||||
scene,
|
|
||||||
tileset.root,
|
|
||||||
);
|
|
||||||
|
|
||||||
scene.primitives.remove(tileset);
|
|
||||||
expect(tileset.isDestroyed()).toBe(true);
|
|
||||||
expect(tile.isDestroyed()).toBe(true);
|
|
||||||
expect(tile.content).toBeUndefined();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"WebGL",
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -214,71 +214,3 @@ describe(
|
||||||
},
|
},
|
||||||
"WebGL",
|
"WebGL",
|
||||||
);
|
);
|
||||||
|
|
||||||
describe(
|
|
||||||
"Scene/GaussianSplatPrimitive_Legacy",
|
|
||||||
function () {
|
|
||||||
const tilesetUrl =
|
|
||||||
"./Data/Cesium3DTiles/GaussianSplats/tower_legacy/tileset.json";
|
|
||||||
|
|
||||||
let scene;
|
|
||||||
let options;
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
scene = createScene();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(function () {
|
|
||||||
scene.destroyForSpecs();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
RequestScheduler.clearForSpecs();
|
|
||||||
scene.morphTo3D(0.0);
|
|
||||||
|
|
||||||
const camera = scene.camera;
|
|
||||||
camera.frustum = new PerspectiveFrustum();
|
|
||||||
camera.frustum.aspectRatio =
|
|
||||||
scene.drawingBufferWidth / scene.drawingBufferHeight;
|
|
||||||
camera.frustum.fov = CesiumMath.toRadians(60.0);
|
|
||||||
|
|
||||||
options = {
|
|
||||||
cullRequestsWhileMoving: false,
|
|
||||||
maximumScreenSpaceError: 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
scene.primitives.removeAll();
|
|
||||||
ResourceCache.clearForSpecs();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("loads a Gaussian splats tileset", 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),
|
|
||||||
);
|
|
||||||
expect(tileset.hasExtension("3DTILES_content_gltf")).toBe(true);
|
|
||||||
expect(
|
|
||||||
tileset.isGltfExtensionUsed("KHR_spz_gaussian_splats_compression"),
|
|
||||||
).toBe(true);
|
|
||||||
expect(
|
|
||||||
tileset.isGltfExtensionRequired("KHR_spz_gaussian_splats_compression"),
|
|
||||||
).toBe(true);
|
|
||||||
|
|
||||||
const tile = await Cesium3DTilesTester.waitForTileContentReady(
|
|
||||||
scene,
|
|
||||||
tileset.root,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(tile.content).toBeDefined();
|
|
||||||
expect(tile.content instanceof GaussianSplat3DTileContent).toBe(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"WebGL",
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Cartesian3,
|
|
||||||
Cartesian4,
|
Cartesian4,
|
||||||
createWorldTerrainAsync,
|
|
||||||
Ellipsoid,
|
|
||||||
EllipsoidTerrainProvider,
|
|
||||||
GeographicTilingScheme,
|
GeographicTilingScheme,
|
||||||
Ray,
|
|
||||||
GlobeSurfaceTile,
|
GlobeSurfaceTile,
|
||||||
ImageryLayerCollection,
|
ImageryLayerCollection,
|
||||||
QuadtreeTile,
|
QuadtreeTile,
|
||||||
|
|
@ -17,8 +12,6 @@ import MockImageryProvider from "../../../../Specs/MockImageryProvider.js";
|
||||||
import MockTerrainProvider from "../../../../Specs/MockTerrainProvider.js";
|
import MockTerrainProvider from "../../../../Specs/MockTerrainProvider.js";
|
||||||
import TerrainTileProcessor from "../../../../Specs/TerrainTileProcessor.js";
|
import TerrainTileProcessor from "../../../../Specs/TerrainTileProcessor.js";
|
||||||
|
|
||||||
import createScene from "../../../../Specs/createScene.js";
|
|
||||||
|
|
||||||
describe("Scene/GlobeSurfaceTile", function () {
|
describe("Scene/GlobeSurfaceTile", function () {
|
||||||
let frameState;
|
let frameState;
|
||||||
let tilingScheme;
|
let tilingScheme;
|
||||||
|
|
@ -322,117 +315,6 @@ describe("Scene/GlobeSurfaceTile", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
|
||||||
"pick",
|
|
||||||
function () {
|
|
||||||
let scene;
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
scene = createScene();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(function () {
|
|
||||||
scene.destroyForSpecs();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gets correct results even when the mesh includes normals", async function () {
|
|
||||||
const terrainProvider = await createWorldTerrainAsync({
|
|
||||||
requestVertexNormals: true,
|
|
||||||
requestWaterMask: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tile = new QuadtreeTile({
|
|
||||||
tilingScheme: new GeographicTilingScheme(),
|
|
||||||
level: 11,
|
|
||||||
x: 3788,
|
|
||||||
y: 1336,
|
|
||||||
});
|
|
||||||
|
|
||||||
processor.frameState = scene.frameState;
|
|
||||||
processor.terrainProvider = terrainProvider;
|
|
||||||
|
|
||||||
await processor.process([tile]);
|
|
||||||
const ray = new Ray(
|
|
||||||
new Cartesian3(
|
|
||||||
-5052039.459789615,
|
|
||||||
2561172.040315167,
|
|
||||||
-2936276.999965875,
|
|
||||||
),
|
|
||||||
new Cartesian3(
|
|
||||||
0.5036332963145244,
|
|
||||||
0.6648033332898124,
|
|
||||||
0.5517155343926082,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const pickResult = tile.data.pick(ray, undefined, undefined, true);
|
|
||||||
const cartographic =
|
|
||||||
Ellipsoid.WGS84.cartesianToCartographic(pickResult);
|
|
||||||
expect(cartographic.height).toBeGreaterThan(-500.0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gets correct result when a closer triangle is processed after a farther triangle", function () {
|
|
||||||
// Pick root tile (level=0, x=0, y=0) from the east side towards the west.
|
|
||||||
// Based on heightmap triangle processing order the west triangle will be tested first, followed
|
|
||||||
// by the east triangle. But since the east triangle is closer we expect it to be the pick result.
|
|
||||||
const terrainProvider = new EllipsoidTerrainProvider();
|
|
||||||
|
|
||||||
const tile = new QuadtreeTile({
|
|
||||||
tilingScheme: new GeographicTilingScheme(),
|
|
||||||
level: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
processor.frameState = scene.frameState;
|
|
||||||
processor.terrainProvider = terrainProvider;
|
|
||||||
|
|
||||||
return processor.process([tile]).then(function () {
|
|
||||||
const origin = new Cartesian3(50000000.0, -1.0, 0.0);
|
|
||||||
const direction = new Cartesian3(-1.0, 0.0, 0.0);
|
|
||||||
const ray = new Ray(origin, direction);
|
|
||||||
const cullBackFaces = false;
|
|
||||||
const pickResult = tile.data.pick(
|
|
||||||
ray,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
cullBackFaces,
|
|
||||||
);
|
|
||||||
expect(pickResult.x).toBeGreaterThan(0.0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ignores triangles that are behind the ray", function () {
|
|
||||||
// Pick root tile (level=0, x=0, y=0) from the center towards the east side (+X).
|
|
||||||
const terrainProvider = new EllipsoidTerrainProvider();
|
|
||||||
|
|
||||||
const tile = new QuadtreeTile({
|
|
||||||
tilingScheme: new GeographicTilingScheme(),
|
|
||||||
level: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
processor.frameState = scene.frameState;
|
|
||||||
processor.terrainProvider = terrainProvider;
|
|
||||||
|
|
||||||
return processor.process([tile]).then(function () {
|
|
||||||
const origin = new Cartesian3(0.0, -1.0, 0.0);
|
|
||||||
const direction = new Cartesian3(1.0, 0.0, 0.0);
|
|
||||||
const ray = new Ray(origin, direction);
|
|
||||||
const cullBackFaces = false;
|
|
||||||
const pickResult = tile.data.pick(
|
|
||||||
ray,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
cullBackFaces,
|
|
||||||
);
|
|
||||||
expect(pickResult.x).toBeGreaterThan(0.0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"WebGL",
|
|
||||||
);
|
|
||||||
|
|
||||||
describe("eligibleForUnloading", function () {
|
describe("eligibleForUnloading", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
processor.mockWebGL();
|
processor.mockWebGL();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import {
|
||||||
Primitive,
|
Primitive,
|
||||||
SceneMode,
|
SceneMode,
|
||||||
VoxelPrimitive,
|
VoxelPrimitive,
|
||||||
|
Sync,
|
||||||
|
WebGLConstants,
|
||||||
} from "../../index.js";
|
} from "../../index.js";
|
||||||
|
|
||||||
import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js";
|
import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js";
|
||||||
|
|
@ -93,13 +95,14 @@ describe(
|
||||||
scene.mode = SceneMode.SCENE3D;
|
scene.mode = SceneMode.SCENE3D;
|
||||||
scene.morphTime = SceneMode.getMorphTime(scene.mode);
|
scene.morphTime = SceneMode.getMorphTime(scene.mode);
|
||||||
|
|
||||||
camera.setView({
|
// Note: Important the camera.frustum is set before camera.setView
|
||||||
destination: largeRectangle,
|
|
||||||
});
|
|
||||||
|
|
||||||
camera.frustum = new PerspectiveFrustum();
|
camera.frustum = new PerspectiveFrustum();
|
||||||
camera.frustum.fov = CesiumMath.toRadians(60.0);
|
camera.frustum.fov = CesiumMath.toRadians(60.0);
|
||||||
camera.frustum.aspectRatio = 1.0;
|
camera.frustum.aspectRatio = 1.0;
|
||||||
|
|
||||||
|
camera.setView({
|
||||||
|
destination: largeRectangle,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
|
@ -235,6 +238,63 @@ describe(
|
||||||
scene.renderForSpecs();
|
scene.renderForSpecs();
|
||||||
expect(scene).toPickPrimitive(rectangle);
|
expect(scene).toPickPrimitive(rectangle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("picks a primitive async", async function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rectangle = createLargeRectangle(0.0);
|
||||||
|
const windowPosition = new Cartesian2(0, 0);
|
||||||
|
|
||||||
|
let actual;
|
||||||
|
let ready = false;
|
||||||
|
scene._picking
|
||||||
|
.pickAsync(scene, windowPosition, undefined, undefined, 1)
|
||||||
|
.then((result) => {
|
||||||
|
actual = result[0];
|
||||||
|
ready = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await pollToPromise(function () {
|
||||||
|
scene.renderForSpecs();
|
||||||
|
return ready;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(actual).toBeDefined();
|
||||||
|
expect(actual.primitive).toEqual(rectangle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("picks async throws timeout if too slow", async function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!scene.context.webgl2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spyOn(Sync.prototype, "getStatus").and.callFake(function () {
|
||||||
|
return WebGLConstants.UNSIGNALED; // simulate never being signaled
|
||||||
|
});
|
||||||
|
const windowPosition = new Cartesian2(0, 0);
|
||||||
|
|
||||||
|
let ready = false;
|
||||||
|
let threw = false;
|
||||||
|
scene._picking
|
||||||
|
.pickAsync(scene, windowPosition, undefined, undefined, 1)
|
||||||
|
.then((_result) => {
|
||||||
|
ready = true;
|
||||||
|
})
|
||||||
|
.catch((_error) => {
|
||||||
|
threw = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await pollToPromise(function () {
|
||||||
|
scene.renderForSpecs();
|
||||||
|
return ready || threw;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(threw).toBe(true);
|
||||||
|
expect(ready).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pickVoxelCoordinate", function () {
|
describe("pickVoxelCoordinate", function () {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import {
|
||||||
ColorGeometryInstanceAttribute,
|
ColorGeometryInstanceAttribute,
|
||||||
HeightReference,
|
HeightReference,
|
||||||
SharedContext,
|
SharedContext,
|
||||||
|
Sync,
|
||||||
} from "../../index.js";
|
} from "../../index.js";
|
||||||
|
|
||||||
import createCanvas from "../../../../Specs/createCanvas.js";
|
import createCanvas from "../../../../Specs/createCanvas.js";
|
||||||
|
|
@ -636,6 +637,10 @@ function pickMetadataAt(scene, schemaId, className, propertyName, x, y) {
|
||||||
describe(
|
describe(
|
||||||
"Scene/Scene",
|
"Scene/Scene",
|
||||||
function () {
|
function () {
|
||||||
|
// It's not easily possible to mock the most detailed pick functions
|
||||||
|
// so don't run those tests when using the WebGL stub
|
||||||
|
const webglStub = !!window.webglStub;
|
||||||
|
|
||||||
let scene;
|
let scene;
|
||||||
|
|
||||||
beforeAll(function () {
|
beforeAll(function () {
|
||||||
|
|
@ -846,6 +851,31 @@ describe(
|
||||||
expect(spyListener).toHaveBeenCalled();
|
expect(spyListener).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("afterRender functions can schedule callbacks for next frame", function () {
|
||||||
|
const spyListener = jasmine.createSpy("listener");
|
||||||
|
const spyListener2 = jasmine.createSpy("listener");
|
||||||
|
|
||||||
|
const primitive = {
|
||||||
|
update: function (frameState) {
|
||||||
|
frameState.afterRender.push(spyListener);
|
||||||
|
frameState.afterRender.push(() => {
|
||||||
|
frameState.afterRender.push(spyListener2);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function () {},
|
||||||
|
isDestroyed: () => false,
|
||||||
|
};
|
||||||
|
scene.primitives.add(primitive);
|
||||||
|
|
||||||
|
scene.renderForSpecs();
|
||||||
|
expect(spyListener).toHaveBeenCalled();
|
||||||
|
expect(spyListener2).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
scene.renderForSpecs();
|
||||||
|
expect(spyListener).toHaveBeenCalled();
|
||||||
|
expect(spyListener2).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
function CommandMockPrimitive(command) {
|
function CommandMockPrimitive(command) {
|
||||||
this.update = function (frameState) {
|
this.update = function (frameState) {
|
||||||
frameState.commandList.push(command);
|
frameState.commandList.push(command);
|
||||||
|
|
@ -1475,6 +1505,99 @@ describe(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("pick", async function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0);
|
||||||
|
const rectanglePrimitive = createRectangle(rectangle);
|
||||||
|
const primitives = scene.primitives;
|
||||||
|
primitives.add(rectanglePrimitive);
|
||||||
|
|
||||||
|
scene.camera.setView({ destination: rectangle });
|
||||||
|
|
||||||
|
const windowPosition = new Cartesian2(0, 0);
|
||||||
|
const result = scene.pick(windowPosition);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.primitive).toEqual(rectanglePrimitive);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pickAsync", async function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0);
|
||||||
|
const rectanglePrimitive = createRectangle(rectangle);
|
||||||
|
const primitives = scene.primitives;
|
||||||
|
primitives.add(rectanglePrimitive);
|
||||||
|
|
||||||
|
scene.camera.setView({ destination: rectangle });
|
||||||
|
|
||||||
|
const windowPosition = new Cartesian2(0, 0);
|
||||||
|
|
||||||
|
let result;
|
||||||
|
let ready = false;
|
||||||
|
let threw = false;
|
||||||
|
scene
|
||||||
|
.pickAsync(windowPosition)
|
||||||
|
.then((result0) => {
|
||||||
|
result = result0;
|
||||||
|
ready = true;
|
||||||
|
})
|
||||||
|
.catch((_error) => {
|
||||||
|
threw = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await pollToPromise(function () {
|
||||||
|
scene.renderForSpecs();
|
||||||
|
return ready || threw;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(scene.context.webgl2).toBeTrue();
|
||||||
|
expect(threw).toBeFalse();
|
||||||
|
expect(ready).toBeTrue();
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.primitive).toEqual(rectanglePrimitive);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pickAsync can reject", async function () {
|
||||||
|
if (webglStub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spyOn(Sync.prototype, "getStatus").and.callFake(function () {
|
||||||
|
return WebGLConstants.UNSIGNALED; // simulate never being signaled
|
||||||
|
});
|
||||||
|
const rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0);
|
||||||
|
const rectanglePrimitive = createRectangle(rectangle);
|
||||||
|
const primitives = scene.primitives;
|
||||||
|
primitives.add(rectanglePrimitive);
|
||||||
|
|
||||||
|
scene.camera.setView({ destination: rectangle });
|
||||||
|
|
||||||
|
const windowPosition = new Cartesian2(0, 0);
|
||||||
|
|
||||||
|
let ready = false;
|
||||||
|
let threw = false;
|
||||||
|
scene
|
||||||
|
.pickAsync(windowPosition)
|
||||||
|
.then((_result0) => {
|
||||||
|
ready = true;
|
||||||
|
})
|
||||||
|
.catch((_error) => {
|
||||||
|
threw = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await pollToPromise(function () {
|
||||||
|
scene.renderForSpecs();
|
||||||
|
return ready || threw;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(scene.context.webgl2).toBeTrue();
|
||||||
|
expect(threw).toBeTrue();
|
||||||
|
expect(ready).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
it("pickPosition", function () {
|
it("pickPosition", function () {
|
||||||
if (!scene.pickPositionSupported) {
|
if (!scene.pickPositionSupported) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue