Compare commits

...

367 Commits

Author SHA1 Message Date
lukemckinstry 7b3b5c25a9 switch to azure production asset ids
deploy / deploy (push) Has been cancelled Details
2025-11-24 11:54:43 -05:00
lukemckinstry dce3549952 remove labels thumbnails 2025-11-24 11:54:43 -05:00
lukemckinstry 162458dd58 update specs 2025-11-24 11:54:43 -05:00
lukemckinstry 528acebaca remove labels only layers from base layer picker defaults 2025-11-24 11:54:43 -05:00
lukemckinstry fc1da0452e use derived resources for attribution calls 2025-11-24 11:54:43 -05:00
lukemckinstry b423ef6c62 add azure maps to default options in imagery layer picker 2025-11-24 11:54:43 -05:00
lukemckinstry e9ab31a23f move azure sandcastles out of development folder 2025-11-24 11:54:43 -05:00
lukemckinstry 9a0da95b1b make Azure class public 2025-11-24 11:54:43 -05:00
lukemckinstry 1c8b77b31f docs typo 2025-11-24 11:54:43 -05:00
lukemckinstry 2d1e175deb update imagery tutorial sandcastle 2025-11-24 11:54:43 -05:00
lukemckinstry b3a6c52f9c add attribution for azure 2025-11-24 11:54:42 -05:00
jjspace ee2b3813b2
Merge pull request #13038 from CesiumGS/fix/textureatlas-border-internal
deploy / deploy (push) Has been cancelled Details
dev / lint (push) Has been cancelled Details
dev / coverage (push) Has been cancelled Details
dev / release-tests (push) Has been cancelled Details
dev / node-20 (push) Has been cancelled Details
sandcastle-dev / deploy (push) Has been cancelled Details
fix(textureatlas): Apply internal padding between images
2025-11-21 17:14:38 +00:00
Don McCurdy 4e3980cc53 fix(textureatlas): Apply internal padding between images 2025-11-21 11:44:18 -05:00
jjspace c92d0d80a1
Merge pull request #13037 from CesiumGS/chore/textureatlas-visual-tests
chore(textureatlas): Refactor unit tests for visual output
2025-11-20 17:39:22 +00:00
Don McCurdy 16d0cccfdb chore(textureatlas): Fix vertical order unit test debug output 2025-11-20 09:31:57 -05:00
Don McCurdy 5213dfc1c0 update empty space character with '.' 2025-11-18 16:44:20 -05:00
Don McCurdy bd5aa3ff29 chore(textureatlas): Refactor unit tests for visual output 2025-11-18 15:34:19 -05:00
Matt Schwartz bcc5ea383e
Merge pull request #13013 from CesiumGS/clear-console
Add option to clear the console in Sandcastle
2025-11-14 16:56:31 +00:00
Matt Schwartz 278174f9f1
Merge pull request #13020 from donmccurdy/fix/billboard-svg-scale
fix(billboards): Rasterize SVG content at billboard resolution
2025-11-14 15:59:37 +00:00
Don McCurdy cfce282f08 fix(billboards): Scale SVGs in texture atlas to match billboard display size 2025-11-13 16:39:02 -05:00
Sean Lilley debd74eb91
Merge pull request #9961 from DanielLeone/fastPickingWithOctree
Fast terrain picking (via quadtree)
2025-11-13 19:04:43 +00:00
Matt Schwartz d063514d1a Changes.md 2025-11-13 13:41:40 -05:00
Matt Schwartz d748edee14 Variable rename in terrainpicker 2025-11-13 11:49:42 -05:00
Matt Schwartz cdbd86b05b Merge remote-tracking branch 'origin' into fastPickingWithOctree 2025-11-13 11:47:52 -05:00
Matt Schwartz a1419b267c Fix to CHANGES.md 2025-11-13 10:39:26 -05:00
Matt Schwartz 644c18332b
Merge pull request #12983 from alarkbentley/alark/async-picking
Added Scene.pickAsync for non GPU blocking picking
2025-11-13 06:24:25 +00:00
Adam Larkeryd 71a12a341b fix for typedef 2025-11-13 12:22:00 +09:00
Matt Schwartz 9113ae75bc PR revisions #1 2025-11-12 13:23:25 -05:00
Matt Schwartz a6e0226968
Merge pull request #12958 from Beilinson/fix-billboard-subregion
fix billboard subregion not displayed
2025-11-12 16:24:55 +00:00
Adam Larkeryd e70ec8043c doc fixes 2025-11-12 10:21:08 +09:00
Adam Larkeryd 7145e11ba4 revert sandcastle picking demo 2025-11-12 09:47:32 +09:00
Adam Larkeryd 95cb3bb3b9 Picking.pickAsync 2025-11-12 09:40:10 +09:00
Adam Beili e6ac78ff3b Promises 2025-11-11 16:06:53 +02:00
Adam Beili 034653c846 Fix jsdoc types 2025-11-11 15:34:32 +02:00
Adam Larkeryd 45f8734037 Context readPixelsToPBO 2025-11-11 15:45:16 +09:00
Adam Larkeryd 64f07aec37 tests for Sync waitForSignal 2025-11-10 16:31:32 +09:00
Adam Larkeryd febfade1df Sync waitForSignal 2025-11-10 15:25:26 +09:00
Adam 9b42731f52 Fix 2025-11-07 18:33:16 +02:00
Adam 55b2ba865a CR 2025-11-07 18:00:24 +02:00
Matt Schwartz d4582b519b
Merge pull request #13014 from CesiumGS/multiple-console-args
Support multiple console arguments in sandcastle
2025-11-07 10:57:05 -05:00
Adam d34e3ccc30 Fix changes 2025-11-07 13:56:22 +02:00
Adam 85fabf515b Fix jsdoc 2025-11-07 13:39:28 +02:00
Adam 84890e6d95 Refactor 2025-11-07 13:28:26 +02:00
Adam 829a768abf Make existing test properly reflect the render loop 2025-11-07 13:02:44 +02:00
Adam Beili 4ed382f4f3
Merge branch 'CesiumGS:main' into fix-billboard-subregion 2025-11-07 11:03:50 +02:00
Adam Larkeryd 7148621fda added synchronus fallback warning 2025-11-06 15:09:45 +09:00
jjspace 3cc5f439f3
support multiple arguments in console mirror messages 2025-11-04 17:00:23 -05:00
jjspace ba462e219d
add option to clear the console log 2025-11-04 15:21:08 -05:00
Matt Schwartz 1c30cf64b5 Unit test fixes 2025-11-03 22:39:56 -05:00
Matt Schwartz cdf07f4214 Merge remote-tracking branch 'origin' into fastPickingWithOctree 2025-11-03 22:10:29 -05:00
Matt Schwartz e399a8a306 Moves and adds unit tests for picking 2025-11-03 22:09:56 -05:00
Matt Schwartz c0acb452b7 Fixes zero-scale bug in non-terrain cases 2025-11-03 21:50:22 -05:00
Matt Schwartz 1689691878 Fixes transform bugs in 2D/CV modes 2025-11-03 21:30:27 -05:00
Adam Larkeryd ac61464780 Merge branch 'main' into alark/async-picking 2025-11-04 10:10:09 +09:00
jjspace edceb42f3a
update changes.md 2025-11-03 16:01:54 -05:00
jjspace 4547dbb196
Merge pull request #13011 from CesiumGS/fix-sandcastle-deployment
Fix gallery build for prod
2025-11-03 14:53:05 -05:00
jjspace 26bb7275c3
build gallery to Build dir for production deployments 2025-11-03 14:48:24 -05:00
jjspace 8f923ededf
update thirdparty 2025-11-03 13:06:53 -05:00
jjspace b833c8c0a1
minor release guide changes 2025-11-03 13:05:48 -05:00
jjspace 34d6698079
update package versions and changes.md 2025-11-03 13:04:22 -05:00
jjspace cf19f38748
update tokens 2025-11-03 13:02:09 -05:00
Jeshurun Hembd a129f06ea9
Merge pull request #13010 from CesiumGS/remove-splat-deprecation
Remove support for the legacy splats extension
2025-11-03 17:55:09 +00:00
jjspace caf297d63e
change wording 2025-11-03 12:32:29 -05:00
jjspace 30ed7083c3
tidy up the code 2025-11-03 11:47:19 -05:00
jjspace 88951ffde4
remove support for the legacy splats extension 2025-11-03 11:40:10 -05:00
Jeshurun Hembd 96c4ca3f4b
Merge pull request #13009 from CesiumGS/remove-banner
Remove banner pointing to legacy sandcastle
2025-11-03 16:18:57 +00:00
jjspace 90e527961e
remove banner pointing to legacy sandcastle 2025-11-03 11:07:59 -05:00
jjspace 7aae7e358e
Merge pull request #12904 from CesiumGS/sandcastle-build-updates
Sandcastle build config updates
2025-11-03 11:07:30 -05:00
jjspace c59e106436
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-11-03 10:48:42 -05:00
jjspace 7d3efcb544
Merge pull request #12963 from CesiumGS/3d-tiles-terrain
3d tiles terrain
2025-11-03 14:58:59 +00:00
Adam Larkeryd eb1fa20a49 Merge branch 'main' into alark/async-picking 2025-11-03 10:38:15 +09:00
Jeshurun Hembd a3ae7943f9
Merge pull request #12959 from CesiumGS/voxel-exaggeration
Fix exaggeration of ellipsoid voxel clipping bounds
2025-11-02 02:56:09 +00:00
Jeshurun Hembd b321b6fadc PR feedback part 2 2025-10-31 18:05:08 -04:00
Jeshurun Hembd 7f81c437f4 Avoid undefined TypedArray type 2025-10-31 17:35:02 -04:00
Jeshurun Hembd 3f911ee8e1 Store exaggeration state in VoxelPrimitive 2025-10-31 13:11:17 -04:00
jjspace 9675ce52d7
don't include engine bundle in npm package 2025-10-31 12:23:04 -04:00
Matt Schwartz 01fde5c93f Style refactor 2025-10-31 10:46:05 -04:00
Matt Schwartz 488bc7c961 More jsdoc 2025-10-31 10:20:32 -04:00
Matt Schwartz c8b00a4ec7 Improves jsdocs for terainpicker 2025-10-31 09:37:48 -04:00
Matt Schwartz 8e57c5a37c Polishes PR 2025-10-31 00:45:30 -04:00
Matt Schwartz b823e08546 Moves transform computation from terrain picker to terrain mesh 2025-10-30 19:59:40 -04:00
Matt Schwartz 7081df8824 Fixes bug with intersections behind ray 2025-10-30 19:24:33 -04:00
Matt Schwartz fdb8ee42a4 Switch terrain picking data structure to quadtree 2025-10-30 19:02:41 -04:00
Gabby Getz ba5b5d77e8
Merge branch 'main' into sandcastle-build-updates 2025-10-30 18:03:48 -04:00
Adam Beili 1d6d5e6570 Cleaner and readable code 2025-10-30 02:07:06 +02:00
Adam Beili f73f2f2b5c Self CR 2025-10-30 01:59:41 +02:00
Adam Beili 6f1349bc09 Suggested CR changes 2025-10-30 01:23:30 +02:00
Matt Schwartz b0f14b30f3 Refactors scene mode change and reset in octree picking 2025-10-29 19:08:15 -04:00
Adam Beili 5d00fca596 Safer implementation, handle edge cases as done previously 2025-10-29 23:29:04 +02:00
Adam Beili 08ecab02d0
Merge branch 'main' into fix-billboard-subregion 2025-10-29 23:05:56 +02:00
Matt Schwartz cbfa3cea27 Fixes to globesurfacetile spec 2025-10-29 14:43:15 -04:00
Matt Schwartz 2e3d6b6bf2 Moves ray-AABB intersection tests to utility class 2025-10-29 14:01:23 -04:00
Matt Schwartz 83364cc5a5 Adapts octree for different scene modes 2025-10-29 11:20:41 -04:00
Adam Larkeryd 86049c8fe6 added pickAsync code example to doc 2025-10-29 10:25:55 +09:00
Adam Larkeryd ec3e71b360 changelog 2025-10-29 10:21:56 +09:00
Adam Larkeryd 40bce95af4 test: picks async throws timeout if too slow 2025-10-29 10:12:54 +09:00
Adam Larkeryd ce0c6a79e9 Merge branch 'main' into alark/async-picking 2025-10-29 09:24:56 +09:00
Jeshurun Hembd b222893b2a PR feedback part 1 2025-10-28 18:56:05 -04:00
Jeshurun Hembd 1f737c2d78 PR feedback part 1 2025-10-28 18:08:42 -04:00
Matt Schwartz 9b215be7b7 Minor fix to clear triangles 2025-10-28 12:29:21 -04:00
Matt Schwartz b00b65cee1 Merge remote-tracking branch 'origin/main' into fastPickingWithOctree 2025-10-28 11:52:03 -04:00
Matt Schwartz 1e8d248e34 Move incremental build to worker 2025-10-28 11:20:19 -04:00
Matt Schwartz 63a8371eb6 Incrementally build octree on pick 2025-10-27 23:15:52 -04:00
jjspace 5cc27ef976
small doc link change 2025-10-27 14:14:10 -04:00
jjspace 22df371dda
extract gulp tasks to help avoid dynamic imports 2025-10-27 14:13:45 -04:00
Matt Schwartz 3af3ef03a2 Marks octree for rebuild on exaggeration changes 2025-10-27 13:04:37 -04:00
jjspace 8b212bd7b5
rebuild package bundles in dev server 2025-10-27 12:12:33 -04:00
Adam Larkeryd 9cfc449c55 Merge branch 'main' into alark/async-picking 2025-10-27 15:16:20 +09:00
Matt Schwartz b1e03247cb Improve memory patterns and simplify function signatures 2025-10-26 21:40:57 -04:00
Matt Schwartz 53fa96bf85 Lazily builds picking octree
WIP - the goal is to only build octrees on tiles
that are picked against. Beyond saving memory and worker time,
this allows us to conservatively rebuild octrees when
vertical exaggeration changes.

TODO: build the octree in a worker task, and fallback to
the default pick strategy while it's being built.
2025-10-26 20:48:50 -04:00
Matt Schwartz 790962d129 Revert changes to RequestErrorEvent.js 2025-10-26 14:38:00 -04:00
jjspace b478c3fb63
rename arguments and functions for clarity 2025-10-24 15:26:49 -04:00
Jeshurun Hembd 61f8dcfeef Clear TODO comments in Cesium3DTilesTerrainGeometryProcessor 2025-10-24 11:22:47 -04:00
Adam Larkeryd f02b48a50a pr comment fixes #1 2025-10-24 11:33:39 +09:00
Adam Larkeryd e53c22d09d Merge branch 'main' into alark/async-picking 2025-10-24 09:32:58 +09:00
Jeshurun Hembd d08ce7a58e Move constructors to tops of files 2025-10-23 19:44:40 -04:00
Jeshurun Hembd 2de6dd7ba3 Merge branch 'main' into 3d-tiles-terrain 2025-10-23 18:32:28 -04:00
Jeshurun Hembd 668b8bba24 Update CHANGES.md, clean up whitespace 2025-10-23 18:28:58 -04:00
Jeshurun Hembd 77fb377592 Avoid recomputing keys in ResourceCache 2025-10-23 18:12:51 -04:00
Matt Schwartz 023b4f2d60 Fixes scratch variable reuse bug 2025-10-23 15:03:53 -04:00
Matt Schwartz f58ea2c51e Add child node after creation in octree 2025-10-23 14:53:10 -04:00
Matt Schwartz a8f7d8f9b8 Fixes bug in octree ray intersection 2025-10-23 14:29:02 -04:00
Matt Schwartz 948e00cfc3 Reverts unit test changes for octree picking
These tests were more like integration tests and would
likely be brittle in the future, and not hard to diagnose.
2025-10-23 14:15:26 -04:00
Matt Schwartz 64ec998897 Refactors octree picking for style and modularity 2025-10-23 14:10:46 -04:00
jjspace ba045e7e77
convert to options params for clarity 2025-10-23 13:22:55 -04:00
jjspace b74d62da2a
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-10-23 12:56:59 -04:00
Jeshurun Hembd e944a82c68 Minor cleanup in Cesium3DTilesTerrainProvider 2025-10-22 22:13:47 -04:00
Jeshurun Hembd c2024630f7 Use async/await in Cesium3DTilesTerrainDataSpec 2025-10-22 14:45:49 -04:00
Adam Larkeryd bf11b3af93 prettier fix 2025-10-22 10:18:32 +09:00
Adam Larkeryd 5c07c4dbbc
Update packages/engine/Source/Scene/PickFramebuffer.js
spelling

Co-authored-by: Matt Schwartz <mzschwartz5@gmail.com>
2025-10-22 09:58:01 +09:00
Adam Larkeryd 2b7861edab
Update packages/engine/Source/Scene/Scene.js
Simplify promise using await

Co-authored-by: Matt Schwartz <mzschwartz5@gmail.com>
2025-10-22 09:49:18 +09:00
Jeshurun Hembd d31a0c3ba7 Fix destructuring in Cesium3DTilesTerrainGeometryProcessor 2025-10-21 18:14:32 -04:00
jjspace 453096724d
switch dev-sandcastle to main branch 2025-10-21 15:35:57 -04:00
Jeshurun Hembd 2afb7b65b9 Clean up, clarify docs in Cesium3DTilesTerrain classes 2025-10-21 12:19:36 -04:00
Adam Larkeryd 4439df1658 Merge branch 'main' into alark/async-picking 2025-10-21 10:49:37 +09:00
jjspace da2dd8a942
update function names 2025-10-20 17:22:15 -04:00
Adam Larkeryd c9c27259ac tests for Context readPixels 2025-10-20 14:53:25 +09:00
Adam Larkeryd 2d3be0f559 added more tests 2025-10-20 11:55:05 +09:00
Jeshurun Hembd 470fbdd000 Clean up types in 3d tiles terrain processing 2025-10-17 19:08:10 -04:00
Adam Larkeryd ebaf3e7d20 added SyncSpec 2025-10-17 12:00:26 +09:00
Adam Larkeryd c8d2d81425 added as contributor 2025-10-17 10:59:03 +09:00
Adam Larkeryd 3a0e0aad04 Merge branch 'main' into alark/async-picking 2025-10-17 10:44:34 +09:00
Jeshurun Hembd 62b5da41fa Merge branch 'main' into 3d-tiles-terrain 2025-10-16 18:15:50 -04:00
Jeshurun Hembd 0ed8a626ba Update 3d tiles terrain sandcastle 2025-10-16 18:09:35 -04:00
Adam Larkeryd aa140c1a79 added .js for imports 2025-10-16 13:33:05 +09:00
Adam Larkeryd 0d3daff896 avoid test asyncpick if stub 2025-10-16 11:33:12 +09:00
Adam Larkeryd 6872ef2e99 added comment 2025-10-16 11:24:45 +09:00
Adam Larkeryd 8e0ba0c6d3 comments and refactoring 2025-10-16 11:15:46 +09:00
Adam Larkeryd f5fad8ed20 pick debouncing 2025-10-16 10:11:29 +09:00
Jeshurun Hembd bc97809deb Merge branch 'terrain-docs' into 3d-tiles-terrain 2025-10-15 15:49:59 -04:00
Adam Larkeryd 7a01e6ebe3 toggle ui for async 2025-10-15 15:06:32 +09:00
Adam Larkeryd f3351b7133 Added test for pixelbuffer and async pick 2025-10-15 14:39:25 +09:00
Adam Larkeryd c0f3fd44fd pickAsync pick first match 2025-10-15 09:56:44 +09:00
Adam Larkeryd 116ef099dd Merge branch 'main' into alark/async-picking 2025-10-15 09:54:47 +09:00
Gabby Getz 80abee3125
Merge branch 'main' into voxel-exaggeration 2025-10-14 16:56:12 -04:00
jjspace 3b6dd1e271
update comment 2025-10-14 13:22:02 -04:00
Adam Larkeryd 988b72f44e async picking 2025-10-14 16:31:11 +09:00
Adam Beili 452dd1984d
Merge branch 'main' into fix-billboard-subregion 2025-10-13 21:21:42 +03:00
Jeshurun Hembd ba486ca401 Revert signatures in TerrainEncodingSpec 2025-10-10 19:28:52 -04:00
Jeshurun Hembd e0eadb4628 Fix ImplicitSubtreeSpec 2025-10-10 18:04:18 -04:00
Jeshurun Hembd c2f5433469 Fix Cesium3DTilesTerrainProviderSpec 2025-10-10 17:52:53 -04:00
Jeshurun Hembd 639ea459b0 Clean up docstrings and types 2025-10-10 17:08:33 -04:00
jjspace bee0278186
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-10-10 12:05:55 -04:00
Adam Beili cae6a029d1 Add CHANGES.md 2025-10-10 18:29:40 +03:00
Adam Beili 981e206407
Merge branch 'main' into fix-billboard-subregion 2025-10-10 18:25:05 +03:00
Jeshurun Hembd 9550c39aa0 Merge branch 'main' into 3d-tiles-terrain 2025-10-09 16:18:03 -04:00
Jeshurun Hembd 00959e31ab Fix exaggeration of ellipsoid voxels 2025-10-08 17:07:21 -04:00
Adam Beili 3363ee863c
Merge branch 'main' into fix-billboard-subregion 2025-10-08 18:09:23 +03:00
Adam Beili 0c9a5236b1 Add billboard subregion condition 2025-10-08 18:01:17 +03:00
jjspace fd8540856e
adjust config structure 2025-10-07 16:04:03 -04:00
jjspace dde0e07c3a
small cleanup, remove excess changes 2025-10-07 15:47:11 -04:00
jjspace 00dce5a3f1
update release index url to new sandcastle 2025-10-07 13:33:10 -04:00
jjspace f3ecf5ba96
build sandcastle on server start when it doesn't exist 2025-10-07 13:30:48 -04:00
jjspace 61d330af7d
correctly build sandcastle for zip file 2025-10-07 13:29:24 -04:00
jjspace b695f5de55
adjust prod GH workflow 2025-10-07 13:15:41 -04:00
jjspace aaf4ae1bd1
move and trim build config 2025-10-07 13:15:20 -04:00
jjspace c4e8eea3ac
switch to relative routes for non-prod builds 2025-10-07 12:36:13 -04:00
jjspace b586c14aa6
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-10-07 11:24:34 -04:00
jjspace c708661860
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-10-03 13:04:58 -04:00
jjspace 0f7a9c8f30
switch deployed sandcastle directory 2025-09-30 13:57:58 -04:00
jjspace 26a011d5d3
remove extra logs 2025-09-30 11:01:46 -04:00
jjspace d83fb7631a
switch from ts api to tsc cli 2025-09-29 17:23:40 -04:00
jjspace 1bd41e93cc
further isolate sandcastle build process 2025-09-29 16:08:45 -04:00
Matt Schwartz f1ab9d0c1e Merge remote-tracking branch 'origin/main' into fastPickingWithOctree 2025-09-28 13:03:40 -04:00
jjspace 63c8ccbe66
clarify build option 2025-09-26 12:58:42 -04:00
jjspace d35002027a
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-09-26 12:52:17 -04:00
jjspace 20f3edc9aa
Merge pull request #12911 from CesiumGS/sandcastle-build-updates-windows
Use proper URLs for import statements
2025-09-23 16:18:31 -04:00
jjspace 2fcefac82f
make sure imports are strings 2025-09-23 16:16:23 -04:00
Marco Hutter 4aab5650d9 Use proper URLs for import statements 2025-09-23 21:47:06 +02:00
jjspace 97f606dec1
move import inside module declaration in package types 2025-09-23 14:55:03 -04:00
jjspace dcc4ec107b
fix ci build double // 2025-09-23 11:16:14 -04:00
jjspace 4de8de12e4
Merge remote-tracking branch 'origin/main' into sandcastle-build-updates 2025-09-19 15:41:51 -04:00
jjspace f3f4075c55
build TS for sandcastle 2025-09-19 15:15:21 -04:00
jjspace aabcb3d9e6
add commit sha to build for CI 2025-09-19 12:07:39 -04:00
jjspace f7c4d62d50
dont include dev sandcastles in prod 2025-09-19 12:07:24 -04:00
jjspace 814cb73efe
remove duplicated configs 2025-09-19 11:45:23 -04:00
jjspace f490f639ad
central sandcastle build function, bundle esm packages 2025-09-19 11:29:55 -04:00
Jeshurun Hembd 77aba4fc58 Merge branch 'main' into 3d-tiles-terrain 2025-07-01 18:37:03 -04:00
Jeshurun Hembd 7b7636fc00 Merge branch 'main' into 3d-tiles-terrain 2025-06-09 17:17:08 -04:00
Jeshurun Hembd 43991b4372 Merge branch 'main' into 3d-tiles-terrain 2025-05-01 15:50:16 -04:00
Jeshurun Hembd 11d846a7f9 Merge branch 'main' into 3d-tiles-terrain 2025-04-04 18:36:45 -04:00
Jeshurun Hembd a8532185a2 Remove calls to deprecated defaultValue 2025-03-18 17:48:53 -04:00
Jeshurun Hembd 3407e681ec Merge branch 'main' into 3d-tiles-terrain 2025-03-18 17:48:13 -04:00
Daniel Leone 4caa147876
fix types 2025-03-15 17:04:18 +08:00
Daniel Leone a64c37f6f8
comments and cleanup 2025-03-15 14:53:12 +08:00
Daniel Leone 3ef69c7324
fixing tests, adding some comments 2025-03-15 14:27:48 +08:00
Daniel Leone d1b4b547c7
Revert .idea to upstream 2025-03-15 13:55:18 +08:00
Daniel Leone 4cfef57f41
fixing some tests, reverting some unnecessary changes, reducing diff size 2025-03-15 13:51:25 +08:00
Victor Malaret 6fe50a1fe2
remove references to unused fast-picking* 2025-03-15 13:15:35 +08:00
Victor Malaret 6c5c2134f0
Remove debug/perf testing stuff 2025-03-15 13:15:34 +08:00
Victor Malaret 9d8871a09e
Fix fast-picking-* tasks so they work again
- fixed testing urls to work with latest cesium
- fixed analyze-results to catch timing events properly
- eslintignored these testing scripts
- note: still requires shelljs and puppeteer to be manually installed
for me, not sure if worth adding for these scripts that i imagine
will be removed before merging
2025-03-15 13:14:37 +08:00
Victor Malaret fd3594ff5d
prettier & Use pre-allocated vectors instead of creating new ones each time 2025-03-15 13:14:36 +08:00
Victor Malaret 78a73e7a1e
sandcastle fix verticalExaggeration 2025-03-15 13:14:36 +08:00
Daniel Leone 04a28b6947
Merge remote-tracking branch 'upstream/main' into fastPickingWithOctree
# Conflicts:
#	packages/engine/Source/Core/TerrainMesh.js
2025-03-15 13:11:33 +08:00
Jeshurun Hembd 9a8a070977 Merge branch 'main' into 3d-tiles-terrain 2025-03-12 15:07:57 -04:00
Daniel Leone 8805911607
fixing merge 2025-02-26 23:53:35 +08:00
Daniel Leone 8e9c687490
Merge remote-tracking branch 'upstream/main' into fastPickingWithOctree
# Conflicts:
#	.eslintignore
#	Specs/Core/sampleTerrainSpec.js
#	package.json
#	packages/engine/Source/Core/HeightmapTerrainData.js
#	packages/engine/Source/Core/HeightmapTessellator.js
#	packages/engine/Source/Core/OctreeTrianglePicking.js
#	packages/engine/Source/Core/QuantizedMeshTerrainData.js
#	packages/engine/Source/Core/TerrainMesh.js
#	packages/engine/Source/Core/TriangleSearchIntersectionTester.js
#	packages/engine/Source/Core/createTriangleVerticesCallback.js
#	packages/engine/Source/Scene/GlobeSurfaceTile.js
#	packages/engine/Source/Scene/Scene.js
#	packages/engine/Source/Workers/createTaskProcessorWorker.js
#	packages/engine/Specs/Scene/GlobeSurfaceTileSpec.js
2025-02-26 23:27:19 +08:00
Jeshurun Hembd a0e2657fd2 Fix spurious merge changes 2025-02-10 18:36:56 -05:00
Jeshurun Hembd c60a29bc3b Re-run prettier 2025-02-10 18:09:06 -05:00
Jeshurun Hembd 74d31b2022 Merge branch 'main' into 3d-tiles-terrain 2025-02-10 17:38:03 -05:00
Sean Lilley b5c1b76a32 Merge branch 'main' into 3d-tiles-terrain 2024-10-31 09:48:28 -04:00
Sean Lilley 61fbc02a40 Bug fixes 2024-10-30 15:28:03 -04:00
Gabby Getz 33a8f3e20f
Merge pull request #12205 from CesiumGS/3d-tiles-terrain-watermask
3D Tiles terrain water mask
2024-09-18 14:12:24 -04:00
ggetz 97f53e0038 Fix specs 2024-09-18 13:54:29 -04:00
ggetz f7d9d1793d Fix merge conflict 2024-09-18 13:31:08 -04:00
ggetz 9c2f2f7b58 Fix build-docs 2024-09-17 17:00:57 -04:00
ggetz 6dbc922d1a Fix ts-build errors 2024-09-17 13:59:29 -04:00
ggetz 2b990bfff5 Fix normals 2024-09-17 13:56:41 -04:00
ggetz 9bac174f8a WaterMasks for 3D Tiles Terrain 2024-09-17 13:44:59 -04:00
ggetz 40f8736c60 Merge branch '3d-tiles-terrain' into 3d-tiles-terrain-watermask 2024-09-12 12:47:15 -04:00
ggetz 0bc0b2b6e4 Merge branch 'main' into 3d-tiles-terrain 2024-09-12 12:46:10 -04:00
ggetz e1be5f9c3f Draft 2024-08-26 09:39:17 -04:00
ggetz 8ad5fd9c32 Merge branch 'main' into 3d-tiles-terrain 2024-08-22 13:28:15 -04:00
Gabby Getz 538fd35db9 Merge branch 'main' into 3d-tiles-terrain 2024-04-15 13:49:15 -04:00
Gabby Getz cedafe74da Merge branch 'main' into 3d-tiles-terrain 2023-08-21 15:25:14 -04:00
Sean Lilley 204fabdc90 Merge branch 'main' into 3d-tiles-terrain 2023-01-06 12:58:07 -05:00
Sean Lilley 1a03cf2bec Merge branch 'main' into 3d-tiles-terrain 2023-01-03 19:11:39 -05:00
Sean Lilley 3c86935826 Fix doc issues 2023-01-02 18:20:29 -05:00
Sean Lilley b3c1a1d06f Remove console.log 2023-01-02 17:42:50 -05:00
Sean Lilley 237e33700e Merge branch 'main' into 3d-tiles-terrain 2023-01-02 16:54:05 -05:00
Sean Lilley 43d4fc36ed Fix sampleTerrainMostDetailed when terrain provider hasn't loaded a subtree yet 2022-07-27 13:36:40 -04:00
Sean Lilley 349f253bd7 Merge branch 'main' into 3d-tiles-terrain 2022-07-25 13:13:40 -04:00
Sean Lilley 2e73640ab9 Merge branch 'main' into 3d-tiles-terrain 2022-06-01 18:27:11 -04:00
Sean Lilley 31d2611e1a Use subtree readyPromise instead of relying on previous when.js behavior that resolves values immediately 2022-06-01 17:41:01 -04:00
Sean Lilley e8a76fab32 Merge branch 'main' into 3d-tiles-terrain 2022-06-01 12:46:19 -04:00
Daniel Leone f4e6772e29 no idea why this test failed, adding more logs 2022-05-17 11:06:23 +08:00
Daniel Leone 404b2202ea renamed a few things 2022-05-16 23:56:28 +08:00
Daniel Leone 8c48cb0407 make getHeight pick in 3D mode 2022-05-08 12:16:35 +08:00
Daniel Leone 3f69f5f8e0 Okay, turns out that passing in mode as undefined was required for the existing tests for some reason.
I can also just grab terrain exaggeration from the encoding because that is mutated on the fly.
That means I don't need FrameState anymore all the way at the top
2022-05-06 16:19:42 +08:00
Daniel Leone 3111ef4a7f hide those private bits 2022-04-30 16:15:24 +08:00
Daniel Leone 526ea8ee45 fixing eslint errors 2022-04-30 15:49:06 +08:00
Daniel Leone 483ab35c9c FrameState is private, definitely breaking the public api here 2022-04-30 10:50:48 +08:00
Daniel Leone 358a793793 fixed some eslint complaints 2022-04-30 10:34:31 +08:00
Daniel Leone 26fe02dd88 passing in frameState parameter as required 2022-04-30 10:23:13 +08:00
Daniel Leone a3b426d8d9 removed duplicate method definition 2022-04-29 16:31:23 +08:00
Daniel Leone 1d005a67f8 added exaggeration options to terrain debug sandcastle
updated access token
stopped train mesh ray checking when exaggeration is applied
fixed some merge conflicts
2022-04-29 15:37:09 +08:00
Daniel Leone cd9ca23946 Merge remote-tracking branch 'upstream/main' into fastPickingWithOctree
# Conflicts:
#	Specs/Scene/GlobeSurfaceTileSpec.js
2022-04-29 15:28:20 +08:00
Sean Lilley c14a8a9d81 Fixes for 1.1 2022-03-16 16:51:58 -04:00
IanLilleyT fa4f844816 3d tiles terrain 2022-03-16 15:32:32 -04:00
Daniel Leone b5c3d90d18 more of changing var to let/const 2022-02-04 11:33:26 +08:00
Daniel Leone 98786878e7 change var to let/const now it's a real thing 2022-02-04 10:35:54 +08:00
Daniel Leone ea5bfcf727 Merge remote-tracking branch 'upstream/main' into fastPickingWithOctree
# Conflicts:
#	Source/Core/HeightmapTessellator.js
#	Source/Core/OrientedBoundingBox.js
#	Source/Scene/GlobeSurfaceTile.js
#	Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js
#	Specs/Core/sampleTerrainSpec.js
#	package.json
2022-02-04 08:46:26 +08:00
Daniel Leone a47080272d create trace dir if not exists 2021-12-04 22:15:18 +08:00
Daniel Leone 93a2487bee checking frame state for exaggeration and scene mode to avoid the octree path 2021-11-27 19:45:16 +08:00
Daniel Leone de1d36c5f7 added a dropdown to turn on/off default picking on the debug page 2021-11-27 18:23:25 +08:00
Daniel Leone d78719e445 removing unused code 2021-11-27 17:46:49 +08:00
Daniel Leone 5d2dce28c1 took some changes from upstream into the original globe pick function (it was moved into another class here)
not sure if new terrain exaggeration thing works?
2021-11-26 00:07:19 +08:00
Daniel Leone f31dc77928 made 2 new sandcastles and reverted the ones I hacked 2021-11-26 00:00:16 +08:00
Daniel Leone 567a3c7f2f Merge remote-tracking branch 'upstream/main' into fastPickingWithOctree
# Conflicts:
#	Apps/Sandcastle/gallery/3D Models.html
#	Apps/Sandcastle/gallery/Terrain.html
#	Source/Core/HeightmapTerrainData.js
#	Source/Core/Ion.js
#	Source/Scene/Globe.js
#	Source/Scene/GlobeSurfaceTile.js
#	Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js
#	package.json
2021-11-25 23:01:01 +08:00
Daniel Leone aa9dbe71c9 more cleanup 2021-11-25 01:52:43 +08:00
Daniel Leone 8d427e1882 code cleanup fixing up more of Terrain sandcastle 2021-11-25 01:40:52 +08:00
Daniel Leone 224836e426 renamed TrianglePicking.js to OctreeTrianglePicking.js 2021-11-25 01:09:13 +08:00
Daniel Leone 29ee6fa651 trying to speed up creating packed triangles a bit 2021-11-25 00:58:14 +08:00
Daniel Leone f236c151ae removed a little more quadtree code 2021-11-25 00:47:00 +08:00
Daniel Leone 1018d81124 code cleanup, code removal, fixing some things in the terrain.html sandcastle for testing
removed all the quadtree stuff
2021-11-25 00:38:22 +08:00
Daniel Leone fe06f25f9b * removed lots more unused code 2021-11-25 00:08:51 +08:00
Daniel Leone 1bb353b5de * fixed up some of the profiling scripts
* fixed a test
* removed unused code
2021-11-24 23:56:16 +08:00
Daniel Leone 52af82768f * oh boy it's fast... finally made that optimization for caching the last matched node for the next triangle, it's so
much faster for arcgis at least. It was so easy as well haha, should've done that months ago
* hacked in a window flag to disable default pick strategy to feel performance with only octree picking, it's actually good.
2021-11-21 15:55:06 +08:00
Daniel Leone d136d73cd7 updated and merged in upstream - still seems to work after all this time lol 2021-11-19 17:12:13 +08:00
Daniel Leone 6a85cd99a0 added octree back to heightmap terrain data.
saw the different results ~4 times (undefined from octree vs a result from the triangle searcher) while moving around a lot... not sure yet.
otherwise, ArcGIS terrain is working ok.
2021-04-27 17:31:10 +08:00
Daniel Leone 853fe3564b tracking of node intersections 2021-04-24 17:29:45 +08:00
Daniel Leone e195558d9d Merge remote-tracking branch 'upstream/master' into fastPickingWithOctree 2021-04-24 12:00:54 +08:00
Daniel Leone 4853568581 a few dev errors related to camera stuff... hmmm..... but actually some working result sometimes 2021-04-23 16:36:16 +08:00
Daniel Leone 26cbe4e001 man what a bug in that triangle packer! what an idiot! T_T
ok all the triangles at least remotely look correct now...
2021-04-22 22:54:35 +08:00
Daniel Leone 75ace54f12 more drawing of octree nodes, not drawing triangles for now 2021-04-19 18:28:00 +08:00
Daniel Leone d506da6590 and just like that, octree picking is back, kind of.
Visualizing the octants is in, and the triangles inside each one... I'm not sure why it doesn't make sense though..
2021-04-19 15:44:24 +08:00
Daniel Leone a667116333 yet more debugging stuff - and a x rotation on the transform 2021-03-02 17:02:18 +08:00
Daniel Leone fef9e252d5 Merge remote-tracking branch 'upstream/master' into fastPicking2
# Conflicts:
#	Apps/Sandcastle/gallery/Terrain.html
2021-03-02 09:50:08 +08:00
Daniel Leone 9d303b4bbb reverted all the octree code 2021-02-24 15:08:56 +08:00
Daniel Leone 1228d6a701 tighten up some coloring 2021-02-24 14:53:30 +08:00
Daniel Leone 377f9dc5ec lots more changes - switched to using the terrain encoding object instead - solves the rounding issues 2021-02-24 02:22:27 +08:00
Daniel Leone ab569b2ea5 lots of changes
fixed all the position index look ups - it actually seems to somewhat work
moved to float 64 array - however it isn't accurate enough I don't think - so attempting to convert to position decoding
2021-02-23 16:01:33 +08:00
Daniel Leone 124262d6b1 fixed the quadtree min/max heights 2021-02-22 15:00:05 +08:00
Daniel Leone 03adf9b17c flipping the Y direction (height - row) and that seems to fix it.... not sure why?? 2021-02-22 14:35:33 +08:00
Daniel Leone 0fbb24c803 * taking in skirt height - not using yet
* I think the squares are working, it's just inverted still I think.
2021-02-19 22:53:10 +08:00
Daniel Leone 15c652c60b more debugging ... more tests. 2021-02-19 16:48:26 +08:00
Daniel Leone 4a49d2e5c5 more debugging - drawing quad tree levels is working - i think. 2021-02-19 13:05:03 +08:00
Daniel Leone c7c2d1ec77 adding more detail to debug help - trying to reverse the transform 2021-02-18 22:05:43 +08:00
Daniel Leone ab8871858d working on quadtree algorithm - looks promising
transform or lookup seems wrong though or something haha
2021-02-18 10:07:15 +08:00
Daniel Leone e4d5c9fb54 added a quad tree path - search not implemented.. not sure how. 2021-02-17 12:04:12 +08:00
Daniel Leone 8e0b99ed17 nuked all the octree traversal code - now just looping through all nodes (pointless); although at least it works.
lots of debugging stuff.
2021-02-17 01:46:27 +08:00
Daniel Leone 4e6fc4c372 figured out what was wrong with the test
split the original triangle tester coder into a separate class
2021-02-16 16:03:41 +08:00
Daniel Leone 6cfefad2f3 lots of reverts - still a failing test 2021-02-15 09:42:43 +08:00
Daniel Leone 3c3756b2d9 lots of debugging stuff; and some fixes 2021-02-15 09:18:44 +08:00
Daniel Leone aadb8c22e1 added a test for ArcGIS - it fails and I have no idea why. 2021-02-09 09:43:02 +08:00
Daniel Leone ea152095a8 added what might be considered a test...
and cleaned up patch xhr load for sample terrain spec
2021-02-04 19:32:23 +08:00
Daniel Leone 23abe63750 more reverts 2021-02-04 17:45:34 +08:00
Daniel Leone 0177f21311 cleaning up some changes 2021-02-04 17:36:02 +08:00
Daniel Leone 43e67eb3e9 deleting some stuff 2021-02-04 16:03:58 +08:00
Daniel Leone 2380ce85b9 removed some unused code
run the puppeteer script with firefox
2021-02-04 12:43:46 +08:00
Daniel Leone d0cfece7ce swapped CWT to packed triangles as well 2021-02-04 12:23:24 +08:00
Daniel Leone 70e9b3a74c optimized both heightmap triangle creation functions 2021-02-04 12:02:29 +08:00
Daniel Leone 25f75822b4 turned off old-picking behaviour with a bool for now 2021-02-03 10:21:17 +08:00
Daniel Leone 9df18c8483 Merge branch 'fix-arcgis-sample-terrain-height' into fastPicking2 2021-02-03 10:14:56 +08:00
Daniel Leone 192323ebed removing some temporary code 2021-02-03 10:14:22 +08:00
Daniel Leone eb3d6bae67 added the pick method to the terrain mesh class so it can hide the triangle picking implementation from globe surface tile 2021-01-21 11:28:41 +08:00
Daniel Leone 9566943878 cleaned up some interfaces a little - removed some methods.
I believe everything is currently working
2021-01-21 10:27:23 +08:00
Daniel Leone 01d5a0b755 just went back to 2 axis overlap counts for now 2021-01-20 23:34:19 +08:00
Daniel Leone 737eb8d66e fixed the perf regression - it was the .concat() call... apparently the for loop is way faster. 2021-01-20 23:30:07 +08:00
Daniel Leone a0fad10a8c reverted - however we've still got the memory issues... hmmmmm what 2021-01-20 13:28:11 +08:00
Daniel Leone 3a6d8cca79 added flag to parse traces to print the logs 2021-01-20 13:11:16 +08:00
Daniel Leone 63bc3d328a switched to using a packed triangle array instead of a js-object triangle array...
seems to increase memory pressure dramatically causing lots and lots of GC's during octree creation.
2021-01-20 13:09:50 +08:00
Daniel Leone ee3759a0a2 fixing up some es6 compile errors 2021-01-20 11:47:22 +08:00
Daniel Leone 52c16a0c8c updated tracing script to measure all timers 2021-01-20 11:24:47 +08:00
Daniel Leone b391890dbd Merge remote-tracking branch 'upstream/master' into fastPicking2 2021-01-20 10:55:40 +08:00
Daniel Leone 10c2d4bfb2 * added commit hash to benchmark script
* removed the callback and just did it inline - seems to help a little bit
* lots more timer markings
2021-01-20 10:55:00 +08:00
Daniel Leone 51db7326c6 back to 2 functions
reduced the size of the tree even more
2021-01-15 14:07:53 +08:00
Daniel Leone bd6a3af42d wait I did that wrong I think.. now it's way slower 2021-01-15 13:02:10 +08:00
Daniel Leone 1048196a13 undo the bitmask thing - again more testing 2021-01-15 12:31:38 +08:00
Daniel Leone 1ed774cf5a * updated parse_traces.js to output --last=n amount 2021-01-15 12:26:40 +08:00
Daniel Leone 494fd9d35b just a checkpoint
* updated sandcastle for manual testing with ArcGIS
* updated parse trace script to only print the latest result
* inlined addNodeTriangleToChildren and converted to using a bitmask rather than 8-ifs (not sure if this is faster yet)
* increased triangles per node to 100 and small overlap count to 3. This means - each node will store upto 100 triangles in it - and only triangles which are complete(?) contained within the node.. so we should end up with a small tree - which less duplication of triangles spread in the tree.
2021-01-15 12:14:15 +08:00
Daniel Leone a17a680a47 added special flags to puppeteer to capture v8 opts and de-opts
- put that into a log file
- runs with the TerrainMesh sandcastle in full-screen mode
- outputs the timings + all the opts/de-opts for relevant functions
2021-01-14 16:40:19 +08:00
Daniel Leone a454c3c441 Merge branch 'fix-arcgis-sample-terrain-height' into fastPicking2
# Conflicts:
#	Apps/Sandcastle/gallery/Terrain.html
2021-01-14 14:06:03 +08:00
Daniel Leone b42fe7f7f9 * tried inlining - made everything works
* added some scripts to measure e2e performance over ~40 tiles

`node puppeteer.js && node parse_traces.js`
2021-01-12 12:03:06 +08:00
Daniel Leone 8ddbdb82f3 * hacked a few arguments in to Globe and GlobeSurfaceTile just for creating test data
* have a few passing tests
2021-01-11 15:23:31 +08:00
Daniel Leone 19c63752a7 Merge remote-tracking branch 'origin/fix-arcgis-sample-terrain-height' into fastPicking2 2021-01-08 10:40:22 +08:00
Daniel Leone a7dd147e97 Merge remote-tracking branch 'upstream/master' into fastPicking2 2021-01-08 10:40:06 +08:00
Daniel Leone 04387ec904 lots of debugging changes, don't test with this commit; it won't work. 2020-08-28 12:40:10 +08:00
Daniel Leone a17d60187f turned back on the new picking 2020-07-29 02:10:45 +08:00
Daniel Leone 161a67584c moved the octree into array buffers before sending cross-thread 2020-07-29 01:41:14 +08:00
Daniel Leone d082644cde Merge branch 'master' into fastPicking 2020-07-24 19:41:00 +08:00
Ian Lilley f1ce06120d Merge branch 'master' into fastPicking 2020-06-03 15:43:44 -04:00
Ian Lilley 3aa7401aef comment 2020-05-27 20:21:59 -04:00
Ian Lilley bdb276d7c9 added support for heightmaps 2020-05-27 09:26:47 -04:00
Ian Lilley 7e60cc60a6 picking in the worker for quantized mesh 2020-05-26 21:08:51 -04:00
Ian Lilley 22bb9243cc rename 2020-05-21 16:10:10 -04:00
Ian Lilley 60e5164d57 clreanup and more doc 2020-05-21 16:08:39 -04:00
Ian Lilley 446307c2d9 moved triangle picking to terrain mesh 2020-05-21 13:56:20 -04:00
Ian Lilley 892bbe457e minor 2020-05-21 12:20:15 -04:00
Ian Lilley 75a68123cf small optimiziation 2020-05-21 11:59:54 -04:00
Ian Lilley 99b90a17b3 not allocating the entire tri array up front 2020-05-21 10:49:59 -04:00
Ian Lilley 7b21e6e330 removed timers from globesurfacetile 2020-05-21 10:36:07 -04:00
Ian Lilley 09b1961b80 renamed filterdown variable names 2020-05-21 10:11:05 -04:00
Ian Lilley ba6d133c8f better support for backfaces 2020-05-20 22:24:19 -04:00
Ian Lilley d13bd0c606 claeanup 2020-05-20 21:22:43 -04:00
Ian Lilley 454d06cb84 fixed scratch var bug 2020-05-20 20:53:59 -04:00
Ian Lilley ba2919d467 added getTriVertsFromIndex 2020-05-20 12:12:40 -04:00
Ian Lilley b82f5d2d79 minor 2020-05-19 15:17:40 -04:00
Ian Lilley 921ac1c0a4 made bit logic faster 2020-05-19 14:09:54 -04:00
Ian Lilley 0c2becaaea alternate construction that may be faster 2020-05-18 19:18:08 -04:00
Ian Lilley d9546f4667 more balanced binning 2020-05-18 09:54:39 -04:00
Ian Lilley ba4aff81a6 small optimization pass 2020-05-17 16:28:58 -04:00
Ian Lilley cc7e38c64a faster traversal 2020-05-17 15:26:18 -04:00
Ian Lilley e150206d4e sadncastle improvements 2020-05-16 15:10:30 -04:00
Ian Lilley c7a8786303 fixed some bugs 2020-05-10 19:22:10 -04:00
Ian Lilley b95cbd282e fixed 2 bugs 2020-05-09 17:34:21 -04:00
Ian Lilley 6012a5fd02 initial 2020-05-09 16:22:44 -04:00
146 changed files with 11701 additions and 2382 deletions

View File

@ -2,7 +2,7 @@ name: deploy
on:
push:
branches-ignore:
- 'cesium.com'
- "cesium.com"
- production
concurrency:
group: deploy-${{ github.ref }}
@ -22,14 +22,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
BASE_URL: /cesium/${{ github.ref_name }}/
DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/
steps:
- uses: actions/checkout@v5
- name: install node 22
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: "22"
- name: npm install
run: npm install
- name: set the version in package.json
@ -42,8 +41,6 @@ jobs:
run: npm pack --workspaces &> /dev/null
- name: 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
- name: deploy to s3
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}

View File

@ -41,14 +41,17 @@ jobs:
run: npm install
- name: build website release
run: npm run website-release
- name: build apps
run: npm run build-apps
- name: build types
run: npm run build-ts
- name: build prod sandcastle
run: npm run build-prod -w packages/sandcastle -- -l warn
- name: build apps
run: npm run build-apps
- name: deploy to cesium.com
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: |
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"

View File

@ -2,7 +2,7 @@ name: sandcastle-dev
on:
push:
branches:
- 'cesium.com'
- "main"
jobs:
deploy:
runs-on: ubuntu-latest
@ -20,7 +20,7 @@ jobs:
- name: install node 22
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: "22"
- name: npm install
run: npm install
- name: build website release
@ -28,7 +28,7 @@ jobs:
- name: build types
run: npm run build-ts
- 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
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}
run: |

View File

@ -23,6 +23,8 @@
/scripts/
/favicon.ico
/gulpfile.js
/gulpfile.apps.js
/gulpfile.makezip.js
/index.html
/index.release.html
/launches

View File

@ -24,10 +24,7 @@
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
Cesium.Ion.defaultAccessToken = "";
const assetId = 1683;
const assetId = 3891169;
const azure = Cesium.ImageryLayer.fromProviderAsync(
Cesium.IonImageryProvider.fromAssetId(assetId),

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -45,10 +45,9 @@
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
{ label: "Bing Maps Aerial", assetId: 2 },
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
{ label: "Bing Maps Road", assetId: 4 },
{ label: "Bing Maps Labels Only", assetId: 2411391 },
{ label: "Azure Maps Aerial", assetId: 3891168 },
{ label: "Azure Maps Roads", assetId: 3891169 },
{ label: "Azure Maps Labels Only", assetId: 3891170 },
{ label: "Sentinel-2", assetId: 3954 },
];

View File

@ -30,9 +30,9 @@
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
baseLayer: Cesium.ImageryLayer.fromWorldImagery({
style: Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
}),
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
Cesium.IonImageryProvider.fromAssetId(3830183),
),
baseLayerPicker: false,
});
const layers = viewer.scene.imageryLayers;

View File

@ -1,29 +1,47 @@
# 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
### @cesium/engine
#### Breaking Changes :mega:
- `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.
- 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)
- `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:
- Added experimental support for loading 3D Tiles as terrain, via `Cesium3DTilesTerrainProvider`. See [the PR](https://github.com/CesiumGS/cesium/pull/12963) for limitations on the types of 3D Tiles that can be used. [#12296](https://github.com/CesiumGS/cesium/issues/12296)
- Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765)
#### Fixes :wrench:
- 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 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)
- 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 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 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

View File

@ -104,6 +104,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
- [Erin Ingram](https://github.com/eringram)
- [Daniel Zhong](https://github.com/danielzhong)
- [Mark Schlosser](https://github.com/markschlosseratbentley)
- [Adam Larkeryd](https://github.com/alarkbentley)
- [Don McCurdy](https://github.com/donmccurdy)
- [Flightradar24 AB](https://www.flightradar24.com)
- [Aleksei Kalmykov](https://github.com/kalmykov)
- [BIT Systems](http://www.caci.com/bit-systems)

View File

@ -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:
- **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)
- **Documentation** : reference documentation built from source. [Documentation guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/DocumentationGuide/README.md#documentation-guide)

View File

@ -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.
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.
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
@ -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.
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`.
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.
1. Sign in with LastPass
1. Sign in with Bitwarden
2. Go to Content at the top
3. Click "New Item" -> Developer Credentials -> API Key credentials
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
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.
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.**
25. Push your commits to main
- `git push`

View File

@ -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.

View File

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

View File

@ -12,7 +12,7 @@
"license": [
"BSD-3-Clause"
],
"version": "2.8.7",
"version": "2.8.8",
"url": "https://www.npmjs.com/package/@zip.js/zip.js"
},
{
@ -44,7 +44,7 @@
"license": [
"Apache-2.0"
],
"version": "3.2.7",
"version": "3.3.0",
"url": "https://www.npmjs.com/package/dompurify",
"notes": "dompurify is available as both MPL-2.0 OR Apache-2.0"
},

View File

@ -48,6 +48,8 @@ export default [
"scripts/**/*.js",
"packages/sandcastle/scripts/**/*.js",
"gulpfile.js",
"gulpfile.apps.js",
"gulpfile.makezip.js",
"server.js",
],
...configCesium.configs.node,

252
gulpfile.apps.js Normal file
View File

@ -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,
);

View File

@ -7,14 +7,9 @@ import { createRequire } from "module";
import { finished } from "stream/promises";
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 open from "open";
import { rimraf } from "rimraf";
import { mkdirp } from "mkdirp";
import karma from "karma";
import yargs from "yargs";
import typeScript from "typescript";
@ -29,7 +24,6 @@ import {
glslToJavaScript,
createCombinedSpecList,
createJsHintOptions,
defaultESBuildOptions,
} from "./scripts/build.js";
// 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 isProduction = process.env.PROD;
//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.
@ -92,34 +85,6 @@ const shaderFiles = [
"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() {
// Configure build options from command line arguments.
const minify = argv.minify ?? false;
@ -287,10 +252,6 @@ export async function buildTs() {
await createTypeScriptDefinitions();
}
export function buildApps() {
return Promise.all([buildCesiumViewer(), buildSandcastle()]);
}
const filesToClean = [
"Source/Cesium.js",
"Source/Shaders/**/*.js",
@ -520,184 +481,6 @@ export const postversion = async function () {
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() {
const buildVersion = argv.buildVersion;
if (buildVersion) {
@ -1239,13 +1022,6 @@ function generateTypeScriptDefinitions(
"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) {
let imports = "";
Object.keys(importModules).forEach((workspace) => {
@ -1259,6 +1035,13 @@ ${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
writeFileSync(definitionsPath, source);
@ -1630,203 +1413,3 @@ export async function buildThirdParty() {
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;
}

206
gulpfile.makezip.js Normal file
View File

@ -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;
},
);

View File

@ -90,7 +90,7 @@
</td>
</tr>
<tr>
<td><a href="Apps/Sandcastle/index.html">Sandcastle</a></td>
<td><a href="Apps/Sandcastle2/index.html">Sandcastle</a></td>
<td>
Cesium's live code editor and example gallery. Browse examples
highlighting features of the Cesium API and edit and run them

View File

@ -1,6 +1,6 @@
{
"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.",
"homepage": "http://cesium.com/cesiumjs/",
"license": "Apache-2.0",
@ -51,8 +51,8 @@
"./Specs/**/*"
],
"dependencies": {
"@cesium/engine": "^21.0.1",
"@cesium/widgets": "^13.2.1"
"@cesium/engine": "^22.0.0",
"@cesium/widgets": "^14.0.0"
},
"devDependencies": {
"@cesium/eslint-config": "^12.0.0",
@ -114,15 +114,15 @@
"build-watch": "gulp buildWatch",
"build-ts": "gulp buildTs",
"build-third-party": "gulp buildThirdParty",
"build-apps": "gulp buildApps",
"build-sandcastle": "npm run build-app --workspace packages/sandcastle",
"build-apps": "gulp -f gulpfile.apps.js buildApps",
"build-sandcastle": "gulp -f gulpfile.apps.js buildSandcastle",
"clean": "gulp clean",
"cloc": "gulp cloc",
"coverage": "gulp coverage",
"build-docs": "gulp buildDocs",
"build-docs-watch": "gulp buildDocsWatch",
"eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet",
"make-zip": "gulp makeZip",
"make-zip": "gulp -f gulpfile.makezip.js makeZip",
"markdownlint": "markdownlint \"**/*.md\"",
"release": "gulp release",
"website-release": "gulp websiteRelease",

View File

@ -222,6 +222,30 @@ AxisAlignedBoundingBox.intersectPlane = function (box, plane) {
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.
*
@ -245,6 +269,18 @@ AxisAlignedBoundingBox.prototype.intersectPlane = function (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
* <code>true</code> if they are equal, <code>false</code> otherwise.

View File

@ -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

View File

@ -210,6 +210,7 @@ GoogleEarthEnterpriseTerrainData.prototype.createMesh = function (options) {
result.vertexCountWithoutSkirts,
result.minimumHeight,
result.maximumHeight,
rectangleScratch,
BoundingSphere.clone(result.boundingSphere3D),
Cartesian3.clone(result.occludeePointInScaledSpace),
result.numberOfAttributes,

View File

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

View File

@ -495,6 +495,86 @@ IntersectionTests.rayEllipsoid = function (ray, ellipsoid) {
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) {
const difference = left + right;
if (

View File

@ -4,7 +4,7 @@ import Resource from "./Resource.js";
let defaultTokenCredit;
const defaultAccessToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjNjI5ZTViNy0wY2FhLTQ0ZDUtYTIzMi0wMWEyMzZkYWYwYWYiLCJpZCI6MjU5LCJpYXQiOjE3NTkzNDcyNDZ9.xyOPig1igKFQvOTaXfTE0KQ7dU7jyn_c3OQPaQ1hEiI";
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhN2VkNDM5ZS1jMDk0LTQ3NDItOTM5ZS00MzU3M2M1MTc2ZTkiLCJpZCI6MjU5LCJpYXQiOjE3NjIxODg4MDB9.ZZG574sONzeHxsX8HJMaL_ZiGA3dh_HrOxL7DrKRcd4";
/**
* Default settings for accessing the Cesium ion API.
*

View File

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

View File

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

View File

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

View File

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

View File

@ -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
* usually created from raw {@link TerrainData}.
@ -15,6 +26,7 @@
* @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} 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 {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,
@ -37,6 +49,7 @@ function TerrainMesh(
vertexCountWithoutSkirts,
minimumHeight,
maximumHeight,
rectangle,
boundingSphere3D,
occludeePointInScaledSpace,
vertexStride,
@ -101,6 +114,12 @@ function TerrainMesh(
*/
this.maximumHeight = maximumHeight;
/**
* The rectangle, in radians, covered by this tile.
* @type {Rectangle}
*/
this.rectangle = rectangle;
/**
* A bounding sphere that completely contains the tile.
* @type {BoundingSphere}
@ -150,5 +169,193 @@ function TerrainMesh(
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
*/
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;

View File

@ -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;

View File

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

View File

@ -122,6 +122,8 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
return node;
}
const borderPadding = this._borderPadding;
// Vertical split (childNode1 = left half, childNode2 = right half).
if (widthDifference > heightDifference) {
node.childNode1 = new TextureNode({
@ -130,12 +132,18 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
width,
height: nodeHeight,
});
// Apply padding only along the vertical "cut".
const widthDifferencePadded = widthDifference - borderPadding;
if (widthDifferencePadded > 0) {
node.childNode2 = new TextureNode({
x: rectangle.x + width,
x: rectangle.x + width + borderPadding,
y: rectangle.y,
width: widthDifference,
width: widthDifferencePadded,
height: nodeHeight,
});
}
return this._findNode(node.childNode1, { width, height });
}
@ -147,12 +155,19 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
width: nodeWidth,
height,
});
// Apply padding only along the horizontal "cut".
const heightDifferencePadded = heightDifference - borderPadding;
if (heightDifferencePadded > 0) {
node.childNode2 = new TextureNode({
x: rectangle.x,
y: rectangle.y + height,
y: rectangle.y + height + borderPadding,
width: nodeWidth,
height: heightDifference,
height: heightDifferencePadded,
});
}
return this._findNode(node.childNode1, { width, height });
}

View File

@ -1131,7 +1131,15 @@ Transforms.rotationMatrixFromPositionVelocity = function (
return result;
};
const swizzleMatrix = new Matrix4(
/**
* An immutable matrix that swaps x, y, z for 2D.
*
* @type {Matrix4}
* @constant
* @private
*/
Transforms.SWIZZLE_3D_TO_2D_MATRIX = Object.freeze(
new Matrix4(
0.0,
0.0,
1.0,
@ -1148,6 +1156,7 @@ const swizzleMatrix = new Matrix4(
0.0,
0.0,
1.0,
),
);
const scratchCartographic = new Cartographic();
@ -1210,7 +1219,7 @@ Transforms.basisTo2D = function (projection, matrix, result) {
const toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);
const rotation = Matrix4.getMatrix3(matrix, scratchRotation);
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
return result;
@ -1260,7 +1269,7 @@ Transforms.ellipsoidTo2DModelMatrix = function (projection, center, result) {
projectedPosition,
scratchFromENU,
);
Matrix4.multiply(swizzleMatrix, toENU, result);
Matrix4.multiply(Transforms.SWIZZLE_3D_TO_2D_MATRIX, toENU, result);
Matrix4.multiply(translation, result, result);
return result;

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js";
* Initialization options for the BillboardGraphics constructor
*
* @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 | 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.

View File

@ -131,10 +131,6 @@ BillboardVisualizer.prototype.update = function (time) {
}
billboard.show = show;
if (item.textureValue !== textureValue) {
billboard.image = textureValue;
item.textureValue = textureValue;
}
billboard.position = position;
billboard.color = Property.getValueOrDefault(
billboardGraphics._color,
@ -227,6 +223,13 @@ BillboardVisualizer.prototype.update = function (time) {
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(
billboardGraphics._imageSubRegion,
time,

View File

@ -72,6 +72,26 @@ function Buffer(options) {
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.
* <br /><br />
@ -242,6 +262,18 @@ Buffer.prototype._getBuffer = function () {
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) {
offsetInBytes = offsetInBytes ?? 0;

View File

@ -7,12 +7,14 @@ const BufferUsage = {
STREAM_DRAW: WebGLConstants.STREAM_DRAW,
STATIC_DRAW: WebGLConstants.STATIC_DRAW,
DYNAMIC_DRAW: WebGLConstants.DYNAMIC_DRAW,
DYNAMIC_READ: WebGLConstants.DYNAMIC_READ,
validate: function (bufferUsage) {
return (
bufferUsage === BufferUsage.STREAM_DRAW ||
bufferUsage === BufferUsage.STATIC_DRAW ||
bufferUsage === BufferUsage.DYNAMIC_DRAW
bufferUsage === BufferUsage.DYNAMIC_DRAW ||
bufferUsage === BufferUsage.DYNAMIC_READ
);
},
};

View File

@ -1,3 +1,4 @@
import Buffer from "./Buffer.js";
import Check from "../Core/Check.js";
import Color from "../Core/Color.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
* @param {object} readState An object with the following properties:
* @param {number} [readState.x=0] The x offset of the rectangle to read from.
* @param {number} [readState.y=0] The y offset of the rectangle to read from.
* @param {number} [readState.width=this.drawingBufferWidth] The width of the rectangle to read from.
* @param {number} [readState.height=this.drawingBufferHeight] The height of the rectangle to read from.
* @param {Framebuffer} [readState.framebuffer] The framebuffer to read from. If undefined, the read will be from the default framebuffer.
* @property {number} [x=0] The x offset of the rectangle to read from.
* @property {number} [y=0] The y offset of the rectangle to read from.
* @property {number} [width=this.drawingBufferWidth] The width of the rectangle to read from.
* @property {number} [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.
*/
/**
* 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.
*/
Context.prototype.readPixels = function (readState) {
@ -1467,12 +1543,14 @@ Context.prototype.readPixels = function (readState) {
//>>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 = PixelFormat.createTypedArray(
PixelFormat.RGBA,
pixelFormat,
pixelDatatype,
width,
height,

View File

@ -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;

View File

@ -59,12 +59,16 @@ function TextureAtlas(options) {
this._initialSize = initialSize;
this._texturePacker = undefined;
/** @type {BoundingRectangle[]} */
this._rectangles = [];
/** @type {Map<number, number>} */
this._subRegions = new Map();
this._guid = createGuid();
this._imagesToAddQueue = [];
/** @type {Map<string, number>} */
this._indexById = new Map();
/** @type {Map<string, Promise<number>>} */
this._indexPromiseById = new Map();
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 {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.
* @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);
Check.typeOf.string("id", id);
Check.defined("image", image);
//>>includeEnd('debug');
let promise = this._indexPromiseById.get(id);
let index = this._indexById.get(id);
if (defined(promise)) {
// This image has already been added
// This image is already being added
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);
const resolveAndAddImage = async () => {
image = await resolveImage(image, id);
const resolvedImage = await resolveImage(image, id);
//>>includeStart('debug', pragmas.debug);
Check.defined("image", image);
Check.defined("image", resolvedImage);
//>>includeEnd('debug');
if (this.isDestroyed() || !defined(image)) {
if (this.isDestroyed() || !defined(resolvedImage)) {
this._indexPromiseById.delete(id);
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();
@ -679,46 +700,65 @@ TextureAtlas.prototype.addImage = function (id, image) {
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.
* @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.
* @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) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("id", id);
Check.defined("subRegion", subRegion);
//>>includeEnd('debug');
const imageIndex = this._indexById.get(id);
if (!defined(imageIndex)) {
throw new RuntimeError(`image with id "${id}" not found in the atlas.`);
}
const indexPromise = 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
return indexPromise.then((resolvedImageIndex) => {
if (resolvedImageIndex === -1) {
// The atlas has been destroyed
return -1;
}
let index = this.getCachedImageSubRegion(id, subRegion, imageIndex);
if (defined(index)) {
return index;
});
}
}
}
const index = this._nextIndex++;
index = this._nextIndex++;
this._subRegions.set(index, imageIndex);
this._rectangles[index] = subRegion.clone();
const indexPromise =
this._indexPromiseById.get(id) ?? Promise.resolve(imageIndex);
return indexPromise.then((imageIndex) => {
if (imageIndex === -1) {
// The atlas has been destroyed

View File

@ -4,7 +4,7 @@ import Resource from "../Core/Resource.js";
let defaultTokenCredit;
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.
*

View File

@ -1,4 +1,5 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import Credit from "../Core/Credit.js";
import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
@ -29,7 +30,6 @@ const trailingSlashRegex = /\/$/;
*
* @alias Azure2DImageryProvider
* @constructor
* @private
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
*
* @example
@ -41,16 +41,18 @@ const trailingSlashRegex = /\/$/;
*/
function Azure2DImageryProvider(options) {
options = options ?? {};
const maximumLevel = options.maximumLevel ?? 22;
const minimumLevel = options.minimumLevel ?? 0;
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"];
//>>includeStart('debug', pragmas.debug);
Check.defined("options.subscriptionKey", subscriptionKey);
Check.defined("options.subscriptionKey", this._subscriptionKey);
//>>includeEnd('debug');
this._tilesetId = options.tilesetId;
const resource =
options.url instanceof IonResource
? options.url
@ -60,19 +62,23 @@ function Azure2DImageryProvider(options) {
if (!trailingSlashRegex.test(templateUrl)) {
templateUrl += "/";
}
templateUrl += `map/tile`;
resource.url = templateUrl;
const tilesUrl = `${templateUrl}map/tile`;
this._viewportUrl = `${templateUrl}map/attribution`;
resource.url = tilesUrl;
resource.setQueryParameters({
"api-version": "2024-04-01",
tilesetId: tilesetId,
"subscription-key": this._subscriptionKey,
zoom: `{z}`,
x: `{x}`,
y: `{y}`,
"subscription-key": subscriptionKey,
});
this._resource = resource;
let credit;
if (defined(options.credit)) {
credit = options.credit;
@ -83,8 +89,8 @@ function Azure2DImageryProvider(options) {
const provider = new UrlTemplateImageryProvider({
...options,
maximumLevel,
minimumLevel,
maximumLevel: this._maximumLevel,
minimumLevel: this._minimumLevel,
url: resource,
credit: credit,
});
@ -93,6 +99,7 @@ function Azure2DImageryProvider(options) {
// This will be defined for ion resources
this._tileCredits = resource.credits;
this._attributionsByLevel = undefined;
}
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.
*/
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,
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;
};
/**
* 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
export default Azure2DImageryProvider;

View File

@ -23,6 +23,8 @@ import SceneMode from "./SceneMode.js";
import SceneTransforms from "./SceneTransforms.js";
import VerticalOrigin from "./VerticalOrigin.js";
import SplitDirection from "./SplitDirection.js";
import getExtensionFromUri from "../Core/getExtensionFromUri.js";
import isDataUri from "../Core/isDataUri.js";
/**
* @typedef {object} Billboard.ConstructorOptions
@ -32,7 +34,7 @@ import SplitDirection from "./SplitDirection.js";
* @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 {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 {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.
@ -189,31 +191,27 @@ function Billboard(options, billboardCollection) {
this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
this._imageTexture = new BillboardTexture(billboardCollection);
this._imageId = options.imageId;
this._imageWidth = undefined;
this._imageHeight = undefined;
this._labelDimensions = undefined;
this._labelHorizontalOrigin = undefined;
this._labelTranslate = undefined;
const image = options.image;
let imageId = options.imageId;
if (defined(image)) {
if (!defined(imageId)) {
if (typeof image === "string") {
imageId = image;
} else if (defined(image.src)) {
imageId = image.src;
} else {
imageId = createGuid();
}
}
this._imageTexture.loadImage(imageId, image);
this._computeImageTextureProperties(options.imageId, image);
this._imageTexture.loadImage(
this._imageId,
image,
this._imageWidth,
this._imageHeight,
);
}
if (defined(options.imageSubRegion)) {
this._imageTexture.addImageSubRegion(imageId, options.imageSubRegion);
this._imageTexture.addImageSubRegion(this._imageId, options.imageSubRegion);
}
this._actualClampedPosition = undefined;
@ -947,18 +945,13 @@ Object.defineProperties(Billboard.prototype, {
return;
}
let id;
if (typeof value === "string") {
id = value;
} else if (value instanceof Resource) {
id = value._url;
} else if (defined(value.src)) {
id = value.src;
} else {
id = createGuid();
}
this._imageTexture.loadImage(id, value);
this._computeImageTextureProperties(undefined, value);
this._imageTexture.loadImage(
this._imageId,
value,
this._imageWidth,
this._imageHeight,
);
},
},
@ -1252,7 +1245,13 @@ Billboard.prototype.setImage = function (id, image) {
Check.defined("image", image);
//>>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);
};
/** 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,
* measured in pixels from the bottom-left.

View File

@ -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 {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.
* @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) {
// This image has already been loaded
return;
@ -192,7 +199,7 @@ BillboardTexture.prototype.loadImage = async function (id, image) {
let index;
const atlas = this._billboardCollection.textureAtlas;
try {
index = await atlas.addImage(id, image);
index = await atlas.addImage(id, image, width, height);
} catch (error) {
// There was an error loading the image
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 {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._loadState = BillboardLoadState.LOADING;
this._loadError = undefined;
this._hasSubregion = true;
let index;
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 {
index = await atlas.addImageSubRegion(id, subRegion);
this._loadState = BillboardLoadState.LOADING;
index = await indexPromise;
} catch (error) {
// There was an error loading the referenced image
this._loadState = BillboardLoadState.ERROR;
@ -268,6 +297,27 @@ BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
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) {
this._loadState = BillboardLoadState.FAILED;
this._index = -1;
@ -280,7 +330,6 @@ BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
this._height = subRegion.height;
this._index = index;
this._loadState = BillboardLoadState.LOADED;
this.dirty = true;
};

View File

@ -125,7 +125,6 @@ GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt = function (
tileset,
) {
let hasGaussianSplatExtension = false;
let hasLegacyGaussianSplatExtension = false;
if (tileset.isGltfExtensionRequired instanceof Function) {
hasGaussianSplatExtension =
tileset.isGltfExtensionRequired("KHR_gaussian_splatting") &&
@ -133,22 +132,20 @@ GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt = function (
"KHR_gaussian_splatting_compression_spz_2",
);
hasLegacyGaussianSplatExtension = tileset.isGltfExtensionRequired(
"KHR_spz_gaussian_splats_compression",
);
}
if (hasLegacyGaussianSplatExtension) {
if (
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 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 " +
"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.",
);
}
}
return hasGaussianSplatExtension || hasLegacyGaussianSplatExtension;
return hasGaussianSplatExtension;
};
Object.defineProperties(GaussianSplat3DTileContent.prototype, {

View File

@ -943,7 +943,9 @@ Globe.prototype.getHeight = function (cartographic) {
const intersection = tile.data.pick(
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,
false,
scratchGetHeightIntersection,

View File

@ -1,12 +1,9 @@
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Cartographic from "../Core/Cartographic.js";
import defined from "../Core/defined.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import IntersectionTests from "../Core/IntersectionTests.js";
import PixelFormat from "../Core/PixelFormat.js";
import Ray from "../Core/Ray.js";
import Request from "../Core/Request.js";
import RequestState from "../Core/RequestState.js";
import RequestType from "../Core/RequestType.js";
@ -23,7 +20,6 @@ import TextureWrap from "../Renderer/TextureWrap.js";
import VertexArray from "../Renderer/VertexArray.js";
import ImageryState from "./ImageryState.js";
import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
import SceneMode from "./SceneMode.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 (
ray,
mode,
@ -160,42 +129,11 @@ GlobeSurfaceTile.prototype.pick = function (
cullBackFaces,
result,
) {
const mesh = this.renderedMesh;
if (!defined(mesh)) {
if (!defined(this.renderedMesh)) {
return undefined;
}
const vertices = mesh.vertices;
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;
const value = this.renderedMesh.pick(ray, cullBackFaces, mode, projection);
return Cartesian3.clone(value, result);
};
GlobeSurfaceTile.prototype.freeResources = function () {
@ -473,8 +411,10 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
encoding.exaggeration !== exaggeration;
const encodingRelativeHeightChanged =
encoding.exaggerationRelativeHeight !== exaggerationRelativeHeight;
const exaggerationChanged =
encodingExaggerationScaleChanged || encodingRelativeHeightChanged;
if (encodingExaggerationScaleChanged || encodingRelativeHeightChanged) {
if (exaggerationChanged) {
// Turning exaggeration scale on/off requires adding or removing geodetic surface normals
// Relative height only translates, so it has no effect on normals
if (encodingExaggerationScaleChanged) {
@ -498,9 +438,21 @@ GlobeSurfaceTile.prototype.updateExaggeration = function (
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) {
let available = terrainProvider.getTileDataAvailable(
tile.x,
@ -929,7 +881,15 @@ function createWaterMaskTextureIfNeeded(context, surfaceTile) {
let texture;
const waterMaskLength = waterMask.length;
if (waterMaskLength === 1) {
if (waterMask instanceof ImageBitmap) {
texture = Texture.create({
context: context,
source: waterMask,
sampler: waterMaskData.sampler,
flipY: false,
skipColorSpaceConversion: true,
});
} else if (waterMaskLength === 1) {
// Length 1 means the tile is entirely land or entirely water.
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
if (waterMask[0] !== 0) {

View File

@ -193,6 +193,7 @@ function GlobeSurfaceTileProvider(options) {
this._oldVerticalExaggeration = undefined;
this._oldVerticalExaggerationRelativeHeight = undefined;
this._oldSceneMode = SceneMode.SCENE3D;
}
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.
const tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
for (

View File

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

View File

@ -1999,11 +1999,6 @@ function fetchSpzExtensionFrom(extensions) {
return spz;
}
const legacySpz = extensions?.KHR_spz_gaussian_splats_compression;
if (defined(legacySpz)) {
return legacySpz;
}
return undefined;
}

View File

@ -101,6 +101,8 @@ function Google2DImageryProvider(options) {
key: encodeURIComponent(options.key),
});
this._resource = resource.clone();
let credit;
if (defined(options.credit)) {
credit = options.credit;
@ -312,7 +314,7 @@ Object.defineProperties(Google2DImageryProvider.prototype, {
* });
* @example
* // Google 2D roadmap overlay with custom styles
* const googleTileProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
* assetId: 3830184,
* overlayLayerType: "layerRoadmap",
* styles: [
@ -403,7 +405,7 @@ Google2DImageryProvider.fromIonAssetId = async function (options) {
* // Google 2D roadmap overlay with custom styles
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
*
* const googleTileProvider = Cesium.Google2DImageryProvider.fromUrl({
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromUrl({
* overlayLayerType: "layerRoadmap",
* styles: [
* {
@ -533,12 +535,7 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
const promises = [];
for (let level = 0; level < maximumLevel + 1; level++) {
promises.push(
fetchViewportAttribution(
this._viewportUrl,
this._key,
this._session,
level,
),
fetchViewportAttribution(this._resource, this._viewportUrl, level),
);
}
const results = await Promise.all(promises);
@ -559,12 +556,10 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
return attributionsByLevel;
};
async function fetchViewportAttribution(url, key, session, level) {
const viewport = await Resource.fetch({
url: url,
async function fetchViewportAttribution(resource, url, level) {
const viewportResource = resource.getDerivedResource({
url,
queryParameters: {
key,
session,
zoom: level,
north: 90,
south: -90,
@ -573,7 +568,7 @@ async function fetchViewportAttribution(url, key, session, level) {
},
data: JSON.stringify(Frozen.EMPTY_OBJECT),
});
const viewportJson = JSON.parse(viewport);
const viewportJson = await viewportResource.fetchJson();
return viewportJson.copyright;
}

View File

@ -370,7 +370,6 @@ ModelUtility.supportedExtensions = {
KHR_texture_transform: true,
KHR_gaussian_splatting: true,
KHR_gaussian_splatting_compression_spz_2: true,
KHR_spz_gaussian_splats_compression: true,
WEB3D_quantized_attributes: true,
};

View File

@ -4,6 +4,10 @@ import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import FramebufferManager from "../Renderer/FramebufferManager.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
@ -27,47 +31,18 @@ function PickFramebuffer(context) {
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.
* @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,
});
function pickObjectsFromPixels(context, pixels, width, height, limit = 1) {
const max = Math.max(width, height);
const length = max * max;
const halfWidth = Math.floor(width * 0.5);
@ -121,6 +96,119 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle, limit = 1) {
y += dy;
}
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);
};
/**

View File

@ -8,6 +8,7 @@ import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Matrix4 from "../Core/Matrix4.js";
import oneTimeWarning from "../Core/oneTimeWarning.js";
import OrthographicFrustum from "../Core/OrthographicFrustum.js";
import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.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
* 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>
* Setup needed before picking.
*
* @param {Scene} scene
* @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} [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,
windowPosition,
drawingBufferRectangle,
width,
height,
limit = 1,
) {
//>>includeStart('debug', pragmas.debug);
Check.defined("windowPosition", windowPosition);
//>>includeEnd('debug');
const { context, frameState, defaultView } = scene;
const { viewport, pickFramebuffer } = defaultView;
@ -304,12 +296,12 @@ Picking.prototype.pick = function (
windowPosition,
scratchPosition,
);
const drawingBufferRectangle = computePickingDrawingBufferRectangle(
computePickingDrawingBufferRectangle(
context.drawingBufferHeight,
drawingBufferPosition,
width,
height,
scratchRectangle,
drawingBufferRectangle,
);
scene.jobScheduler.disableThisFrame();
@ -334,9 +326,99 @@ Picking.prototype.pick = function (
scene.updateAndExecuteCommands(passState, scratchColorZero);
scene.resolveFramebuffers(passState);
}
const pickedObjects = pickFramebuffer.end(drawingBufferRectangle, limit);
/**
* Teardown needed after picking.
*
* @param {Scene} scene
*/
function pickEnd(scene) {
const { context } = scene;
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;
};

View File

@ -3932,14 +3932,15 @@ function callAfterRenderFunctions(scene) {
// Functions are queued up during primitive update and executed here in case
// the function modifies scene state that should remain constant over the frame.
const functions = scene._frameState.afterRender;
for (let i = 0; i < functions.length; ++i) {
const shouldRequestRender = functions[i]();
const functionsCpy = functions.slice(); // Snapshot before iterate allows callbacks to add functions for next frame
functions.length = 0;
for (let i = 0; i < functionsCpy.length; ++i) {
const shouldRequestRender = functionsCpy[i]();
if (shouldRequestRender) {
scene.requestRender();
}
}
functions.length = 0;
}
function getGlobeHeight(scene) {
@ -4517,6 +4518,37 @@ Scene.prototype.pick = function (windowPosition, width, height) {
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,
* or <code>undefined</code> if no voxel is rendered at that position.

View File

@ -1274,6 +1274,7 @@ function createFillMesh(tileProvider, frameState, tile, vertexArraysToDestroy) {
vertexCount,
minimumHeight,
maximumHeight,
rectangle,
BoundingSphere.fromOrientedBoundingBox(obb),
computeOccludeePoint(
tileProvider,

View File

@ -172,38 +172,6 @@ function VoxelPrimitive(options) {
*/
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.
*
@ -238,6 +206,22 @@ function VoxelPrimitive(options) {
*/
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
*
@ -271,30 +255,12 @@ function VoxelPrimitive(options) {
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}
* @private
*/
this._exaggeratedModelMatrix = 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();
this._modelMatrixOld = Matrix4.clone(this._modelMatrix);
/**
* @type {CustomShader}
@ -622,21 +588,7 @@ function initialize(primitive, provider) {
primitive.minClippingBounds = minBounds.clone();
primitive.maxClippingBounds = maxBounds.clone();
// Initialize the exaggerated versions of bounds and model matrix
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);
checkTransformAndBounds(primitive);
// Create the shape object, and update it so it is valid for VoxelTraversal
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
@ -1176,13 +1128,12 @@ VoxelPrimitive.prototype.update = function (frameState) {
return;
}
updateVerticalExaggeration(this, frameState);
// 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
// getters.
const shapeDirty = checkTransformAndBounds(this, provider);
if (shapeDirty) {
const shapeDirty = checkTransformAndBounds(this);
const exaggerationChanged = updateVerticalExaggeration(this, frameState);
if (shapeDirty || exaggerationChanged) {
this._shapeVisible = updateShapeAndTransforms(this);
if (checkShapeDefines(this)) {
this._shaderDirty = true;
@ -1383,102 +1334,30 @@ function getTileCoordinates(primitive, positionLocal, result) {
const scratchExaggerationScale = new Cartesian3();
const scratchExaggerationCenter = new Cartesian3();
const scratchCartographicCenter = new Cartographic();
const scratchExaggerationTranslation = new Cartesian3();
/**
* Update the exaggerated bounds of a primitive to account for vertical exaggeration
* @param {VoxelPrimitive} primitive
* @param {FrameState} frameState
* Check for changes in the vertical exaggeration of the primitive
* @param {VoxelPrimitive} primitive The primitive to update
* @param {FrameState} frameState The current frame state
* @returns {boolean} <code>true</code> if the exaggeration was changed
* @private
*/
function updateVerticalExaggeration(primitive, frameState) {
primitive._exaggeratedMinBounds = Cartesian3.clone(
primitive._minBounds,
primitive._exaggeratedMinBounds,
);
primitive._exaggeratedMaxBounds = Cartesian3.clone(
primitive._maxBounds,
primitive._exaggeratedMaxBounds,
);
const { verticalExaggeration, verticalExaggerationRelativeHeight } =
frameState;
if (primitive.shape === VoxelShapeType.ELLIPSOID) {
// Apply the exaggeration by stretching the height bounds
const relativeHeight = frameState.verticalExaggerationRelativeHeight;
const exaggeration = frameState.verticalExaggeration;
primitive._exaggeratedMinBounds.z =
(primitive._minBounds.z - relativeHeight) * exaggeration + relativeHeight;
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,
);
}
if (
primitive._verticalExaggeration === verticalExaggeration &&
primitive._verticalExaggerationRelativeHeight ===
verticalExaggerationRelativeHeight
) {
return false;
}
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
// to relativeHeight, after it is scaled by verticalExaggeration
const exaggeratedHeight = VerticalExaggeration.getHeight(
centerHeight,
frameState.verticalExaggeration,
frameState.verticalExaggerationRelativeHeight,
);
return Cartesian3.fromElements(
0.0,
0.0,
(exaggeratedHeight - centerHeight) / frameState.verticalExaggeration,
scratchExaggerationTranslation,
);
primitive._verticalExaggeration = verticalExaggeration;
primitive._verticalExaggerationRelativeHeight =
verticalExaggerationRelativeHeight;
return true;
}
/**
@ -1575,39 +1454,14 @@ function initFromProvider(primitive, provider, context) {
/**
* Track changes in provider transform and primitive bounds
* @param {VoxelPrimitive} primitive
* @param {VoxelProvider} provider
* @returns {boolean} Whether any of the transform or bounds changed
* @private
*/
function checkTransformAndBounds(primitive, provider) {
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,
);
function checkTransformAndBounds(primitive) {
const numChanges =
updateBound(primitive, "_compoundModelMatrix", "_compoundModelMatrixOld") +
updateBound(primitive, "_modelMatrix", "_modelMatrixOld") +
updateBound(primitive, "_minBounds", "_minBoundsOld") +
updateBound(primitive, "_maxBounds", "_maxBoundsOld") +
updateBound(
primitive,
"_exaggeratedMinBounds",
"_exaggeratedMinBoundsOld",
) +
updateBound(
primitive,
"_exaggeratedMaxBounds",
"_exaggeratedMaxBoundsOld",
) +
updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") +
updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld");
return numChanges > 0;
@ -1633,6 +1487,13 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
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
* @param {VoxelPrimitive} primitive
@ -1640,13 +1501,95 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
* @private
*/
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 visible = shape.update(
primitive._compoundModelMatrix,
primitive._exaggeratedMinBounds,
primitive._exaggeratedMaxBounds,
primitive.minClippingBounds,
primitive.maxClippingBounds,
compoundModelMatrix,
exaggeratedMinBounds,
exaggeratedMaxBounds,
exaggeratedMinClippingBounds,
exaggeratedMaxClippingBounds,
);
if (!visible) {
return false;
@ -1668,6 +1611,70 @@ function updateShapeAndTransforms(primitive) {
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.
* @param {VoxelTraversal} traversal

View File

@ -105,6 +105,8 @@ Object.defineProperties(VoxelShape.prototype, {
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum 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.
*/
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;

View File

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

View File

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

View File

@ -0,0 +1,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);

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,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",
);

View File

@ -178,9 +178,10 @@ describe(
const bb = billboardCollection.get(0);
await pollToPromise(function () {
entityCluster.update(scene.frameState);
scene.renderForSpecs();
visualizer.update(time);
return bb.show;
return bb.ready;
});
expect(bb.position).toEqual(testObject.position.getValue(time));

View File

@ -690,6 +690,160 @@ describe(
b.destroy();
}).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",

View File

@ -5,6 +5,9 @@ import {
BufferUsage,
Context,
ContextLimits,
ClearCommand,
PixelFormat,
PixelDatatype,
} from "../../index.js";
import createContext from "../../../../Specs/createContext.js";
@ -340,6 +343,65 @@ describe(
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",
);

View File

@ -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

View File

@ -40,8 +40,10 @@ describe("Scene/Azure2DImageryProvider", function () {
tilesetId: "a-tileset-id",
});
provider._attributionsByLevel = {};
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.tileHeight).toEqual(256);
@ -74,6 +76,8 @@ describe("Scene/Azure2DImageryProvider", function () {
rectangle: rectangle,
});
provider._attributionsByLevel = {};
expect(provider.tileWidth).toEqual(256);
expect(provider.tileHeight).toEqual(256);
expect(provider.maximumLevel).toBe(22);
@ -133,6 +137,7 @@ describe("Scene/Azure2DImageryProvider", function () {
subscriptionKey: "test-subscriptionKey",
tilesetId: "a-tileset-id",
});
provider._attributionsByLevel = {};
const layer = new ImageryLayer(provider);

View File

@ -347,6 +347,40 @@ describe("Scene/BillboardCollection", function () {
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 () {
expect(billboards.isDestroyed()).toEqual(false);
});

View File

@ -184,112 +184,3 @@ describe(
},
"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",
);

View File

@ -214,71 +214,3 @@ describe(
},
"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",
);

View File

@ -1,11 +1,6 @@
import {
Cartesian3,
Cartesian4,
createWorldTerrainAsync,
Ellipsoid,
EllipsoidTerrainProvider,
GeographicTilingScheme,
Ray,
GlobeSurfaceTile,
ImageryLayerCollection,
QuadtreeTile,
@ -17,8 +12,6 @@ import MockImageryProvider from "../../../../Specs/MockImageryProvider.js";
import MockTerrainProvider from "../../../../Specs/MockTerrainProvider.js";
import TerrainTileProcessor from "../../../../Specs/TerrainTileProcessor.js";
import createScene from "../../../../Specs/createScene.js";
describe("Scene/GlobeSurfaceTile", function () {
let frameState;
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 () {
beforeEach(function () {
processor.mockWebGL();

View File

@ -22,6 +22,8 @@ import {
Primitive,
SceneMode,
VoxelPrimitive,
Sync,
WebGLConstants,
} from "../../index.js";
import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js";
@ -93,13 +95,14 @@ describe(
scene.mode = SceneMode.SCENE3D;
scene.morphTime = SceneMode.getMorphTime(scene.mode);
camera.setView({
destination: largeRectangle,
});
// Note: Important the camera.frustum is set before camera.setView
camera.frustum = new PerspectiveFrustum();
camera.frustum.fov = CesiumMath.toRadians(60.0);
camera.frustum.aspectRatio = 1.0;
camera.setView({
destination: largeRectangle,
});
});
afterEach(function () {
@ -235,6 +238,63 @@ describe(
scene.renderForSpecs();
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 () {

View File

@ -48,6 +48,7 @@ import {
ColorGeometryInstanceAttribute,
HeightReference,
SharedContext,
Sync,
} from "../../index.js";
import createCanvas from "../../../../Specs/createCanvas.js";
@ -636,6 +637,10 @@ function pickMetadataAt(scene, schemaId, className, propertyName, x, y) {
describe(
"Scene/Scene",
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;
beforeAll(function () {
@ -846,6 +851,31 @@ describe(
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) {
this.update = function (frameState) {
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 () {
if (!scene.pickPositionSupported) {
return;

Some files were not shown because too many files have changed in this diff Show More