diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 8a02ca44d78e..3b982b06af77 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: install node 18 + - name: install node 20 uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '20' - name: npm install run: npm install - name: lint *.js @@ -27,8 +27,8 @@ jobs: coverage: runs-on: ubuntu-latest env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-1 BRANCH: ${{ github.ref_name }} steps: @@ -45,7 +45,7 @@ jobs: run: npm run coverage -- --browsers FirefoxHeadless --webgl-stub --failTaskOnError --suppressPassed - name: upload coverage artifacts if: ${{ env.AWS_ACCESS_KEY_ID != '' }} - run: aws s3 sync ./Build/Coverage s3://cesium-dev/cesium/$BRANCH/Build/Coverage --delete --color on + run: aws s3 sync ./Build/Coverage s3://cesium-public-builds/cesium/$BRANCH/Build/Coverage --delete --color on release-tests: runs-on: ubuntu-latest steps: @@ -69,8 +69,8 @@ jobs: contents: read env: BUILD_VERSION: ${{ github.ref_name }}.${{ github.run_number }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-1 BRANCH: ${{ github.ref_name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -97,7 +97,22 @@ jobs: - uses: ./.github/actions/verify-package - name: deploy to s3 if: ${{ env.AWS_ACCESS_KEY_ID != '' }} - run: npm run deploy-s3 -- -b "cesium-dev" -d cesium/$BRANCH -c 'no-cache' --confirm + run: | + aws s3 sync . s3://cesium-public-builds/cesium/$BRANCH/ \ + --cache-control "no-cache" \ + --exclude ".git/*" \ + --exclude ".concierge/*" \ + --exclude ".github/*" \ + --exclude ".husky/*" \ + --exclude ".vscode/*" \ + --exclude "Build/Coverage/*" \ + --exclude "Build/CesiumDev/*" \ + --exclude "Build/Specs/e2e" \ + --exclude "Documentation/*" \ + --exclude "node_modules/*" \ + --exclude "scripts/*" \ + --exclude "Tools/*" \ + --delete - name: set status if: ${{ env.AWS_ACCESS_KEY_ID != '' }} run: npm run deploy-status -- --status success --message Deployed @@ -105,10 +120,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: install node 16 + - name: install node 18 uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '18' - name: npm install run: npm install - name: release build diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 775d2db212d9..2ddc67d198ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ on: jobs: if_error_or_failure: runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion != 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'failure' }} steps: - name: message result in slack id: slack diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index cdd897e6c41e..63fc926c4b12 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -8,10 +8,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: install node 18 + - name: install node 20 uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '20' - name: npm install run: npm install - name: lint *.js @@ -22,13 +22,10 @@ jobs: run: npm run prettier-check deploy: runs-on: ubuntu-latest - permissions: - statuses: write env: PROD: true - BUILD_VERSION: ${{ github.ref_name }}.${{ github.run_number }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-1 BRANCH: ${{ github.ref_name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -36,10 +33,10 @@ jobs: GITHUB_SHA: ${{ github.sha }} steps: - uses: actions/checkout@v3 - - name: install node 18 + - name: install node 20 uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '20' - name: npm install run: npm install - name: build website release @@ -48,7 +45,10 @@ jobs: run: npm run build-apps - name: deploy to cesium.com if: ${{ env.AWS_ACCESS_KEY_ID != '' }} - run: npm run deploy-s3 -- -b "cesium.com-next" -c 'public, max-age=1800'--skip --confirm - - name: set status - if: ${{ env.AWS_ACCESS_KEY_ID != '' }} - run: npm run deploy-status -- --status success --message Deployed \ No newline at end of file + 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" + aws s3 sync Build/release/ s3://cesium-website/cesiumjs/releases/$(cat package.json | jq -r '.version' | sed 's/\.0$//')/ --cache-control "public, max-age=1800" --delete + aws s3 sync Build/Documentation/ s3://cesium-website/cesiumjs/ref-doc/ --cache-control "public, max-age=1800" --delete + aws s3 sync Build/CesiumViewer/ s3://cesium-website/cesiumjs/cesium-viewer/ --cache-control "public, max-age=1800" --delete + aws s3 sync Build/Sandcastle/ s3://cesium-sandcastle-website/ --cache-control "public, max-age=1800" --delete diff --git a/.slackbot.yml b/.slackbot.yml index f1b54f977c8d..ca0d87fc8160 100644 --- a/.slackbot.yml +++ b/.slackbot.yml @@ -4,5 +4,11 @@ releaseSchedule: - ggetz, 3/1/2023 - jjhembd, 4/3/2023 - ggetz, 5/1/2023 - -jjhembd, 6/1/2023 + - jjhembd, 6/1/2023 - ggetz, 7/1/2023 + - jjhembd, 8/1/2023 + - ggetz, 9/1/2023 + - jjhembd, 10/1/2023 + - ggetz, 11/1/2023 + - jjhembd, 12/1/2023 + - ggetz, 1/2/2024 diff --git a/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.html b/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.html new file mode 100644 index 000000000000..f49f14fa1df8 --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.html @@ -0,0 +1,422 @@ + + + + + + + + + + 3D Tiles 1.1 Photogrammetry + + + + + + +
+
+
+
+
+ 3D Tiles 1.0 (JPEG textures) +
+
+ Tiles Loaded: --- / + --- +
+ GPU Memory: --- MB +
+ +
+ Tile Load Time (s): --- +
+
+
+ +
+
+

Loading...

+
+
+
+ Maximum Screen Space Error + + +
+
+ + + diff --git a/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.jpg b/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.jpg new file mode 100644 index 000000000000..de98b367803d Binary files /dev/null and b/Apps/Sandcastle/gallery/3D Tiles 1.1 Photogrammetry.jpg differ diff --git a/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html b/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html index 0ac29a48c476..f57e5a705972 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html +++ b/Apps/Sandcastle/gallery/3D Tiles Next S2 Globe.html @@ -79,9 +79,9 @@ // MAXAR OWT WFF 1.2 Base Globe tileset = await Cesium.Cesium3DTileset.fromIonAssetId(691510, { maximumScreenSpaceError: 4, + enableCameraCollision: true, }); scene.primitives.add(tileset); - scene.enableCollisionDetectionForTileset(tileset); } catch (error) { console.log(`Error loading tileset: ${error}`); } diff --git a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html index fba90865bd20..84f5cd4e59c3 100644 --- a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html +++ b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html @@ -47,7 +47,6 @@ try { const googleTileset = await Cesium.createGooglePhotorealistic3DTileset(); viewer.scene.primitives.add(googleTileset); - viewer.scene.enableCollisionDetectionForTileset(googleTileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. ${error}`); diff --git a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html index 13e7fdd55c5b..63e67341a908 100644 --- a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html @@ -47,7 +47,6 @@ try { const tileset = await Cesium.createGooglePhotorealistic3DTileset(); viewer.scene.primitives.add(tileset); - viewer.scene.enableCollisionDetectionForTileset(tileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. ${error}`); diff --git a/Apps/Sandcastle/gallery/3D Tiles Picking.html b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html similarity index 58% rename from Apps/Sandcastle/gallery/3D Tiles Picking.html rename to Apps/Sandcastle/gallery/development/3D Tiles Picking.html index a5e09173953b..843b1d4f30e4 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Picking.html +++ b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html @@ -39,42 +39,74 @@ timeline: false, animation: false, baseLayerPicker: false, - // The globe does not need to be displayed, - // since the Photorealistic 3D Tiles include terrain globe: false, }); - - // Enable rendering the sky - viewer.scene.skyAtmosphere.show = true; + const scene = viewer.scene; let tileset; - // Add Photorealistic 3D Tiles - try { - tileset = await Cesium.createGooglePhotorealistic3DTileset( - undefined, - { - enableDebugWireframe: true, - } - ); - viewer.scene.primitives.add(tileset); - } catch (error) { - console.log(`Error loading Photorealistic 3D Tiles tileset. - ${error}`); - } + const options = [ + { + text: "Google P3DT", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.createGooglePhotorealistic3DTileset(); + scene.primitives.add(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Maxar OWT WFF 1.2", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(691510, { + maximumScreenSpaceError: 4, + }); + scene.primitives.add(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Bentley BIM Model", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(1240402); + scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Instanced", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json" + ); + scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(error); + } + }, + }, + ]; - viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin); - const inspectorViewModel = viewer.cesium3DTilesInspector.viewModel; - inspectorViewModel.tileset = tileset; - - const scene = viewer.scene; + Sandcastle.addDefaultToolbarMenu(options); const scratchCartesian = new Cesium.Cartesian3(); - const scratchTo2D = new Cesium.Matrix4(); const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); handler.setInputAction(function (movement) { - // const feature = scene.pick(movement.position); const pickedPositionResult = scene.pickPosition(movement.position); - console.log(pickedPositionResult); if (Cesium.defined(pickedPositionResult)) { viewer.entities.add({ position: pickedPositionResult, @@ -87,14 +119,8 @@ } const ray = scene.camera.getPickRay(movement.position); - const picked = tileset.pick( - ray, - scene.frameState, - true, - scratchCartesian - ); + const picked = tileset.pick(ray, scene.frameState, scratchCartesian); - console.log(picked); if (Cesium.defined(picked)) { viewer.entities.add({ position: picked, @@ -105,7 +131,6 @@ }, }); } - // } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); //Sandcastle_End }; if (typeof Cesium !== "undefined") { diff --git a/CHANGES.md b/CHANGES.md index 60212357f17d..d09272f3b9e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,28 @@ # Change Log +### 1.113 - 2024-01-02 + +#### @cesium/engine + +##### Additions :tada: + +- Added `Cesium3DTileset.getHeight` to sample height values of the loaded tiles. If using WebGL 1, the `enablePick` option must be set to true to use this function. [#11581](https://github.com/CesiumGS/cesium/pull/11581) +- Added `Cesium3DTileset.enableCameraCollision` to prevent the camera from going below a 3D tileset. Tilesets created with `createGooglePhotorealistic3DTileset` have this option enabled by default. [#11581](https://github.com/CesiumGS/cesium/pull/11581) + +##### Fixes :wrench: + +- Changes the default `RequestScheduler.maximumRequestsPerServer` from 6 to 18. This should improve performance on HTTP/2 servers and above [#11627](https://github.com/CesiumGS/cesium/issues/11627) +- Corrected JSDoc and Typescript definitions that marked optional arguments as required in `ImageryProvider` constructor [#11625](https://github.com/CesiumGS/cesium/issues/11625) + +### 1.112 - 2023-12-01 + +#### @cesium/engine + +##### Fixes :wrench: + +- Fixed terrain lockups in `requestTileGeometry` by ensuring promise handling aligns with CesiumJS's expectations. [#11630](https://github.com/CesiumGS/cesium/pull/11630) +- Corrected JSDoc and Typescript definitions that marked optional arguments as required in `Cesium3dTileset.fromIonAssetId` [#11623](https://github.com/CesiumGS/cesium/issues/11623), and `IonImageryProvider.fromAssetId` [#11624](https://github.com/CesiumGS/cesium/issues/11624) + ### 1.111 - 2023-11-01 #### @cesium/engine @@ -10,6 +33,9 @@ ##### Fixes :wrench: +- By default, `createGooglePhotorealistic3DTileset` no longer shows credits on screen but links to them instead, as this is compliant with the minimum required attribution. To restore this behavior, pass the option `showCreditsOnScreen: true`. [#11589](https://github.com/CesiumGS/cesium/pull/11589) +- Fixed an issue with polygon hole rendering. [#11583](https://github.com/CesiumGS/cesium/issues/11583) +- Fixed error with rhumb lines that have a 0 degree heading. [#11573](https://github.com/CesiumGS/cesium/pull/11573) - Fixed `czm_normal`, `czm_normal3D`, `czm_inverseNormal`, and `czm_inverseNormal3D` for cases where the model matrix has non-uniform scale. [#11553](https://github.com/CesiumGS/cesium/pull/11553) - Fixed issue with clustered labels when `dataSource.show` was toggled. [#11560](https://github.com/CesiumGS/cesium/pull/11560) - Fixed inconsistant clustering when `dataSource.show` was toggled. [#11560](https://github.com/CesiumGS/cesium/pull/11560) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82d6d09c6eae..944352e97f9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,8 +70,7 @@ Our code is our lifeblood so maintaining CesiumJS's high code quality is importa - If this is your first contribution to CesiumJS, add your name to [CONTRIBUTORS.md](https://github.com/CesiumGS/cesium/blob/main/CONTRIBUTORS.md). - For an overview of our workflow see [github pull request workflows](https://cesium.com/blog/2013/10/08/github-pull-request-workflows/). - Pull request tips - - After you open a pull request, the friendly [cesium-concierge](https://github.com/CesiumGS/cesium-concierge) bot will comment with a short automated review. At least one human will also review your pull request. - - If your pull request fixes an existing issue, include a link to the issue in the description (like this: [#1](https://github.com/CesiumGS/cesium/issues/1)). Likewise, if your pull request fixes an issue reported on the Cesium forum, include a link to the thread. + - If your pull request fixes an existing issue, include a link to the issue in the description (like this: "Fixes [#1](https://github.com/CesiumGS/cesium/issues/1)"). Likewise, if your pull request fixes an issue reported on the Cesium forum, include a link to the thread. - If your pull request needs additional work, include a [task list](https://github.com/blog/1375%0A-task-lists-in-gfm-issues-pulls-comments). - Once you are done making new commits to address feedback, add a comment to the pull request such as `"this is ready"` since GitHub doesn't notify us about commits. - Code and tests diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 20b960874675..af7f4d2e4c0f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -160,6 +160,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Daniel Krupka](https://github.com/krupkad) - [Jeshurun Hembd](https://github.com/jjhembd) - [Mia Tang](https://github.com/miatang13) + - [Mark Dane](https://github.com/angrycat9000) + - [jjspace](https://github.com/jjspace) - [Northrop Grumman](http://www.northropgrumman.com) - [Joseph Stein](https://github.com/nahgrin) - [EOX IT Services GmbH](https://eox.at) @@ -184,6 +186,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Robert Irving](https://github.com/robert-irving-snc) - [General Atomics CCRi](https://www.ga-ccri.com/) - [matthias-ccri](https://github.com/matthias-ccri) +- [Terradepth, Inc.](https://www.terradepth.com/) + - [Marc Johnson](https://github.com/marcejohnson) + - [Jacob Frazer](https://github.com/coderjake91) ## [Individual CLA](Documentation/Contributors/CLAs/individual-contributor-license-agreement-v1.0.pdf) diff --git a/Documentation/Contributors/BuildGuide/README.md b/Documentation/Contributors/BuildGuide/README.md index 72518b29a1a2..df4fa912da65 100644 --- a/Documentation/Contributors/BuildGuide/README.md +++ b/Documentation/Contributors/BuildGuide/README.md @@ -148,7 +148,6 @@ Here's the full set of scripts and what they do. - `test-webgl-validation` - Runs all tests with Karma and enables low-level WebGL validation - `test-release` - Runs all tests on the minified release version of built Cesium - **Deployment scripts** - - `deploy-s3` - Deploys the built CesiumJS files, the npm package, and the zip file to Amazon S3. This requires having credentials set up for the S3 bucket to which you are deploying - `deploy-status` - Sets the deployment statuses in GitHub, for use in CI - `deploy-set-version` - Sets the version of `package.json`, for use in CI @@ -170,26 +169,25 @@ Additional set up is required for deployment if you do not have commit access to ### Configure a Different S3 Bucket -It is possible to configure your development branch of CesiumJS to deploy build artifacts to a different [AWS S3 Bucket](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html). If you are using the cesium-dev bucket and have valid credentials, skip to [Configure S3 Credentials](#configure-s3-credentials) +It is possible to configure your development branch of CesiumJS to deploy build artifacts to a different [AWS S3 Bucket](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html). If you are using the cesium-public-builds bucket and have valid credentials, skip to [Configure S3 Credentials](#configure-s3-credentials) -- In `.gtihub/workflows/dev.yml`, in the following lines, replace "cesium-dev" with the name of your S3 bucket. +- In `.gtihub/workflows/dev.yml`, in the following lines, replace "cesium-public-builds" with the name of your S3 bucket. ```yml -aws s3 sync ./Build/Coverage s3://cesium-dev/cesium/$BRANCH/Build/Coverage --delete --color on +aws s3 sync ./Build/Coverage s3://cesium-public-builds/cesium/$BRANCH/Build/Coverage --delete --color on ``` ```yml -npm run deploy-s3 -- -b "cesium-dev" -d cesium/$BRANCH -c 'no-cache' --confirm +aws s3 sync Build/unzipped/ s3://cesium-public-builds/cesium/$BRANCH/Build/ --cache-control "no-cache" --delete ``` - In `gulpfile.js`, edit the following line: ```javascript -const devDeployUrl = - "http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/"; +const devDeployUrl = "https://ci-builds.cesium.com/cesium/"; ``` -- Edit the URL to match the URL of the S3 bucket specified in the previous step. +- Edit the URL to match the URL hosting the S3 bucket specified in the previous step. ### Configure S3 Credentials diff --git a/Documentation/Contributors/CodeReviewGuide/README.md b/Documentation/Contributors/CodeReviewGuide/README.md index 1bf35f1d62c0..bd5af3af8435 100644 --- a/Documentation/Contributors/CodeReviewGuide/README.md +++ b/Documentation/Contributors/CodeReviewGuide/README.md @@ -16,7 +16,7 @@ This guide describes best practices for code reviewers. - It is ultimately the responsibility of the pull request opener to get their changes merged. They should champion their code being merged and should bump the PR or `@mention` a specific developer if it is not getting the necessary attention. - GitHub has great [tools for code reviews in pull requests](https://help.github.com/articles/using-pull-requests/#reviewing-proposed-changes) that you should become familiar with. -- We need a CLA for any contribution. If we don't have a CLA for the contributor who opened the pull request (or, more precisely, any contributor to the branch), the Cesium Concierge will ask for one. If you receive no updates, politely ask for one before reviewing the pull request ([example](https://github.com/CesiumGS/cesium/pull/2918#issuecomment-127805425)). +- We need a CLA for any contribution. If we don't have a CLA for the contributor who opened the pull request (or, more precisely, any contributor to the branch), we will ask for one. If you receive no updates, politely ask for one before reviewing the pull request ([example](https://github.com/CesiumGS/cesium/pull/2918#issuecomment-127805425)). - Most pull requests require additional work, minor or major, before being merged. Sometime pull requests are submitted incomplete for early feedback. Include a [task list](https://github.com/blog/1375%0A-task-lists-in-gfm-issues-pulls-comments) covering the steps that must be completed before merging. - Anyone is encouraged to review any pull request that interests them. However, someone familiar with the changed code should ultimately merge it. - It's OK to provide a few comments without taking responsibility for the final merge, for example commenting on the state of the public API or a Sandcastle example. However, be explicit that you will not be reviewing again. This sometimes happens when a reviewer wants to take a quick look at the public API or code examples but not all the implementation details. diff --git a/Documentation/Contributors/CodingGuide/README.md b/Documentation/Contributors/CodingGuide/README.md index 409213b3a0c1..99ade2899769 100644 --- a/Documentation/Contributors/CodingGuide/README.md +++ b/Documentation/Contributors/CodingGuide/README.md @@ -119,7 +119,7 @@ A few more naming conventions are introduced below along with their design patte ## Linting -For syntax and style guidelines, we use the ESLint recommended settings (the list of rules can be found [here](http://eslint.org/docs/rules/)) as a base and extend it with additional rules via a shared config Node module, [eslint-config-cesium](https://www.npmjs.com/package/eslint-config-cesium). This package is maintained as a part of the Cesium repository and is also used throughout the Cesium ecosystem. For a list of which rules are enabled, look in [index.js](https://github.com/CesiumGS/cesium/blob/main/Tools/eslint-config-cesium/index.js), [browser.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/browser.js), and [node.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/node.js). +For syntax and style guidelines, we use the ESLint recommended settings (the list of rules can be found [here](http://eslint.org/docs/rules/)) as a base and extend it with additional rules via a shared config Node module, [eslint-config-cesium](https://www.npmjs.com/package/eslint-config-cesium). This package is maintained as a part of the Cesium repository and is also used throughout the Cesium ecosystem. For an up to date list of which rules are enabled, look in [index.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/index.js), [browser.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/browser.js), and [node.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/node.js). Below are listed some specific rules to keep in mind **General rules:** @@ -136,14 +136,11 @@ For syntax and style guidelines, we use the ESLint recommended settings (the lis - [no-trailing-spaces](http://eslint.org/docs/rules/no-trailing-spaces) - [no-lonely-if](http://eslint.org/docs/rules/no-lonely-if) - [quotes](http://eslint.org/docs/rules/quotes) to enforce using single-quotes -- [no-sequences](http://eslint.org/docs/rules/no-sequences) -- [no-unused-expressions](http://eslint.org/docs/rules/no-unused-expressions) **Node-specific rules:** - [global-require](http://eslint.org/docs/rules/global-require) -- [no-buffer-constructor](http://eslint.org/docs/rules/no-buffer-constructor) -- [no-new-require](http://eslint.org/docs/rules/no-new-require) +- [n/no-new-require](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-new-require.md) **[Disabling Rules with Inline Comments](http://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)** @@ -169,21 +166,13 @@ try { ## Units - Cesium uses SI units: - - meters for distances, - - radians for angles, and - - seconds for time durations. + - meters for distances + - radians for angles + - seconds for time durations - If a function has a parameter with a non-standard unit, such as degrees, put the unit in the function name, e.g., ```javascript -Cartesian3.fromDegrees = function ( - longitude, - latitude, - height, - ellipsoid, - result -) { - /* ... */ -}; +Cartesian3.fromDegrees(); // Not Cartesin3.fromAngle() ``` ## Basic Code Construction @@ -256,7 +245,7 @@ console.log(i); // i is undefined here. Never use a variable before it is decla let i = 0.0; ``` -- A `const` variables is preferred when a value is not updated. This ensures immutability. +- A `const` variable is preferred when a value is not updated. This ensures immutability. - :speedboat: Avoid redundant nested property access. This @@ -294,31 +283,26 @@ function radiiEquals(left, right) { ``` - Use `undefined` instead of `null`. -- Test if a variable is defined using Cesium's `defined` function, e.g., +- Test if a variable is defined using Cesium's `defined` function. It checks specifically for `undefined` and `null` values allowing _falsy_ values to be defined, e.g., ```javascript -const v = undefined; -if (defined(v)) { - // False -} +defined(undefined); // False +defined(null); // False -const u = {}; -if (defined(u)) { - // True -} +defined({}); // True +defined(""); // True +defined(0); // True ``` - Use `Object.freeze` function to create enums, e.g., ```javascript +const ModelAnimationState = { + STOPPED: 0, + ANIMATING: 1, +}; - const ModelAnimationState = { - STOPPED : 0, - ANIMATING : 1 - }; - - return Object.freeze(ModelAnimationState); -}); +return Object.freeze(ModelAnimationState); ``` - Use descriptive comments for non-obvious code, e.g., @@ -491,7 +475,7 @@ Some common sensible defaults are ### Throwing Exceptions -Use the functions of Cesium's [Check](https://github.com/CesiumGS/cesium/blob/main/Source/Core/Check.js) class to throw a `DeveloperError` when the user has a coding error. The most common errors are parameters that are missing, have the wrong type or are out of rangers of the wrong type or are out of range. +Use the functions of Cesium's [Check](https://github.com/CesiumGS/cesium/blob/main/Source/Core/Check.js) class to throw a `DeveloperError` when the user has a coding error. The most common errors are parameters that are missing, have the wrong type or are out of range. - For example, to check that a parameter is defined and is an object: @@ -681,7 +665,7 @@ Functions that start with `to` return a new type of object, e.g., ```javascript Cartesian3.prototype.toString = function () { - return "(${this.x}, ${this.y}, ${this.z})"; + return `(${this.x}, ${this.y}, ${this.z})`; }; ``` diff --git a/Documentation/Contributors/DocumentationGuide/README.md b/Documentation/Contributors/DocumentationGuide/README.md index d5b9946158a9..caac7aa57d4d 100644 --- a/Documentation/Contributors/DocumentationGuide/README.md +++ b/Documentation/Contributors/DocumentationGuide/README.md @@ -30,7 +30,7 @@ Generally, just follow the patterns that are already in comparable parts of the ## Building the Doc -The reference doc is written in JavaScript code comments using [JSDoc3](http://usejsdoc.org/index.html) tags. At the command line, build the doc from the root CesiumJS directory by running the following: +The reference doc is written in JavaScript code comments using [JSDoc3](https://jsdoc.app/) tags. At the command line, build the doc from the root CesiumJS directory by running the following: ```bash npm run build-docs @@ -50,7 +50,7 @@ npm start ## Basics -Consider one of the simplest functions in CesiumJS, `defined`: +Consider one of the simplest functions in CesiumJS, [`defined`](../../../packages/engine/Source/Core/defined.js): ```javascript /** @@ -67,7 +67,7 @@ Consider one of the simplest functions in CesiumJS, `defined`: * } */ function defined(value) { - return value !== undefined; + return value !== undefined && value !== null; } ``` @@ -80,7 +80,7 @@ The above reference doc is built into the following: ![Documentation for defined](defined.jpg) -This guide describes best practices for writing doc. For complete details on JSDoc tags, see their [documentation](http://usejsdoc.org/index.html). +This guide describes best practices for writing doc. For complete details on JSDoc tags, see their [documentation](https://jsdoc.app/). ## Parameters @@ -116,13 +116,14 @@ As a complete example, * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created. * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided. */ -Matrix4.fromRotationTranslation = function(rotation, translation, result) { +Matrix4.fromRotationTranslation = function (rotation, translation, result) { // .. ``` generates ![Function Documentation](fromRotationTranslation.jpg) + The CesiumJS classes in the `Type` column are links to their doc. ## `options` Parameters diff --git a/Documentation/Contributors/PerformanceTestingGuide/README.md b/Documentation/Contributors/PerformanceTestingGuide/README.md index ff4a98e9f111..adfc888ad445 100644 --- a/Documentation/Contributors/PerformanceTestingGuide/README.md +++ b/Documentation/Contributors/PerformanceTestingGuide/README.md @@ -78,7 +78,7 @@ detail will be loaded. For performance testing, we want a reasonably detailed scene without hitting out-of-memory problems or the test taking a long time. Decrease -`maximumScreenSpaceError` to as needed. For example: +`maximumScreenSpaceError` as needed. For example: ```js tileset.maximumScreenSpaceError = 4; @@ -86,7 +86,7 @@ tileset.maximumScreenSpaceError = 4; ### How to measure elapsed time -In the browser, measuring elapsed time is quite simple using `performance.now()`: +In the browser, measuring elapsed time is quite simple using [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now): ```js const start = performance.now(); diff --git a/Specs/Data/CesiumTerrainTileJson/11_3027_1342.terrain b/Specs/Data/CesiumTerrainTileJson/11_3027_1342.terrain new file mode 100644 index 000000000000..ca4dabcf67ff Binary files /dev/null and b/Specs/Data/CesiumTerrainTileJson/11_3027_1342.terrain differ diff --git a/Specs/test.cjs b/Specs/test.cjs index 11388433e704..c2d24da84ba8 100644 --- a/Specs/test.cjs +++ b/Specs/test.cjs @@ -5,15 +5,13 @@ const assert = require("node:assert"); const { - CesiumTerrainProvider, Cartographic, + createWorldTerrainAsync, sampleTerrain, } = require("cesium"); async function test() { - const provider = await CesiumTerrainProvider.fromUrl( - "https://s3.amazonaws.com/cesiumjs/smallTerrain" - ); + const provider = await createWorldTerrainAsync(); const results = await sampleTerrain(provider, 11, [ Cartographic.fromDegrees(86.925145, 27.988257), Cartographic.fromDegrees(87.0, 28.0), diff --git a/Specs/test.mjs b/Specs/test.mjs index 13ba4c1162ed..37bd1260575b 100644 --- a/Specs/test.mjs +++ b/Specs/test.mjs @@ -1,12 +1,10 @@ -import { Cartographic, CesiumTerrainProvider, sampleTerrain } from "cesium"; +import { Cartographic, createWorldTerrainAsync, sampleTerrain } from "cesium"; import assert from "node:assert"; // NodeJS smoke screen test async function test() { - const provider = await CesiumTerrainProvider.fromUrl( - "https://s3.amazonaws.com/cesiumjs/smallTerrain" - ); + const provider = await createWorldTerrainAsync(); const results = await sampleTerrain(provider, 11, [ Cartographic.fromDegrees(86.925145, 27.988257), Cartographic.fromDegrees(87.0, 28.0), diff --git a/ThirdParty.json b/ThirdParty.json index 21ee7959ef66..5e1b954747a5 100644 --- a/ThirdParty.json +++ b/ThirdParty.json @@ -133,7 +133,7 @@ "license": [ "MIT" ], - "version": "0.19.0", + "version": "0.20.0", "url": "https://www.npmjs.com/package/meshoptimizer" }, { diff --git a/gulpfile.js b/gulpfile.js index bdd55f9d1a66..3d22144b2bbb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,11 +1,8 @@ /*eslint-env node*/ import { writeFileSync, copyFileSync, readFileSync, existsSync } from "fs"; import { readFile, writeFile } from "fs/promises"; -import { join, basename, resolve, posix, dirname } from "path"; +import { join, basename, resolve, dirname } from "path"; import { exec, execSync } from "child_process"; -import { createHash } from "crypto"; -import { gzipSync } from "zlib"; -import { createInterface } from "readline"; import fetch from "node-fetch"; import { createRequire } from "module"; @@ -22,21 +19,9 @@ import mergeStream from "merge-stream"; import streamToPromise from "stream-to-promise"; import karma from "karma"; import yargs from "yargs"; -import { - S3Client, - DeleteObjectsCommand, - HeadObjectCommand, - ListObjectsCommand, - PutObjectCommand, -} from "@aws-sdk/client-s3"; -import { Upload } from "@aws-sdk/lib-storage"; -import mime from "mime"; import typeScript from "typescript"; import { build as esbuild } from "esbuild"; import { createInstrumenter } from "istanbul-lib-instrument"; -import pLimit from "p-limit"; -import download from "download"; -import decompress from "decompress"; import { buildCesium, @@ -61,8 +46,7 @@ if (/\.0$/.test(version)) { } const karmaConfigFile = resolve("./Specs/karma.conf.cjs"); -const devDeployUrl = - "http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/"; +const devDeployUrl = "https://ci-builds.cesium.com/cesium/"; const isProduction = process.env.PROD; //Gulp doesn't seem to have a way to get the currently running tasks for setting @@ -443,6 +427,8 @@ function combineForSandcastle() { } export const websiteRelease = gulp.series( + buildEngine, + buildWidgets, function () { return buildCesium({ development: false, @@ -517,7 +503,6 @@ async function pruneScriptsForZip(packageJsonPath) { delete scripts.prettier; // Remove deploy tasks - delete scripts["deploy-s3"]; delete scripts["deploy-status"]; delete scripts["deploy-set-version"]; delete scripts["website-release"]; @@ -689,529 +674,6 @@ export const makeZip = gulp.series(release, async function () { ); }); -export async function deployS3() { - const argv = yargs(process.argv) - .usage("Usage: deploy-s3 -b [Bucket Name] -d [Upload Directory]") - .options({ - bucket: { - alias: "b", - description: "Bucket name.", - type: "string", - demandOption: true, - }, - directory: { - alias: "d", - description: "Upload directory.", - type: "string", - }, - "cache-control": { - alias: "c", - description: - "The cache control option set on the objects uploaded to S3.", - type: "string", - default: "max-age=3600", - }, - "dry-run": { - description: "Only print file paths and S3 keys.", - type: "boolean", - default: false, - }, - skip: { - description: "Skip re-uploading exisiting files.", - type: "boolean", - default: false, - }, - confirm: { - description: "Skip confirmation step, useful for CI.", - type: "boolean", - default: false, - }, - }).argv; - - const uploadDirectory = argv.directory; - const bucketName = argv.bucket; - const dryRun = argv.dryRun; - const cacheControl = argv.cacheControl ? argv.cacheControl : "max-age=3600"; - const skipFiles = argv.skip; - - if (argv.confirm) { - return deployCesium( - bucketName, - uploadDirectory, - cacheControl, - skipFiles, - dryRun - ); - } - - const iface = createInterface({ - input: process.stdin, - output: process.stdout, - }); - - return new Promise((resolve) => { - // prompt for confirmation - iface.question( - `Files from your computer will be published to the ${bucketName} bucket. Continue? [y/n] `, - function (answer) { - iface.close(); - if (answer === "y") { - resolve( - deployCesium( - bucketName, - uploadDirectory, - cacheControl, - skipFiles, - dryRun - ) - ); - } else { - console.log("Deploy aborted by user."); - resolve(); - } - } - ); - }); -} - -// Deploy cesium to s3 -async function deployCesium( - bucketName, - uploadDirectory, - cacheControl, - skipFiles, - dryRun -) { - // Limit promise concurrency since we are reading many - // files off disk in parallel - const limit = pLimit(2000); - - const refDocPrefix = "cesiumjs/ref-doc/"; - const sandcastlePrefix = "sandcastle/"; - const cesiumViewerPrefix = "cesiumjs/cesium-viewer/"; - - const s3Client = new S3Client({ - region: "us-east-1", - maxRetries: 10, - retryDelayOptions: { - base: 500, - }, - }); - - const existingBlobs = []; - let totalFiles = 0; - let uploaded = 0; - let skipped = 0; - const errors = []; - - if (!isProduction) { - await listAll(s3Client, bucketName, `${uploadDirectory}/`, existingBlobs); - } - - async function getContents(file, blobName) { - const mimeLookup = getMimeType(blobName); - const contentType = mimeLookup.type; - const compress = mimeLookup.compress; - const contentEncoding = compress ? "gzip" : undefined; - - totalFiles++; - - let content = await readFile(file); - - if (compress) { - const alreadyCompressed = content[0] === 0x1f && content[1] === 0x8b; - if (alreadyCompressed) { - if (verbose) { - console.log(`Skipping compressing already compressed file: ${file}`); - } - } else { - content = gzipSync(content); - } - } - - const computeEtag = (content) => { - return createHash("md5").update(content).digest("base64"); - }; - - const index = existingBlobs.indexOf(blobName); - if (index <= -1) { - return { - content, - etag: computeEtag(content), - contentType, - contentEncoding, - }; - } - - // remove files from the list to clean later - // as we find them on disk - existingBlobs.splice(index, 1); - - // get file info - const headObjectCommand = new HeadObjectCommand({ - Bucket: bucketName, - Key: blobName, - }); - const data = await s3Client.send(headObjectCommand); - const hash = createHash("md5").update(content).digest("hex"); - - if ( - !skipFiles || - data.ETag !== `"${hash}"` || - data.CacheControl !== cacheControl || - data.ContentType !== contentType || - data.ContentEncoding !== contentEncoding - ) { - return { - content, - etag: computeEtag(content), - contentType, - contentEncoding, - }; - } - - // We don't need to upload this file again - skipped++; - } - - async function readAndUpload(prefix, existingPrefix, file) { - const blobName = `${prefix}${file.replace(existingPrefix, "")}`; - - let fileContents; - try { - fileContents = await getContents(file, blobName); - } catch (e) { - errors.push(e); - } - - if (!fileContents) { - return; - } - - const content = fileContents.content; - const etag = fileContents.etag; - const contentType = fileContents.contentType; - const contentEncoding = fileContents.contentEncoding; - - if (verbose) { - console.log(`Uploading ${blobName}...`); - } - - const params = { - Bucket: bucketName, - Key: blobName, - Body: content, - ContentMD5: etag, - ContentType: contentType, - ContentEncoding: contentEncoding, - CacheControl: cacheControl, - }; - - const putObjectCommand = new PutObjectCommand(params); - - if (dryRun) { - uploaded++; - return; - } - - try { - await s3Client.send(putObjectCommand); - uploaded++; - } catch (e) { - errors.push(e); - } - } - - let uploads; - if (isProduction) { - const uploadSandcastle = async () => { - const files = await globby(["Build/Sandcastle/**"]); - return Promise.all( - files.map((file) => { - return limit(() => - readAndUpload(sandcastlePrefix, "Build/Sandcastle/", file) - ); - }) - ); - }; - - const uploadRefDoc = async () => { - const files = await globby(["Build/Documentation/**"]); - return Promise.all( - files.map((file) => { - return limit(() => - readAndUpload(refDocPrefix, "Build/Documentation/", file) - ); - }) - ); - }; - - const uploadCesiumViewer = async () => { - const files = await globby(["Build/CesiumViewer/**"]); - return Promise.all( - files.map((file) => { - return limit(() => - readAndUpload(cesiumViewerPrefix, "Build/CesiumViewer/", file) - ); - }) - ); - }; - - uploads = [ - uploadSandcastle(), - uploadRefDoc(), - uploadCesiumViewer(), - deployCesiumRelease(bucketName, s3Client, errors), - ]; - } else { - const files = await globby( - [ - "Apps/**", - "Build/**", - "!Build/CesiumDev/**", - "packages/**", - "Source/**", - "Specs/**", - "ThirdParty/**", - "*.md", - "favicon.ico", - "gulpfile.js", - "index.html", - "package.json", - "server.js", - "web.config", - "*.zip", - "*.tgz", - ], - { - dot: true, // include hidden files - } - ); - - uploads = files.map((file) => { - return limit(() => readAndUpload(`${uploadDirectory}/`, "", file)); - }); - } - - await Promise.all(uploads); - - console.log( - `Skipped ${skipped} files and successfully uploaded ${uploaded} files of ${ - totalFiles - skipped - } files.` - ); - - if (!isProduction && existingBlobs.length >= 0) { - const objectsToDelete = []; - existingBlobs.forEach(function (file) { - // Don't delete generated zip files - if (!/\.(zip|tgz)$/.test(file)) { - objectsToDelete.push({ Key: file }); - } - }); - - if (objectsToDelete.length > 0) { - console.log(`Cleaning ${objectsToDelete.length} files...`); - - // If more than 1000 files, we must issue multiple requests - const batches = []; - while (objectsToDelete.length > 1000) { - batches.push(objectsToDelete.splice(0, 1000)); - } - batches.push(objectsToDelete); - - const deleteObjects = async (objects) => { - const deleteObjectsCommand = new DeleteObjectsCommand({ - Bucket: bucketName, - Delete: { - Objects: objects, - }, - }); - - try { - if (!dryRun) { - await s3Client.send(deleteObjectsCommand); - } - } catch (e) { - errors.push(e); - } - - if (verbose) { - console.log(`Cleaned ${objects.length} files.`); - } - }; - - await Promise.all(batches.map(deleteObjects)); - } - } - - if (errors.length === 0) { - return; - } - - console.log("Errors: "); - errors.map(console.log); - return Promise.reject("There was an error while deploying Cesium"); -} - -async function deployCesiumRelease(bucketName, s3Client, errors) { - const releaseDir = "cesiumjs/releases"; - const quiet = process.env.CI; - - let release; - try { - // Deploy any new releases - const response = await fetch( - "https://api.github.com/repos/CesiumGS/cesium/releases/latest", - { - method: "GET", - headers: { - Authorization: process.env.GITHUB_TOKEN - ? `token ${process.env.GITHUB_TOKEN}` - : undefined, - "User-Agent": "cesium.com-build", - }, - } - ); - - const body = await response.json(); - - release = { - tag: body.tag_name, - name: body.name, - url: body.assets[0].browser_download_url, - }; - - const headObjectCommand = new HeadObjectCommand({ - Bucket: bucketName, - Key: posix.join(releaseDir, release.tag, "cesium.zip"), - }); - await s3Client.send(headObjectCommand); - console.log( - `Cesium version ${release.tag} up to date. Skipping release deployment.` - ); - } catch (error) { - if (error.$metadata) { - const { httpStatusCode } = error.$metadata; - // The current version is not uploaded - if (httpStatusCode === 404) { - console.log("Updating cesium version..."); - const data = await download(release.url); - // upload and unzip contents - const key = posix.join(releaseDir, release.tag, "cesium.zip"); - await uploadObject(bucketName, s3Client, key, data, quiet); - const files = await decompress(data); - const limit = pLimit(5); - return Promise.all( - files.map((file) => { - return limit(async () => { - if (file.path.startsWith("Apps")) { - // skip uploading apps and sandcastle - return; - } - - // Upload to release directory - const key = posix.join(releaseDir, release.tag, file.path); - return uploadObject(bucketName, s3Client, key, file.data, quiet); - }); - }) - ); - } - } - - // else, unexpected error - errors.push(error); - } -} - -async function uploadObject(bucketName, s3Client, key, contents, quiet) { - if (!quiet) { - console.log(`Uploading ${key}...`); - } - - const upload = new Upload({ - client: s3Client, - params: { - Bucket: bucketName, - Key: key, - Body: contents, - ContentType: mime.getType(key) || undefined, - CacheControl: "public, max-age=1800", - }, - }); - return upload.done(); -} - -function getMimeType(filename) { - //Remove page anchors from documentation, as mime does not properly handle them - filename = filename.split("#")[0]; - - const mimeType = mime.getType(filename); - if (mimeType) { - //Compress everything except zipfiles, binary images, and video - let compress = !/^(image\/|video\/|application\/zip|application\/gzip)/i.test( - mimeType - ); - if (mimeType === "image/svg+xml") { - compress = true; - } - return { type: mimeType, compress: compress }; - } - - //Non-standard mime types not handled by mime - if (/\.(glsl|LICENSE|config|state)$/i.test(filename)) { - return { type: "text/plain", compress: true }; - } else if (/\.(czml|topojson)$/i.test(filename)) { - return { type: "application/json", compress: true }; - } else if (/\.tgz$/i.test(filename)) { - return { type: "application/octet-stream", compress: false }; - } - - // Handle dotfiles, such as .jshintrc - const baseName = basename(filename); - if (baseName[0] === "." || baseName.indexOf(".") === -1) { - return { type: "text/plain", compress: true }; - } - - // Everything else can be octet-stream compressed but print a warning - // if we introduce a type we aren't specifically handling. - if (!/\.(terrain|b3dm|geom|pnts|vctr|cmpt|i3dm|metadata)$/i.test(filename)) { - console.log(`Unknown mime type for ${filename}`); - } - - return { type: "application/octet-stream", compress: true }; -} - -// get all files currently in bucket asynchronously -async function listAll(s3Client, bucketName, prefix, files, marker) { - const listObjectsCommand = new ListObjectsCommand({ - Bucket: bucketName, - MaxKeys: 1000, - Prefix: prefix, - Marker: marker, - }); - const data = await s3Client.send(listObjectsCommand); - const items = data.Contents; - if (!items) { - return; - } - - for (let i = 0; i < items.length; i++) { - files.push(items[i].Key); - } - - if (data.IsTruncated) { - // get next page of results - return listAll( - s3Client, - bucketName, - prefix, - files, - files[files.length - 1] - ); - } -} - export async function deploySetVersion() { const buildVersion = argv.buildVersion; if (buildVersion) { diff --git a/package.json b/package.json index 70c7e2e9df1c..fd9639e2e426 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cesium", - "version": "1.110.1", + "version": "1.112.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", @@ -50,26 +50,22 @@ "./Specs/**/*" ], "dependencies": { - "@cesium/engine": "^6.0.0", - "@cesium/widgets": "^4.2.0" + "@cesium/engine": "^6.1.1", + "@cesium/widgets": "^4.3.1" }, "devDependencies": { - "@aws-sdk/client-s3": "^3.342.0", - "@aws-sdk/lib-storage": "^3.342.0", - "@playwright/test": "^1.34.3", + "@playwright/test": "^1.40.1", "chokidar": "^3.5.3", "cloc": "^2.8.0", "compression": "^1.7.4", - "decompress": "^4.2.1", - "download": "^8.0.0", - "esbuild": "^0.19.2", - "eslint": "^8.41.0", + "esbuild": "^0.19.8", + "eslint": "^8.54.0", "eslint-config-cesium": "^10.0.1", "eslint-plugin-es": "^4.1.0", "eslint-plugin-html": "^7.1.0", "eslint-plugin-node": "^11.1.0", "express": "^4.17.1", - "globby": "^13.1.3", + "globby": "^14.0.0", "glsl-strip-comments": "^1.0.0", "gulp": "^4.0.2", "gulp-clean-css": "^4.3.0", @@ -77,7 +73,7 @@ "gulp-rename": "^2.0.0", "gulp-replace": "^1.1.3", "gulp-tap": "^2.0.0", - "gulp-zip": "^5.1.0", + "gulp-zip": "^6.0.0", "husky": "^8.0.2", "istanbul-lib-instrument": "^6.0.0", "jasmine-core": "^5.0.1", @@ -96,19 +92,17 @@ "karma-spec-reporter": "^0.0.36", "markdownlint-cli": "^0.37.0", "merge-stream": "^2.0.0", - "mime": "^3.0.0", "mkdirp": "^3.0.1", "node-fetch": "^3.2.10", "open": "^9.1.0", - "p-limit": "^4.0.0", "prettier": "2.1.2", "prismjs": "^1.28.0", "request": "^2.79.0", "rimraf": "^5.0.0", - "sinon": "^16.0.0", + "sinon": "^17.0.0", "stream-to-promise": "^3.0.0", "tsd-jsdoc": "^2.5.0", - "typescript": "^5.0.2", + "typescript": "^5.3.2", "yargs": "^17.0.1" }, "scripts": { @@ -144,7 +138,6 @@ "test-e2e-release-all": "release=true playwright test -c Specs/e2e/playwright.config.js", "test-e2e-report": "playwright show-report Build/Specs/e2e/report", "test-e2e-update": "playwright test -c Specs/e2e/playwright.config.js --project=chromium -u", - "deploy-s3": "gulp deployS3", "deploy-status": "gulp deployStatus", "deploy-set-version": "gulp deploySetVersion", "prettier": "prettier --write --no-config \"**/*\"", @@ -167,4 +160,4 @@ "packages/engine", "packages/widgets" ] -} \ No newline at end of file +} diff --git a/packages/engine/Source/Core/CesiumTerrainProvider.js b/packages/engine/Source/Core/CesiumTerrainProvider.js index 84d43629725a..c70769c02fcf 100644 --- a/packages/engine/Source/Core/CesiumTerrainProvider.js +++ b/packages/engine/Source/Core/CesiumTerrainProvider.js @@ -897,11 +897,18 @@ CesiumTerrainProvider.prototype.requestTileGeometry = function ( if (!defined(layerToUse) && unknownAvailability) { // Try again when availability data is ready– Otherwise the tile will be marked as failed and never re-requested - return availabilityPromise.then(() => - this.requestTileGeometry(x, y, level, request) - ); + return availabilityPromise.then(() => { + // handle promise or undefined return + return new Promise((resolve) => { + // defer execution to the next event loop + setTimeout(() => { + const promise = this.requestTileGeometry(x, y, level, request); + resolve(promise); + }, 0); // next tick + }); + }); } - + // call overridden function below return requestTileGeometry(this, x, y, level, layerToUse, request); }; diff --git a/packages/engine/Source/Core/EllipsoidRhumbLine.js b/packages/engine/Source/Core/EllipsoidRhumbLine.js index 047d48e87961..51b410622677 100644 --- a/packages/engine/Source/Core/EllipsoidRhumbLine.js +++ b/packages/engine/Source/Core/EllipsoidRhumbLine.js @@ -333,10 +333,16 @@ function interpolateUsingSurfaceDistance( latitude = calculateInverseM(M2, ellipticity, major); //Now find the longitude of the second point - const sigma1 = calculateSigma(ellipticity, start.latitude); - const sigma2 = calculateSigma(ellipticity, latitude); - deltaLongitude = Math.tan(heading) * (sigma2 - sigma1); - longitude = CesiumMath.negativePiToPi(start.longitude + deltaLongitude); + + // Check to see if the rhumb line has constant longitude + if (Math.abs(heading) < CesiumMath.EPSILON10) { + longitude = CesiumMath.negativePiToPi(start.longitude); + } else { + const sigma1 = calculateSigma(ellipticity, start.latitude); + const sigma2 = calculateSigma(ellipticity, latitude); + deltaLongitude = Math.tan(heading) * (sigma2 - sigma1); + longitude = CesiumMath.negativePiToPi(start.longitude + deltaLongitude); + } } else { //If heading is close to 90 degrees latitude = start.latitude; diff --git a/packages/engine/Source/Core/Ion.js b/packages/engine/Source/Core/Ion.js index 08ed35fa1709..3f7c2a48d1ef 100644 --- a/packages/engine/Source/Core/Ion.js +++ b/packages/engine/Source/Core/Ion.js @@ -4,7 +4,7 @@ import Resource from "./Resource.js"; let defaultTokenCredit; const defaultAccessToken = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhYjVkZWE2Yi05ZjlmLTQyOTAtYjFkOC0xMmFmZTYyMjhmZGIiLCJpZCI6MjU5LCJpYXQiOjE2OTYyNjgyMzN9.iKIgzd7PueqxXsUNSIhk5C6n8uiugH-Dxy9uhOdJ5Zg"; + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzN2UwMjViMC0xOTJhLTQxMmItOWEzMS1kMjJiNmZiY2E5N2YiLCJpZCI6MjU5LCJpYXQiOjE3MDE0NTc1NjB9.bMNhc7S4cuG1qUnRUiLxr5lunSH1k6-UfkRXSqVFYyM"; /** * Default settings for accessing the Cesium ion API. * diff --git a/packages/engine/Source/Core/PolygonGeometry.js b/packages/engine/Source/Core/PolygonGeometry.js index dee6abd84979..1e334f66b30b 100644 --- a/packages/engine/Source/Core/PolygonGeometry.js +++ b/packages/engine/Source/Core/PolygonGeometry.js @@ -636,7 +636,7 @@ function createGeometryFromPositionsExtruded( } let outerRing = hierarchy.outerRing; - let tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid); + const tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid); let positions2D = tangentPlane.projectPointsOntoPlane( outerRing, createGeometryFromPositionsExtrudedPositions @@ -664,8 +664,6 @@ function createGeometryFromPositionsExtruded( const holes = hierarchy.holes; for (i = 0; i < holes.length; i++) { let hole = holes[i]; - - tangentPlane = EllipsoidTangentPlane.fromPoints(hole, ellipsoid); positions2D = tangentPlane.projectPointsOntoPlane( hole, createGeometryFromPositionsExtrudedPositions @@ -1331,7 +1329,7 @@ function getTangentPlane(rectangle, positions, ellipsoid) { } const scratchCartographicCyllindrical = new Cartographic(); -function createProjectTo2d(rectangle, ellipsoid) { +function createProjectTo2d(rectangle, outerPositions, ellipsoid) { return (positions, results) => { // If the polygon positions span a large enough extent, use a specialized projection if (rectangle.height >= CesiumMath.PI || rectangle.width >= CesiumMath.PI) { @@ -1360,7 +1358,10 @@ function createProjectTo2d(rectangle, ellipsoid) { } // Use a local tangent plane for smaller extents - const tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); + const tangentPlane = EllipsoidTangentPlane.fromPoints( + outerPositions, + ellipsoid + ); return tangentPlane.projectPointsOntoPlane(positions, results); }; } @@ -1466,7 +1467,7 @@ PolygonGeometry.createGeometry = function (polygonGeometry) { const results = PolygonGeometryLibrary.polygonsFromHierarchy( polygonHierarchy, hasTextureCoordinates, - createProjectTo2d(rectangle, ellipsoid), + createProjectTo2d(rectangle, outerPositions, ellipsoid), !perPositionHeight, ellipsoid, createSplitPolygons(rectangle, ellipsoid, arcType, perPositionHeight) diff --git a/packages/engine/Source/Core/RequestScheduler.js b/packages/engine/Source/Core/RequestScheduler.js index c5bde44ceb37..2caded8f67b9 100644 --- a/packages/engine/Source/Core/RequestScheduler.js +++ b/packages/engine/Source/Core/RequestScheduler.js @@ -60,9 +60,9 @@ RequestScheduler.maximumRequests = 50; * The maximum number of simultaneous active requests per server. Un-throttled requests or servers specifically * listed in {@link requestsByServer} do not observe this limit. * @type {number} - * @default 6 + * @default 18 */ -RequestScheduler.maximumRequestsPerServer = 6; +RequestScheduler.maximumRequestsPerServer = 18; /** * A per server key list of overrides to use for throttling instead of maximumRequestsPerServer. @@ -78,13 +78,7 @@ RequestScheduler.maximumRequestsPerServer = 6; * "assets.cesium.com:443": 18, * }; */ -RequestScheduler.requestsByServer = { - "api.cesium.com:443": 18, - "assets.ion.cesium.com:443": 18, - "ibasemaps-api.arcgis.com:443": 18, - "tile.googleapis.com:443": 18, - "tile.openstreetmap.org:443": 18, -}; +RequestScheduler.requestsByServer = {}; /** * Specifies if the request scheduler should throttle incoming requests, or let the browser queue requests under its control. diff --git a/packages/engine/Source/Scene/ArcGisMapService.js b/packages/engine/Source/Scene/ArcGisMapService.js index 6cd673976ac2..1920062edfe5 100644 --- a/packages/engine/Source/Scene/ArcGisMapService.js +++ b/packages/engine/Source/Scene/ArcGisMapService.js @@ -4,7 +4,7 @@ import Resource from "../Core/Resource.js"; let defaultTokenCredit; const defaultAccessToken = - "AAPK4db83df15a06415e9b7d45d826eec992u0REAmuLmykhOFOk_4e61HgsO_e3C_qGUgiWNGElpjpiMfO9_Qg5ZRyy8POvB5EF"; + "AAPK2b93071721df4cc78be0d8b3d79b1fd54YMocOcx2NxlbYTDkyO5gPk8XsDnguQgeMdFKepFwLwTgb8vHfPvSTdjy_KlMHlS"; /** * Default options for accessing the ArcGIS image tile service. * diff --git a/packages/engine/Source/Scene/Cesium3DTileContent.js b/packages/engine/Source/Scene/Cesium3DTileContent.js index 76dcdd255b47..945242881b42 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContent.js +++ b/packages/engine/Source/Scene/Cesium3DTileContent.js @@ -355,18 +355,12 @@ Cesium3DTileContent.prototype.update = function (tileset, frameState) { * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Cesium3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Cesium3DTileContent.prototype.pick = function (ray, frameState, result) { DeveloperError.throwInstantiationError(); }; diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 50fcf0be44ed..a682d4c93c86 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -110,11 +110,13 @@ import Ray from "../Core/Ray.js"; * @property {string|number} [instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @property {boolean} [showCreditsOnScreen=false] Whether to display the credits of this tileset on screen. * @property {SplitDirection} [splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this tileset. - * @property {boolean} [projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has loaded. + * @property {boolean} [enableCameraCollision=false] When {@link ScreenSpaceCameraController#enableCollisionDetection} is true, prevents the camera from going below the tileset surface. + * @property {boolean} [projectTo2D=false] Whether to accurately project the tileset to 2D. If this is true, the tileset will be projected accurately to 2D, but it will use more memory to do so. If this is false, the tileset will use less memory and will still render in 2D / CV mode, but its projected positions may be inaccurate. This cannot be set after the tileset has been created. + * @property {boolean} [enablePick=false] Whether to allow with CPU picking with pick when not using WebGL 2 or above. If using WebGL 2 or above, this option will be ignored. If using WebGL 1 and this is true, the pick operation will work correctly, but it will use more memory to do so. If running with WebGL 1 and this is false, the model will use less memory, but pick will always return undefined. This cannot be set after the tileset has loaded. * @property {string} [debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. * @property {boolean} [debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @property {boolean} [debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. - * @property {boolean} [enableDebugWireframe] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has loaded. + * @property {boolean} [enableDebugWireframe=false] For debugging only. This must be true for debugWireframe to work in WebGL1. This cannot be set after the tileset has been created. * @property {boolean} [debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. * @property {boolean} [debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. * @property {boolean} [debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. @@ -151,6 +153,18 @@ import Ray from "../Core/Ray.js"; * } * * @example + * // Keep camera from going under 3D tileset + * try { + * const tileset = await Cesium.Cesium3DTileset.fromUrl( + * "http://localhost:8002/tilesets/Seattle/tileset.json", + * { enableCameraCollision: true } + * ); + * scene.primitives.add(tileset); + * } catch (error) { + * console.error(`Error creating tileset: ${error}`); + * } + * + * @example * // Common setting for the skipLevelOfDetail optimization * const tileset = await Cesium.Cesium3DTileset.fromUrl( * "http://localhost:8002/tilesets/Seattle/tileset.json", { @@ -827,7 +841,23 @@ function Cesium3DTileset(options) { SplitDirection.NONE ); + /** + * When {@link ScreenSpaceCameraController#enableCollisionDetection} is true, prevents the camera from going below the tileset surface. + * If using WebGL 1, {@link Cesium3DTileset#ConstructorOptions} enablePick must be true for this behavior to work. + * + * @type {boolean} + * @default false + */ + this.enableCameraCollision = defaultValue( + options.enableCameraCollision, + false + ); + this._projectTo2D = defaultValue(options.projectTo2D, false); + this._enablePick = defaultValue( + options.enablePick, + this.enableCameraCollision + ); /** * This property is for debugging only; it is not optimized for production use. @@ -1907,7 +1937,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { * used for streaming massive heterogeneous 3D geospatial datasets, from a Cesium ion asset ID. * * @param {number} assetId The Cesium ion asset id. - * @param {Cesium3DTileset.ConstructorOptions} options An object describing initialization options + * @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options * @returns {Promise} * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. @@ -3439,11 +3469,17 @@ const scratchIntersection = new Cartesian3(); const scratchGetHeightCartographic = new Cartographic(); /** - * Get the height of the loaded surface at a given cartographic. + * Get the height of the loaded surface at a given cartographic. This function will only take into account meshes for loaded tiles, not neccisarily the most detailed tiles available for a tileset. This function will always return undefined when sampling a point cloud. * * @param {Cartographic} cartographic The cartographic for which to find the height. * @param {Scene} scene The scene where visualization is taking place. * @returns {number|undefined} The height of the cartographic or undefined if it could not be found. + * + * @example + * const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(124624234); + * scene.primitives.add(tileset); + * + * const height = tileset.getHeight(scene.camera.positionCartographic, scene); */ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { //>>includeStart('debug', pragmas.debug); @@ -3457,18 +3493,21 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { } const ray = scratchGetHeightRay; - ray.direction = ellipsoid.cartographicToCartesian( + const position = ellipsoid.cartographicToCartesian( cartographic, ray.direction ); Cartesian3.normalize(ray.direction, ray.direction); - const intersection = this.pick( - ray, - scene.frameState, - false, - scratchIntersection + ray.direction = Cartesian3.normalize(position, ray.direction); + ray.direction = Cartesian3.negate(position, ray.direction); + ray.origin = Cartesian3.multiplyByScalar( + ray.direction, + -2 * ellipsoid.maximumRadius, + ray.origin ); + + const intersection = this.pick(ray, scene.frameState, scratchIntersection); if (!defined(intersection)) { return; } @@ -3527,24 +3566,19 @@ Cesium3DTileset.prototype.updateHeight = function ( }; const scratchSphereIntersection = new Interval(); +const scratchPickIntersection = new Cartesian3(); /** * Find an intersection between a ray and the tileset surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Cesium3DTileset.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Cesium3DTileset.prototype.pick = function (ray, frameState, result) { const selectedTiles = this._selectedTiles; const selectedLength = selectedTiles.length; @@ -3561,7 +3595,11 @@ Cesium3DTileset.prototype.pick = function ( continue; } - const candidate = tile.content.pick(ray, frameState, cullBackFaces, result); + const candidate = tile.content.pick( + ray, + frameState, + scratchPickIntersection + ); if (!defined(candidate)) { continue; @@ -3569,7 +3607,7 @@ Cesium3DTileset.prototype.pick = function ( const distance = Cartesian3.distance(ray.origin, candidate); if (distance < minDistance) { - intersection = candidate; + intersection = Cartesian3.clone(candidate, result); minDistance = distance; } } @@ -3578,8 +3616,7 @@ Cesium3DTileset.prototype.pick = function ( return undefined; } - Cartesian3.clone(intersection, result); - return result; + return intersection; }; /** diff --git a/packages/engine/Source/Scene/Composite3DTileContent.js b/packages/engine/Source/Scene/Composite3DTileContent.js index 900a901cc75b..02be6c08ac6c 100644 --- a/packages/engine/Source/Scene/Composite3DTileContent.js +++ b/packages/engine/Source/Scene/Composite3DTileContent.js @@ -1,3 +1,4 @@ +import Cartesian3 from "../Core/Cartesian3.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; @@ -347,6 +348,47 @@ Composite3DTileContent.prototype.update = function (tileset, frameState) { } }; +/** + * Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates. + * + * @param {Ray} ray The ray to test for intersection. + * @param {FrameState} frameState The frame state. + * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. + * @returns {Cartesian3|undefined} The intersection or undefined if none was found. + * + * @private + */ +Composite3DTileContent.prototype.pick = function (ray, frameState, result) { + if (!this._ready) { + return undefined; + } + + let intersection; + let minDistance = Number.POSITIVE_INFINITY; + const contents = this._contents; + const length = contents.length; + + for (let i = 0; i < length; ++i) { + const candidate = contents[i].pick(ray, frameState, result); + + if (!defined(candidate)) { + continue; + } + + const distance = Cartesian3.distance(ray.origin, candidate); + if (distance < minDistance) { + intersection = candidate; + minDistance = distance; + } + } + + if (!defined(intersection)) { + return undefined; + } + + return result; +}; + Composite3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/Empty3DTileContent.js b/packages/engine/Source/Scene/Empty3DTileContent.js index 2f4f16f57ce3..0b5c4459da35 100644 --- a/packages/engine/Source/Scene/Empty3DTileContent.js +++ b/packages/engine/Source/Scene/Empty3DTileContent.js @@ -150,7 +150,7 @@ Empty3DTileContent.prototype.applyStyle = function (style) {}; Empty3DTileContent.prototype.update = function (tileset, frameState) {}; -Empty3DTileContent.prototype.pick = function (ray, frameState) { +Empty3DTileContent.prototype.pick = function (ray, frameState, result) { return undefined; }; diff --git a/packages/engine/Source/Scene/Geometry3DTileContent.js b/packages/engine/Source/Scene/Geometry3DTileContent.js index 93007bd46a1f..d89a278eb4b1 100644 --- a/packages/engine/Source/Scene/Geometry3DTileContent.js +++ b/packages/engine/Source/Scene/Geometry3DTileContent.js @@ -525,6 +525,10 @@ Geometry3DTileContent.prototype.update = function (tileset, frameState) { } }; +Geometry3DTileContent.prototype.pick = function (ray, frameState, result) { + return undefined; +}; + Geometry3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 42077c0234d9..68989c3fe5c7 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -179,6 +179,7 @@ const GltfLoaderState = { * @param {Axis} [options.forwardAxis=Axis.Z] The forward-axis of the glTF model. * @param {boolean} [options.loadAttributesAsTypedArray=false] Load all attributes and indices as typed arrays instead of GPU buffers. If the attributes are interleaved in the glTF they will be de-interleaved in the typed array. * @param {boolean} [options.loadAttributesFor2D=false] If true, load the positions buffer and any instanced attribute buffers as typed arrays for accurately projecting models to 2D. + * @param {boolean} [options.enablePick=false] If true, load the positions buffer, any instanced attribute buffers, and index buffer as typed arrays for CPU-enabled picking in WebGL1. * @param {boolean} [options.loadIndicesForWireframe=false] If true, load the index buffer as both a buffer and typed array. The latter is useful for creating wireframe indices in WebGL1. * @param {boolean} [options.loadPrimitiveOutline=true] If true, load outlines from the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set false to avoid post-processing geometry at load time. * @param {boolean} [options.loadForClassification=false] If true and if the model has feature IDs, load the feature IDs and indices as typed arrays. This is useful for batching features for classification. @@ -203,6 +204,7 @@ function GltfLoader(options) { false ); const loadAttributesFor2D = defaultValue(options.loadAttributesFor2D, false); + const enablePick = defaultValue(options.enablePick); const loadIndicesForWireframe = defaultValue( options.loadIndicesForWireframe, false @@ -234,6 +236,7 @@ function GltfLoader(options) { this._forwardAxis = forwardAxis; this._loadAttributesAsTypedArray = loadAttributesAsTypedArray; this._loadAttributesFor2D = loadAttributesFor2D; + this._enablePick = enablePick; this._loadIndicesForWireframe = loadIndicesForWireframe; this._loadPrimitiveOutline = loadPrimitiveOutline; this._loadForClassification = loadForClassification; @@ -1241,6 +1244,8 @@ function loadVertexAttribute( !hasInstances && loader._loadAttributesFor2D && !frameState.scene3DOnly; + const loadTypedArrayForPicking = + isPositionAttribute && loader._enablePick && !frameState.context.webgl2; const loadTypedArrayForClassification = loader._loadForClassification && isFeatureIdAttribute; @@ -1252,6 +1257,7 @@ function loadVertexAttribute( const outputTypedArray = outputTypedArrayOnly || loadTypedArrayFor2D || + loadTypedArrayForPicking || loadTypedArrayForClassification; // Determine what to load right now: @@ -1319,6 +1325,8 @@ function loadInstancedAttribute( loader._loadAttributesAsTypedArray || (hasRotation && isTransformAttribute) || !frameState.context.instancedArrays; + const loadTypedArrayForPicking = + loader._enablePick && !frameState.context.webgl2; const loadBuffer = !loadAsTypedArrayOnly; @@ -1328,7 +1336,8 @@ function loadInstancedAttribute( // - the model will be projected to 2D. const loadFor2D = loader._loadAttributesFor2D && !frameState.scene3DOnly; const loadTranslationAsTypedArray = - isTranslationAttribute && (!hasTranslationMinMax || loadFor2D); + isTranslationAttribute && + (!hasTranslationMinMax || loadFor2D || loadTypedArrayForPicking); const loadTypedArray = loadAsTypedArrayOnly || loadTranslationAsTypedArray; @@ -1365,9 +1374,10 @@ function loadIndices( indices.count = accessor.count; const loadAttributesAsTypedArray = loader._loadAttributesAsTypedArray; - // Load the index buffer as a typed array to generate wireframes in WebGL1. - const loadForWireframe = - loader._loadIndicesForWireframe && !frameState.context.webgl2; + // Load the index buffer as a typed array to generate wireframes or pick in WebGL1. + const loadForCpuOperations = + (loader._loadIndicesForWireframe || loader._enablePick) && + !frameState.context.webgl2; // Load the index buffer as a typed array to batch features together for classification. const loadForClassification = loader._loadForClassification && hasFeatureIds; @@ -1377,7 +1387,7 @@ function loadIndices( const outputTypedArrayOnly = loadAttributesAsTypedArray; const outputBuffer = !outputTypedArrayOnly; const outputTypedArray = - loadAttributesAsTypedArray || loadForWireframe || loadForClassification; + loadAttributesAsTypedArray || loadForCpuOperations || loadForClassification; // Determine what to load right now: // diff --git a/packages/engine/Source/Scene/ImageryLayer.js b/packages/engine/Source/Scene/ImageryLayer.js index a038ef799997..8e1ca78ffba9 100644 --- a/packages/engine/Source/Scene/ImageryLayer.js +++ b/packages/engine/Source/Scene/ImageryLayer.js @@ -132,8 +132,8 @@ import TileImagery from "./TileImagery.js"; * @alias ImageryLayer * @constructor * - * @param {ImageryProvider} imageryProvider The imagery provider to use. - * @param {ImageryLayer.ConstructorOptions} options An object describing initialization options + * @param {ImageryProvider} [imageryProvider] The imagery provider to use. + * @param {ImageryLayer.ConstructorOptions} [options] An object describing initialization options * * @see ImageryLayer.fromProviderAsync * @see ImageryLayer.fromWorldImagery diff --git a/packages/engine/Source/Scene/Implicit3DTileContent.js b/packages/engine/Source/Scene/Implicit3DTileContent.js index 96af9c099ad5..96eebae51c1d 100644 --- a/packages/engine/Source/Scene/Implicit3DTileContent.js +++ b/packages/engine/Source/Scene/Implicit3DTileContent.js @@ -1177,6 +1177,10 @@ Implicit3DTileContent.prototype.applyStyle = function (style) {}; Implicit3DTileContent.prototype.update = function (tileset, frameState) {}; +Implicit3DTileContent.prototype.pick = function (ray, frameState, result) { + return undefined; +}; + Implicit3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/IonImageryProvider.js b/packages/engine/Source/Scene/IonImageryProvider.js index 17ef1d580094..5b1361bb734e 100644 --- a/packages/engine/Source/Scene/IonImageryProvider.js +++ b/packages/engine/Source/Scene/IonImageryProvider.js @@ -251,7 +251,7 @@ Object.defineProperties(IonImageryProvider.prototype, { * Creates a provider for tiled imagery using the Cesium ion REST API. * * @param {Number} assetId An ion imagery asset ID. - * @param {IonImageryProvider.ConstructorOptions} options Object describing initialization options. + * @param {IonImageryProvider.ConstructorOptions} [options] Object describing initialization options. * @returns {Promise} A promise which resolves to the created IonImageryProvider. * * @example diff --git a/packages/engine/Source/Scene/MapboxImageryProvider.js b/packages/engine/Source/Scene/MapboxImageryProvider.js index b8e5d069231d..2c5a943bd52a 100644 --- a/packages/engine/Source/Scene/MapboxImageryProvider.js +++ b/packages/engine/Source/Scene/MapboxImageryProvider.js @@ -43,8 +43,8 @@ const defaultCredit = new Credit( * accessToken: 'thisIsMyAccessToken' * }); * - * @see {@link https://www.mapbox.com/developers/api/maps/#tiles} - * @see {@link https://www.mapbox.com/developers/api/#access-tokens} + * @see {@link https://docs.mapbox.com/api/maps/raster-tiles/} + * @see {@link https://docs.mapbox.com/api/accounts/tokens/} */ function MapboxImageryProvider(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); diff --git a/packages/engine/Source/Scene/Model/B3dmLoader.js b/packages/engine/Source/Scene/Model/B3dmLoader.js index 7385f18a690d..600221ac4dde 100644 --- a/packages/engine/Source/Scene/Model/B3dmLoader.js +++ b/packages/engine/Source/Scene/Model/B3dmLoader.js @@ -50,6 +50,7 @@ const FeatureIdAttribute = ModelComponents.FeatureIdAttribute; * @param {Axis} [options.forwardAxis=Axis.X] The forward-axis of the glTF model. * @param {boolean} [options.loadAttributesAsTypedArray=false] If true, load all attributes as typed arrays instead of GPU buffers. If the attributes are interleaved in the glTF they will be de-interleaved in the typed array. * @param {boolean} [options.loadAttributesFor2D=false] If true, load the positions buffer and any instanced attribute buffers as typed arrays for accurately projecting models to 2D. + * @param {boolean} [options.enablePick=false] If true, load the positions buffer, any instanced attribute buffers, and index buffer as typed arrays for CPU-enabled picking in WebGL1. * @param {boolean} [options.loadIndicesForWireframe=false] If true, load the index buffer as a typed array. This is useful for creating wireframe indices in WebGL1. * @param {boolean} [options.loadPrimitiveOutline=true] If true, load outlines from the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set false to avoid post-processing geometry at load time. * @param {boolean} [options.loadForClassification=false] If true and if the model has feature IDs, load the feature IDs and indices as typed arrays. This is useful for batching features for classification. @@ -74,6 +75,7 @@ function B3dmLoader(options) { false ); const loadAttributesFor2D = defaultValue(options.loadAttributesFor2D, false); + const enablePick = defaultValue(options.enablePick, false); const loadIndicesForWireframe = defaultValue( options.loadIndicesForWireframe, false @@ -102,6 +104,7 @@ function B3dmLoader(options) { this._forwardAxis = forwardAxis; this._loadAttributesAsTypedArray = loadAttributesAsTypedArray; this._loadAttributesFor2D = loadAttributesFor2D; + this._enablePick = enablePick; this._loadIndicesForWireframe = loadIndicesForWireframe; this._loadPrimitiveOutline = loadPrimitiveOutline; this._loadForClassification = loadForClassification; @@ -223,6 +226,7 @@ B3dmLoader.prototype.load = function () { incrementallyLoadTextures: this._incrementallyLoadTextures, loadAttributesAsTypedArray: this._loadAttributesAsTypedArray, loadAttributesFor2D: this._loadAttributesFor2D, + enablePick: this._enablePick, loadIndicesForWireframe: this._loadIndicesForWireframe, loadPrimitiveOutline: this._loadPrimitiveOutline, loadForClassification: this._loadForClassification, diff --git a/packages/engine/Source/Scene/Model/I3dmLoader.js b/packages/engine/Source/Scene/Model/I3dmLoader.js index 2a1aff969bd2..0f9457624419 100644 --- a/packages/engine/Source/Scene/Model/I3dmLoader.js +++ b/packages/engine/Source/Scene/Model/I3dmLoader.js @@ -64,6 +64,7 @@ const Instances = ModelComponents.Instances; * @param {Axis} [options.upAxis=Axis.Y] The up-axis of the glTF model. * @param {Axis} [options.forwardAxis=Axis.X] The forward-axis of the glTF model. * @param {boolean} [options.loadAttributesAsTypedArray=false] Load all attributes as typed arrays instead of GPU buffers. If the attributes are interleaved in the glTF they will be de-interleaved in the typed array. + * @param {boolean} [options.enablePick=false] If true, load the positions buffer, any instanced attribute buffers, and index buffer as typed arrays for CPU-enabled picking in WebGL1. * @param {boolean} [options.loadIndicesForWireframe=false] Load the index buffer as a typed array so wireframe indices can be created for WebGL1. * @param {boolean} [options.loadPrimitiveOutline=true] If true, load outlines from the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set false to avoid post-processing geometry at load time. */ @@ -91,6 +92,7 @@ function I3dmLoader(options) { false ); const loadPrimitiveOutline = defaultValue(options.loadPrimitiveOutline, true); + const enablePick = defaultValue(options.enablePick, false); //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.i3dmResource", i3dmResource); @@ -111,6 +113,7 @@ function I3dmLoader(options) { this._loadAttributesAsTypedArray = loadAttributesAsTypedArray; this._loadIndicesForWireframe = loadIndicesForWireframe; this._loadPrimitiveOutline = loadPrimitiveOutline; + this._enablePick = enablePick; this._state = I3dmLoaderState.NOT_LOADED; this._promise = undefined; @@ -240,6 +243,7 @@ I3dmLoader.prototype.load = function () { releaseGltfJson: this._releaseGltfJson, incrementallyLoadTextures: this._incrementallyLoadTextures, loadAttributesAsTypedArray: this._loadAttributesAsTypedArray, + enablePick: this._enablePick, loadIndicesForWireframe: this._loadIndicesForWireframe, loadPrimitiveOutline: this._loadPrimitiveOutline, }; diff --git a/packages/engine/Source/Scene/Model/InstancingPipelineStage.js b/packages/engine/Source/Scene/Model/InstancingPipelineStage.js index 850410c2fd2b..809a1dd06d32 100644 --- a/packages/engine/Source/Scene/Model/InstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/InstancingPipelineStage.js @@ -73,6 +73,7 @@ InstancingPipelineStage.process = function (renderResources, node, frameState) { frameState.mode !== SceneMode.SCENE3D && !frameState.scene3DOnly && model._projectTo2D; + const keepTypedArray = model._enablePick && !frameState.context.webgl2; const instancingVertexAttributes = []; @@ -81,7 +82,8 @@ InstancingPipelineStage.process = function (renderResources, node, frameState) { frameState, instances, instancingVertexAttributes, - use2D + use2D, + keepTypedArray ); processFeatureIdAttributes( @@ -697,7 +699,8 @@ function processTransformAttributes( frameState, instances, instancingVertexAttributes, - use2D + use2D, + keepTypedArray ) { const rotationAttribute = ModelUtility.getAttributeBySemantic( instances, @@ -711,7 +714,8 @@ function processTransformAttributes( instances, instancingVertexAttributes, frameState, - use2D + use2D, + keepTypedArray ); } else { processTransformVec3Attributes( @@ -729,7 +733,8 @@ function processTransformMatrixAttributes( instances, instancingVertexAttributes, frameState, - use2D + use2D, + keepTypedArray ) { const shaderBuilder = renderResources.shaderBuilder; const count = instances.attributes[0].count; @@ -755,6 +760,10 @@ function processTransformMatrixAttributes( buffer = createVertexBuffer(transformsTypedArray, frameState); model._modelResources.push(buffer); + if (keepTypedArray) { + runtimeNode.transformsTypedArray = transformsTypedArray; + } + runtimeNode.instancingTransformsBuffer = buffer; } @@ -812,7 +821,8 @@ function processTransformVec3Attributes( instances, instancingVertexAttributes, frameState, - use2D + use2D, + keepTypedArray ) { const shaderBuilder = renderResources.shaderBuilder; const runtimeNode = renderResources.runtimeNode; @@ -872,7 +882,7 @@ function processTransformVec3Attributes( attributeString ); - if (!use2D) { + if (!use2D && !keepTypedArray) { return; } @@ -898,6 +908,10 @@ function processTransformVec3Attributes( ); const projectedTypedArray = translationsToTypedArray(projectedTranslations); + if (keepTypedArray) { + runtimeNode.transformsTypedArray = projectedTypedArray; + } + // This memory is counted during the statistics stage at the end // of the pipeline. buffer2D = createVertexBuffer(projectedTypedArray, frameState); @@ -906,6 +920,10 @@ function processTransformVec3Attributes( runtimeNode.instancingTranslationBuffer2D = buffer2D; } + if (!use2D) { + return; + } + const byteOffset = 0; const byteStride = undefined; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 26be311fc8b9..389c7aa9d8ad 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -1,23 +1,17 @@ -import AttributeType from "../AttributeType.js"; -import AttributeCompression from "../../Core/AttributeCompression.js"; import BoundingSphere from "../../Core/BoundingSphere.js"; import Cartesian3 from "../../Core/Cartesian3.js"; import Cartographic from "../../Core/Cartographic.js"; import Check from "../../Core/Check.js"; import Credit from "../../Core/Credit.js"; import Color from "../../Core/Color.js"; -import ComponentDatatype from "../../Core/ComponentDatatype.js"; import defined from "../../Core/defined.js"; import defaultValue from "../../Core/defaultValue.js"; import DeveloperError from "../../Core/DeveloperError.js"; import destroyObject from "../../Core/destroyObject.js"; import DistanceDisplayCondition from "../../Core/DistanceDisplayCondition.js"; import Event from "../../Core/Event.js"; -import IndexDatatype from "../../Core/IndexDatatype.js"; -import IntersectionTests from "../../Core/IntersectionTests.js"; import Matrix3 from "../../Core/Matrix3.js"; import Matrix4 from "../../Core/Matrix4.js"; -import Ray from "../../Core/Ray.js"; import Resource from "../../Core/Resource.js"; import RuntimeError from "../../Core/RuntimeError.js"; import Pass from "../../Renderer/Pass.js"; @@ -45,8 +39,7 @@ import ModelUtility from "./ModelUtility.js"; import oneTimeWarning from "../../Core/oneTimeWarning.js"; import PntsLoader from "./PntsLoader.js"; import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; -import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; -import Transforms from "../../Core/Transforms.js"; +import pickModel from "./pickModel.js"; /** *
@@ -160,6 +153,7 @@ import Transforms from "../../Core/Transforms.js"; * @privateParam {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen. * @privateParam {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this model. * @privateParam {boolean} [options.projectTo2D=false] Whether to accurately project the model's positions in 2D. If this is true, the model will be projected accurately to 2D, but it will use more memory to do so. If this is false, the model will use less memory and will still render in 2D / CV mode, but its positions may be inaccurate. This disables minimumPixelSize and prevents future modification to the model matrix. This also cannot be set after the model has loaded. + * @privateParam {boolean} [options.enablePick=false] Whether to allow with CPU picking with pick when not using WebGL 2 or above. If using WebGL 2 or above, this option will be ignored. If using WebGL 1 and this is true, the pick operation will work correctly, but it will use more memory to do so. If running with WebGL 1 and this is false, the model will use less memory, but pick will always return undefined. This cannot be set after the model has loaded. * @privateParam {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @privateParam {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @privateParam {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. @@ -463,6 +457,7 @@ function Model(options) { this._sceneMode = undefined; this._projectTo2D = defaultValue(options.projectTo2D, false); + this._enablePick = defaultValue(options.enablePick, false); this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); @@ -2487,201 +2482,18 @@ Model.prototype.isClippingEnabled = function () { ); }; -const scratchV0 = new Cartesian3(); -const scratchV1 = new Cartesian3(); -const scratchV2 = new Cartesian3(); -const scratchModelMatrix = new Matrix4(); -const scratchPickCartographic = new Cartographic(); - /** * Find an intersection between a ray and the model surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Model.prototype.pick = function (ray, frameState, cullBackFaces, result) { - if (frameState.mode === SceneMode.MORPHING) { - return; - } - - let minT = Number.MAX_VALUE; - const modelMatrix = this.sceneGraph.computedModelMatrix; - - // Check all the primitive positions - const nodes = this._sceneGraph._runtimeNodes; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - const instances = node.node.instances; - if (defined(instances)) { - // TODO: Instances - return; - } - - const nodeComputedTransform = node.computedTransform; - let computedModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - nodeComputedTransform, - scratchModelMatrix - ); - if (frameState.mode !== SceneMode.SCENE3D) { - computedModelMatrix = Transforms.basisTo2D( - frameState.mapProjection, - computedModelMatrix, - computedModelMatrix - ); - } - - for (let j = 0; j < node.node.primitives.length; j++) { - const primitive = node.node.primitives[j]; - const positionAttribute = ModelUtility.getAttributeBySemantic( - primitive, - VertexAttributeSemantic.POSITION - ); - const vertexCount = positionAttribute.count; - - let indices = primitive.indices.typedArray; - if (!defined(indices)) { - const indicesBuffer = primitive.indices.buffer; - const indicesCount = primitive.indices.count; - if (defined(indicesBuffer) && frameState.context.webgl2) { - const useUint8Array = indicesBuffer.sizeInBytes === indicesCount; - indices = useUint8Array - ? new Uint8Array(indicesCount) - : IndexDatatype.createTypedArray(vertexCount, indicesCount); - indicesBuffer.getBufferData(indices, 0, 0, indicesCount); - } - primitive.indices.typedArray = indices; - } - - let vertices = positionAttribute.typedArray; - let componentDatatype = positionAttribute.componentDatatype; - let attributeType = positionAttribute.type; - - const quantization = positionAttribute.quantization; - if (defined(quantization)) { - componentDatatype = positionAttribute.quantization.componentDatatype; - attributeType = positionAttribute.quantization.type; - } - - const numComponents = AttributeType.getNumberOfComponents(attributeType); - const elementCount = vertexCount * numComponents; - - if (!defined(vertices)) { - const verticesBuffer = positionAttribute.buffer; - - if (defined(verticesBuffer) && frameState.context.webgl2) { - vertices = ComponentDatatype.createTypedArray( - componentDatatype, - elementCount - ); - verticesBuffer.getBufferData( - vertices, - positionAttribute.byteOffset, - 0, - elementCount - ); - } - - if (quantization && positionAttribute.normalized) { - vertices = AttributeCompression.dequantize( - vertices, - componentDatatype, - attributeType, - vertexCount - ); - } - - positionAttribute.typedArray = vertices; - } - - const indicesLength = indices.length; - for (let i = 0; i < indicesLength; i += 3) { - const i0 = indices[i]; - const i1 = indices[i + 1]; - const i2 = indices[i + 2]; - - const getPosition = (vertices, index, numComponents, result) => { - const i = index * numComponents; - result.x = vertices[i]; - result.y = vertices[i + 1]; - result.z = vertices[i + 2]; - - if (defined(quantization)) { - if (quantization.octEncoded) { - result = AttributeCompression.octDecodeInRange( - result, - quantization.normalizationRange, - result - ); - - if (quantization.octEncodedZXY) { - const x = result.x; - result.x = result.z; - result.z = result.y; - result.y = x; - } - } else { - result = Cartesian3.multiplyComponents( - result, - quantization.quantizedVolumeStepSize, - result - ); - - result = Cartesian3.add( - result, - quantization.quantizedVolumeOffset, - result - ); - } - } - - result = Matrix4.multiplyByPoint(computedModelMatrix, result, result); - - return result; - }; - - const v0 = getPosition(vertices, i0, numComponents, scratchV0); - const v1 = getPosition(vertices, i1, numComponents, scratchV1); - const v2 = getPosition(vertices, i2, numComponents, scratchV2); - - const t = IntersectionTests.rayTriangleParametric( - ray, - v0, - v1, - v2, - cullBackFaces - ); - - if (defined(t)) { - if (t < minT && t >= 0.0) { - minT = t; - } - } - } - } - } - - if (minT === Number.MAX_VALUE) { - return undefined; - } - - result = Ray.getPoint(ray, minT, result); - if (frameState.mode !== SceneMode.SCENE3D) { - Cartesian3.fromElements(result.y, result.z, result.x, result); - - const projection = frameState.mapProjection; - const ellipsoid = projection.ellipsoid; - - const cart = projection.unproject(result, scratchPickCartographic); - ellipsoid.cartographicToCartesian(cart, result); - } - - return result; +Model.prototype.pick = function (ray, frameState, result) { + return pickModel(this, ray, frameState, result); }; /** @@ -2843,6 +2655,7 @@ Model.prototype.destroyModelResources = function () { * @param {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen. * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this model. * @param {boolean} [options.projectTo2D=false] Whether to accurately project the model's positions in 2D. If this is true, the model will be projected accurately to 2D, but it will use more memory to do so. If this is false, the model will use less memory and will still render in 2D / CV mode, but its positions may be inaccurate. This disables minimumPixelSize and prevents future modification to the model matrix. This also cannot be set after the model has loaded. + * @param {boolean} [options.enablePick=false] Whether to allow with CPU picking with pick when not using WebGL 2 or above. If using WebGL 2 or above, this option will be ignored. If using WebGL 1 and this is true, the pick operation will work correctly, but it will use more memory to do so. If running with WebGL 1 and this is false, the model will use less memory, but pick will always return undefined. This cannot be set after the model has loaded. * @param {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and lighting. @@ -2937,6 +2750,7 @@ Model.fromGltfAsync = async function (options) { upAxis: options.upAxis, forwardAxis: options.forwardAxis, loadAttributesFor2D: options.projectTo2D, + enablePick: options.enablePick, loadIndicesForWireframe: options.enableDebugWireframe, loadPrimitiveOutline: options.enableShowOutline, loadForClassification: defined(options.classificationType), @@ -3013,6 +2827,7 @@ Model.fromB3dm = async function (options) { upAxis: options.upAxis, forwardAxis: options.forwardAxis, loadAttributesFor2D: options.projectTo2D, + enablePick: options.enablePick, loadIndicesForWireframe: options.enableDebugWireframe, loadPrimitiveOutline: options.enableShowOutline, loadForClassification: defined(options.classificationType), @@ -3068,6 +2883,7 @@ Model.fromI3dm = async function (options) { upAxis: options.upAxis, forwardAxis: options.forwardAxis, loadAttributesFor2D: options.projectTo2D, + enablePick: options.enablePick, loadIndicesForWireframe: options.enableDebugWireframe, loadPrimitiveOutline: options.enableShowOutline, }; @@ -3201,6 +3017,7 @@ function makeModelOptions(loader, modelType, options) { showCreditsOnScreen: options.showCreditsOnScreen, splitDirection: options.splitDirection, projectTo2D: options.projectTo2D, + enablePick: options.enablePick, featureIdLabel: options.featureIdLabel, instanceFeatureIdLabel: options.instanceFeatureIdLabel, pointCloudShading: options.pointCloudShading, diff --git a/packages/engine/Source/Scene/Model/Model3DTileContent.js b/packages/engine/Source/Scene/Model/Model3DTileContent.js index c0e71069a2c4..3273c81146d6 100644 --- a/packages/engine/Source/Scene/Model/Model3DTileContent.js +++ b/packages/engine/Source/Scene/Model/Model3DTileContent.js @@ -421,23 +421,17 @@ Model3DTileContent.fromGeoJson = async function ( * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Model3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Model3DTileContent.prototype.pick = function (ray, frameState, result) { if (!defined(this._model) || !this._ready) { return undefined; } - return this._model.pick(ray, frameState, cullBackFaces, result); + return this._model.pick(ray, frameState, result); }; function makeModelOptions(tileset, tile, content, additionalOptions) { @@ -466,6 +460,7 @@ function makeModelOptions(tileset, tile, content, additionalOptions) { enableDebugWireframe: tileset._enableDebugWireframe, debugWireframe: tileset.debugWireframe, projectTo2D: tileset._projectTo2D, + enablePick: tileset._enablePick, enableShowOutline: tileset._enableShowOutline, showOutline: tileset.showOutline, outlineColor: tileset.outlineColor, diff --git a/packages/engine/Source/Scene/Model/pickModel.js b/packages/engine/Source/Scene/Model/pickModel.js new file mode 100644 index 000000000000..16a2bb02f94f --- /dev/null +++ b/packages/engine/Source/Scene/Model/pickModel.js @@ -0,0 +1,369 @@ +import AttributeCompression from "../../Core/AttributeCompression.js"; +import BoundingSphere from "../../Core/BoundingSphere.js"; +import Cartesian3 from "../../Core/Cartesian3.js"; +import Cartographic from "../../Core/Cartographic.js"; +import Check from "../../Core/Check.js"; +import ComponentDatatype from "../../Core/ComponentDatatype.js"; +import defaultValue from "../../Core/defaultValue.js"; +import defined from "../../Core/defined.js"; +import IndexDatatype from "../../Core/IndexDatatype.js"; +import IntersectionTests from "../../Core/IntersectionTests.js"; +import Ray from "../../Core/Ray.js"; +import Matrix4 from "../../Core/Matrix4.js"; +import Transforms from "../../Core/Transforms.js"; +import AttributeType from "../AttributeType.js"; +import SceneMode from "../SceneMode.js"; +import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; +import ModelUtility from "./ModelUtility.js"; + +const scratchV0 = new Cartesian3(); +const scratchV1 = new Cartesian3(); +const scratchV2 = new Cartesian3(); +const scratchNodeComputedTransform = new Matrix4(); +const scratchModelMatrix = new Matrix4(); +const scratchcomputedModelMatrix = new Matrix4(); +const scratchPickCartographic = new Cartographic(); +const scratchBoundingSphere = new BoundingSphere(); + +/** + * Find an intersection between a ray and the model surface that was rendered. The ray must be given in world coordinates. + * + * @param {Model} model The model to pick. + * @param {Ray} ray The ray to test for intersection. + * @param {FrameState} frameState The frame state. + * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. + * @returns {Cartesian3|undefined} The intersection or undefined if none was found. + * + * @private + */ +export default function pickModel(model, ray, frameState, result) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("model", model); + Check.typeOf.object("ray", ray); + Check.typeOf.object("frameState", frameState); + //>>includeEnd('debug'); + + if (!model._ready || frameState.mode === SceneMode.MORPHING) { + return; + } + + let minT = Number.MAX_VALUE; + const sceneGraph = model.sceneGraph; + + const nodes = sceneGraph._runtimeNodes; + for (let i = 0; i < nodes.length; i++) { + const runtimeNode = nodes[i]; + const node = runtimeNode.node; + + let nodeComputedTransform = Matrix4.clone( + runtimeNode.computedTransform, + scratchNodeComputedTransform + ); + let modelMatrix = Matrix4.clone( + sceneGraph.computedModelMatrix, + scratchModelMatrix + ); + + const instances = node.instances; + if (defined(instances)) { + if (instances.transformInWorldSpace) { + // Replicate the multiplication order in LegacyInstancingStageVS. + modelMatrix = Matrix4.multiplyTransformation( + model.modelMatrix, + sceneGraph.components.transform, + modelMatrix + ); + + nodeComputedTransform = Matrix4.multiplyTransformation( + sceneGraph.axisCorrectionMatrix, + runtimeNode.computedTransform, + nodeComputedTransform + ); + } + } + + if (frameState.mode !== SceneMode.SCENE3D) { + modelMatrix = Transforms.basisTo2D( + frameState.mapProjection, + modelMatrix, + modelMatrix + ); + } + + const computedModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + nodeComputedTransform, + scratchcomputedModelMatrix + ); + + const transforms = []; + if (defined(instances)) { + const transformsCount = instances.attributes[0].count; + const instanceComponentDatatype = + instances.attributes[0].componentDatatype; + + const transformElements = 12; + let transformsTypedArray = runtimeNode.transformsTypedArray; + if (!defined(transformsTypedArray)) { + const instanceTransformsBuffer = runtimeNode.instancingTransformsBuffer; + if (defined(instanceTransformsBuffer) && frameState.context.webgl2) { + transformsTypedArray = ComponentDatatype.createTypedArray( + instanceComponentDatatype, + transformsCount * transformElements + ); + instanceTransformsBuffer.getBufferData(transformsTypedArray); + } + } + + if (defined(transformsTypedArray)) { + for (let i = 0; i < transformsCount; i++) { + const index = i * transformElements; + + const transform = new Matrix4( + transformsTypedArray[index], + transformsTypedArray[index + 1], + transformsTypedArray[index + 2], + transformsTypedArray[index + 3], + transformsTypedArray[index + 4], + transformsTypedArray[index + 5], + transformsTypedArray[index + 6], + transformsTypedArray[index + 7], + transformsTypedArray[index + 8], + transformsTypedArray[index + 9], + transformsTypedArray[index + 10], + transformsTypedArray[index + 11], + 0, + 0, + 0, + 1 + ); + + if (instances.transformInWorldSpace) { + Matrix4.multiplyTransformation( + transform, + nodeComputedTransform, + transform + ); + Matrix4.multiplyTransformation(modelMatrix, transform, transform); + } else { + Matrix4.multiplyTransformation( + transform, + computedModelMatrix, + transform + ); + } + transforms.push(transform); + } + } + } + + if (transforms.length === 0) { + transforms.push(computedModelMatrix); + } + + const primitivesLength = runtimeNode.runtimePrimitives.length; + for (let j = 0; j < primitivesLength; j++) { + const runtimePrimitive = runtimeNode.runtimePrimitives[j]; + const primitive = runtimePrimitive.primitive; + + if (defined(runtimePrimitive.boundingSphere) && !defined(instances)) { + const boundingSphere = BoundingSphere.transform( + runtimePrimitive.boundingSphere, + computedModelMatrix, + scratchBoundingSphere + ); + const boundsIntersection = IntersectionTests.raySphere( + ray, + boundingSphere + ); + if (!defined(boundsIntersection)) { + continue; + } + } + + const positionAttribute = ModelUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.POSITION + ); + const vertexCount = positionAttribute.count; + + if (!defined(primitive.indices)) { + // Point clouds + continue; + } + + let indices = primitive.indices.typedArray; + if (!defined(indices)) { + const indicesBuffer = primitive.indices.buffer; + const indicesCount = primitive.indices.count; + const indexDatatype = primitive.indices.indexDatatype; + if (defined(indicesBuffer) && frameState.context.webgl2) { + if (indexDatatype === IndexDatatype.UNSIGNED_BYTE) { + indices = new Uint8Array(indicesCount); + } else if (indexDatatype === IndexDatatype.UNSIGNED_SHORT) { + indices = new Uint16Array(indicesCount); + } else if (indexDatatype === IndexDatatype.UNSIGNED_INT) { + indices = new Uint32Array(indicesCount); + } + + indicesBuffer.getBufferData(indices); + } + } + + let vertices = positionAttribute.typedArray; + let componentDatatype = positionAttribute.componentDatatype; + let attributeType = positionAttribute.type; + + const quantization = positionAttribute.quantization; + if (defined(quantization)) { + componentDatatype = positionAttribute.quantization.componentDatatype; + attributeType = positionAttribute.quantization.type; + } + + const numComponents = AttributeType.getNumberOfComponents(attributeType); + const elementCount = vertexCount * numComponents; + + if (!defined(vertices)) { + const verticesBuffer = positionAttribute.buffer; + + if (defined(verticesBuffer) && frameState.context.webgl2) { + vertices = ComponentDatatype.createTypedArray( + componentDatatype, + elementCount + ); + verticesBuffer.getBufferData( + vertices, + positionAttribute.byteOffset, + 0, + elementCount + ); + } + + if (quantization && positionAttribute.normalized) { + vertices = AttributeCompression.dequantize( + vertices, + componentDatatype, + attributeType, + vertexCount + ); + } + } + + if (!defined(indices) || !defined(vertices)) { + return; + } + + const indicesLength = indices.length; + for (let i = 0; i < indicesLength; i += 3) { + const i0 = indices[i]; + const i1 = indices[i + 1]; + const i2 = indices[i + 2]; + + for (const instanceTransform of transforms) { + const v0 = getVertexPosition( + vertices, + i0, + numComponents, + quantization, + instanceTransform, + scratchV0 + ); + const v1 = getVertexPosition( + vertices, + i1, + numComponents, + quantization, + instanceTransform, + scratchV1 + ); + const v2 = getVertexPosition( + vertices, + i2, + numComponents, + quantization, + instanceTransform, + scratchV2 + ); + + const t = IntersectionTests.rayTriangleParametric( + ray, + v0, + v1, + v2, + defaultValue(model.backFaceCulling, true) + ); + + if (defined(t)) { + if (t < minT && t >= 0.0) { + minT = t; + } + } + } + } + } + } + + if (minT === Number.MAX_VALUE) { + return undefined; + } + + result = Ray.getPoint(ray, minT, result); + if (frameState.mode !== SceneMode.SCENE3D) { + Cartesian3.fromElements(result.y, result.z, result.x, result); + + const projection = frameState.mapProjection; + const ellipsoid = projection.ellipsoid; + + const cartographic = projection.unproject(result, scratchPickCartographic); + ellipsoid.cartographicToCartesian(cartographic, result); + } + + return result; +} + +function getVertexPosition( + vertices, + index, + numComponents, + quantization, + instanceTransform, + result +) { + const i = index * numComponents; + result.x = vertices[i]; + result.y = vertices[i + 1]; + result.z = vertices[i + 2]; + + if (defined(quantization)) { + if (quantization.octEncoded) { + result = AttributeCompression.octDecodeInRange( + result, + quantization.normalizationRange, + result + ); + + if (quantization.octEncodedZXY) { + const x = result.x; + result.x = result.z; + result.z = result.y; + result.y = x; + } + } else { + result = Cartesian3.multiplyComponents( + result, + quantization.quantizedVolumeStepSize, + result + ); + + result = Cartesian3.add( + result, + quantization.quantizedVolumeOffset, + result + ); + } + } + + result = Matrix4.multiplyByPoint(instanceTransform, result, result); + + return result; +} diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index cd62641172b4..42ede698a349 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -1,3 +1,4 @@ +import Cartesian3 from "../Core/Cartesian3.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; @@ -650,6 +651,47 @@ Multiple3DTileContent.prototype.update = function (tileset, frameState) { } }; +/** + * Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates. + * + * @param {Ray} ray The ray to test for intersection. + * @param {FrameState} frameState The frame state. + * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. + * @returns {Cartesian3|undefined} The intersection or undefined if none was found. + * + * @private + */ +Multiple3DTileContent.prototype.pick = function (ray, frameState, result) { + if (!this._ready) { + return undefined; + } + + let intersection; + let minDistance = Number.POSITIVE_INFINITY; + const contents = this._contents; + const length = contents.length; + + for (let i = 0; i < length; ++i) { + const candidate = contents[i].pick(ray, frameState, result); + + if (!defined(candidate)) { + continue; + } + + const distance = Cartesian3.distance(ray.origin, candidate); + if (distance < minDistance) { + intersection = candidate; + minDistance = distance; + } + } + + if (!defined(intersection)) { + return undefined; + } + + return result; +}; + Multiple3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 59239949c591..40a6e81bea95 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -163,7 +163,6 @@ function Scene(options) { this._globeTranslucencyState = new GlobeTranslucencyState(); this._primitives = new PrimitiveCollection(); this._groundPrimitives = new PrimitiveCollection(); - this._terrainTilesets = []; this._globeHeight = undefined; this._cameraUnderground = false; @@ -3568,35 +3567,6 @@ function callAfterRenderFunctions(scene) { functions.length = 0; } -/** - * Allow camera collisions, if enabled for the camera, on a tileset surface - * @param {Cesium3DTileset} tileset Tileset fo which to enable collision. - */ -Scene.prototype.enableCollisionDetectionForTileset = function (tileset) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("tileset", tileset); - //>>includeEnd('debug'); - - this._terrainTilesets.push(tileset); -}; - -/** - * Disallow camera collisions on a tileset surface - * @param {Cesium3DTileset} tileset Tileset for which to disable collision. - */ -Scene.prototype.disableCollisionDetectionForTileset = function (tileset) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("tileset", tileset); - //>>includeEnd('debug'); - - const i = this._terrainTilesets.indexOf(tileset); - if (i === -1) { - return; - } - - this._terrainTilesets.splice(i, 1); -}; - function getGlobeHeight(scene) { const camera = scene.camera; const cartographic = camera.positionCartographic; @@ -3623,14 +3593,17 @@ Scene.prototype.getHeight = function (cartographic, heightReference) { heightReference === HeightReference.RELATIVE_TO_3D_TILE; let maxHeight = Number.NEGATIVE_INFINITY; + if (!ignore3dTiles) { - for (const tileset of this._terrainTilesets) { - if (!tileset.show) { + const length = this.primitives.length; + for (let i = 0; i < length; ++i) { + const primitive = this.primitives.get(i); + if (!primitive.isCesium3DTileset || !primitive.enableCameraCollision) { continue; } - const result = tileset.getHeight(cartographic, this); - if (result > maxHeight) { + const result = primitive.getHeight(cartographic, this); + if (defined(result) && result > maxHeight) { maxHeight = result; } } diff --git a/packages/engine/Source/Scene/Tileset3DTileContent.js b/packages/engine/Source/Scene/Tileset3DTileContent.js index 655f0e675ef1..41d1652453c1 100644 --- a/packages/engine/Source/Scene/Tileset3DTileContent.js +++ b/packages/engine/Source/Scene/Tileset3DTileContent.js @@ -168,6 +168,10 @@ Tileset3DTileContent.prototype.applyStyle = function (style) {}; Tileset3DTileContent.prototype.update = function (tileset, frameState) {}; +Tileset3DTileContent.prototype.pick = function (ray, frameState, result) { + return undefined; +}; + Tileset3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index e191ede706a9..120a22db5801 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -727,6 +727,10 @@ Vector3DTileContent.prototype.update = function (tileset, frameState) { } }; +Vector3DTileContent.prototype.pick = function (ray, frameState, result) { + return undefined; +}; + Vector3DTileContent.prototype.getPolylinePositions = function (batchId) { const polylines = this._polylines; if (!defined(polylines)) { diff --git a/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js b/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js index d6c708a9d12d..7877b3ac852b 100644 --- a/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js +++ b/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js @@ -41,12 +41,15 @@ import Resource from "../Core/Resource.js"; */ async function createGooglePhotorealistic3DTileset(key, options) { options = defaultValue(options, {}); - options.showCreditsOnScreen = true; options.cacheBytes = defaultValue(options.cacheBytes, 1536 * 1024 * 1024); options.maximumCacheOverflowBytes = defaultValue( options.maximumCacheOverflowBytes, 1024 * 1024 * 1024 ); + options.enableCameraCollision = defaultValue( + options.enableCameraCollision, + true + ); key = defaultValue(key, GoogleMaps.defaultApiKey); if (!defined(key)) { diff --git a/packages/engine/Specs/Core/EllipsoidRhumbLineSpec.js b/packages/engine/Specs/Core/EllipsoidRhumbLineSpec.js index f56803e3f411..87157adea73d 100644 --- a/packages/engine/Specs/Core/EllipsoidRhumbLineSpec.js +++ b/packages/engine/Specs/Core/EllipsoidRhumbLineSpec.js @@ -13,6 +13,7 @@ describe("Core/EllipsoidRhumbLine", function () { const fifteenDegrees = Math.PI / 12; const thirtyDegrees = Math.PI / 6; const fortyfiveDegrees = Math.PI / 4; + const threeHundredDegrees = (5 * Math.PI) / 6; it("throws without start", function () { expect(function () { @@ -749,6 +750,46 @@ describe("Core/EllipsoidRhumbLine", function () { ); }); + it("interpolates when heading is near 90 degrees", function () { + const start = new Cartographic(0.0, 0.0); + const end = new Cartographic(Math.PI / 2, 0.0); + const expectedMid = new Cartographic(fortyfiveDegrees, 0.0); + + const rhumb = new EllipsoidRhumbLine(start, end); + expect(rhumb.heading).toEqualEpsilon(Math.PI / 2, CesiumMath.EPSILON12); + + const midpoint = rhumb.interpolateUsingFraction(0.5); + + expect(expectedMid.longitude).toEqualEpsilon( + midpoint.longitude, + CesiumMath.EPSILON12 + ); + expect(expectedMid.latitude).toEqualEpsilon( + midpoint.latitude, + CesiumMath.EPSILON12 + ); + }); + + it("interpolates when heading is near 0 degrees", function () { + const start = new Cartographic(-threeHundredDegrees, fifteenDegrees); + const end = new Cartographic(-threeHundredDegrees, fortyfiveDegrees); + const expectedMid = new Cartographic(-threeHundredDegrees, thirtyDegrees); + + const rhumb = new EllipsoidRhumbLine(start, end); + expect(rhumb.heading).toEqualEpsilon(0, CesiumMath.EPSILON12); + + const midpoint = rhumb.interpolateUsingFraction(0.5); + + expect(expectedMid.longitude).toEqualEpsilon( + midpoint.longitude, + CesiumMath.EPSILON12 + ); + expect(expectedMid.latitude).toEqualEpsilon( + midpoint.latitude, + CesiumMath.EPSILON3 + ); + }); + it("interpolates midpoint fraction using result parameter", function () { const start = new Cartographic(fifteenDegrees, 0.0); const end = new Cartographic(fortyfiveDegrees, 0.0); diff --git a/packages/engine/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/packages/engine/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 7847fe668050..8927fca15e42 100644 --- a/packages/engine/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/packages/engine/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -314,7 +314,11 @@ describe("Core/GoogleEarthEnterpriseTerrainProvider", function () { const promises = []; return pollToPromise(function () { let b = true; - for (let i = 0; i < 10; ++i) { + for ( + let i = 0; + i < RequestScheduler.maximumRequestsPerServer + 2; + ++i + ) { b = b && terrainProvider.getTileDataAvailable(i, i, i); } return b && terrainProvider.getTileDataAvailable(1, 2, 3); diff --git a/packages/engine/Specs/Core/sampleTerrainMostDetailedSpec.js b/packages/engine/Specs/Core/sampleTerrainMostDetailedSpec.js index 3a55df6250dc..77635449b0d9 100644 --- a/packages/engine/Specs/Core/sampleTerrainMostDetailedSpec.js +++ b/packages/engine/Specs/Core/sampleTerrainMostDetailedSpec.js @@ -28,9 +28,9 @@ describe("Core/sampleTerrainMostDetailed", function () { expect(positions[1].height).toBeLessThan(10000); }); - it("should throw querying heights from Small Terrain", async function () { + it("should throw querying heights from terrain without availability", async function () { const terrainProvider = await CesiumTerrainProvider.fromUrl( - "https://s3.amazonaws.com/cesiumjs/smallTerrain" + "Data/CesiumTerrainTileJson/StandardHeightmap.tile.json" ); const positions = [ diff --git a/packages/engine/Specs/Core/sampleTerrainSpec.js b/packages/engine/Specs/Core/sampleTerrainSpec.js index 3369481f068d..c88c1a5ddff3 100644 --- a/packages/engine/Specs/Core/sampleTerrainSpec.js +++ b/packages/engine/Specs/Core/sampleTerrainSpec.js @@ -15,6 +15,11 @@ describe("Core/sampleTerrain", function () { worldTerrain = await createWorldTerrainAsync(); }); + afterEach(function () { + Resource._Implementations.loadWithXhr = + Resource._DefaultImplementations.loadWithXhr; + }); + it("queries heights", function () { const positions = [ Cartographic.fromDegrees(86.925145, 27.988257), @@ -32,9 +37,42 @@ describe("Core/sampleTerrain", function () { }); }); - it("queries heights from Small Terrain", async function () { + it("queries heights from terrain without availability", async function () { + // Mock terrain tile loading + Resource._Implementations.loadWithXhr = function ( + url, + responseType, + method, + data, + headers, + deferred, + overrideMimeType + ) { + if (defined(url.match(/\/\d+\/\d+\/\d+\.terrain/))) { + Resource._DefaultImplementations.loadWithXhr( + "Data/CesiumTerrainTileJson/11_3027_1342.terrain", + responseType, + method, + data, + headers, + deferred + ); + return; + } + + Resource._DefaultImplementations.loadWithXhr( + url, + responseType, + method, + data, + headers, + deferred, + overrideMimeType + ); + }; + const terrainProvider = await CesiumTerrainProvider.fromUrl( - "https://s3.amazonaws.com/cesiumjs/smallTerrain" + "Data/CesiumTerrainTileJson/StandardHeightmap.tile.json" ); const positions = [ diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 2e3381f89d49..341c8e365a1d 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -46,6 +46,7 @@ import Cesium3DTilesTester from "../../../../Specs/Cesium3DTilesTester.js"; import createScene from "../../../../Specs/createScene.js"; import generateJsonBuffer from "../../../../Specs/generateJsonBuffer.js"; import pollToPromise from "../../../../Specs/pollToPromise.js"; +import Ellipsoid from "../../Source/Core/Ellipsoid.js"; describe( "Scene/Cesium3DTileset", @@ -2414,6 +2415,183 @@ describe( }); }); + it("picks", async function () { + const tileset = await Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { + enablePick: !scene.frameState.context.webgl2, + }); + viewRootOnly(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215026.8094312553, + -4736367.339076743, + 4081652.238842398 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks tileset of tilesets", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetOfTilesetsUrl, + { + enablePick: !scene.frameState.context.webgl2, + } + ); + viewRootOnly(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215026.8094312553, + -4736367.339076743, + 4081652.238842398 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks instanced tileset", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + instancedUrl, + { + enablePick: !scene.frameState.context.webgl2, + } + ); + viewInstances(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215015.7820120894, + -4736324.352446682, + 4081615.004915994 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks translucent tileset", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + translucentUrl, + { + enablePick: !scene.frameState.context.webgl2, + } + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215013.1035421563, + -4736313.911345786, + 4081605.96109977 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks tileset with transforms", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetWithTransformsUrl, + { + enablePick: !scene.frameState.context.webgl2, + } + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215013.8353220497, + -4736316.763939952, + 4081608.4319443353 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picking point cloud tileset returns undefined", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + pointCloudUrl, + { + enablePick: !scene.frameState.context.webgl2, + } + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + expect(tileset.pick(ray, scene.frameState)).toBeUndefined(); + }); + + it("getHeight samples height at a cartographic position", async function () { + const tileset = await Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { + enablePick: !scene.frameState.context.webgl2, + }); + viewRootOnly(); + await Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); + scene.renderForSpecs(); + + const center = Ellipsoid.WGS84.cartesianToCartographic( + tileset.boundingSphere.center + ); + const height = tileset.getHeight(center, scene); + expect(height).toEqualEpsilon(78.1558019795064, CesiumMath.EPSILON12); + }); + it("destroys", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset diff --git a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 129bb57b8cf0..9818f2534b2b 100644 --- a/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/packages/engine/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -12,6 +12,7 @@ import { GeographicProjection, HeadingPitchRoll, Rectangle, + Resource, WebMercatorProjection, ContextLimits, RenderState, @@ -104,6 +105,8 @@ describe( afterEach(function () { scene.imageryLayers.removeAll(); scene.primitives.removeAll(); + Resource._Implementations.loadWithXhr = + Resource._DefaultImplementations.loadWithXhr; }); it("conforms to QuadtreeTileProvider interface", function () { @@ -902,8 +905,41 @@ describe( scene.imageryLayers.addImageryProvider(provider); const terrainCredit = new Credit("terrain credit"); + + // Mock terrain tile loading + Resource._Implementations.loadWithXhr = function ( + url, + responseType, + method, + data, + headers, + deferred, + overrideMimeType + ) { + if (defined(url.match(/\/\d+\/\d+\/\d+\.terrain/))) { + Resource._DefaultImplementations.loadWithXhr( + "Data/CesiumTerrainTileJson/tile.32bitIndices.terrain", + responseType, + method, + data, + headers, + deferred + ); + return; + } + + Resource._DefaultImplementations.loadWithXhr( + url, + responseType, + method, + data, + headers, + deferred, + overrideMimeType + ); + }; scene.terrainProvider = await CesiumTerrainProvider.fromUrl( - "https://s3.amazonaws.com/cesiumjs/smallTerrain", + "Data/CesiumTerrainTileJson/QuantizedMesh.tile.json", { credit: terrainCredit, } diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index df62257eb949..a93f00370367 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -4392,6 +4392,28 @@ describe( }); }); + it("pick returns position of intersection between ray and model surface", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.5, 0, 0.5); + expect(model.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + it("destroy works", function () { spyOn(ShaderProgram.prototype, "destroy").and.callThrough(); return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( diff --git a/packages/engine/Specs/Scene/Model/pickModelSpec.js b/packages/engine/Specs/Scene/Model/pickModelSpec.js new file mode 100644 index 000000000000..af89fd06907d --- /dev/null +++ b/packages/engine/Specs/Scene/Model/pickModelSpec.js @@ -0,0 +1,365 @@ +import { + pickModel, + Cartesian2, + Cartesian3, + HeadingPitchRange, + Math as CesiumMath, + Model, + Ray, + SceneMode, +} from "../../../index.js"; + +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; +import createScene from "../../../../../Specs/createScene.js"; + +describe("Scene/Model/pickModel", function () { + const boxTexturedGltfUrl = + "./Data/Models/glTF-2.0/BoxTextured/glTF/BoxTextured.gltf"; + const boxInstanced = + "./Data/Models/glTF-2.0/BoxInstancedNoNormals/glTF/BoxInstancedNoNormals.gltf"; + const boxWithOffsetUrl = + "./Data/Models/glTF-2.0/BoxWithOffset/glTF/BoxWithOffset.gltf"; + const pointCloudUrl = + "./Data/Models/glTF-2.0/PointCloudWithRGBColors/glTF-Binary/PointCloudWithRGBColors.glb"; + const boxWithMixedCompression = + "./Data/Models/glTF-2.0/BoxMixedCompression/glTF/BoxMixedCompression.gltf"; + const boxWithQuantizedAttributes = + "./Data/Models/glTF-2.0/BoxWeb3dQuantizedAttributes/glTF/BoxWeb3dQuantizedAttributes.gltf"; + const boxCesiumRtcUrl = + "./Data/Models/glTF-2.0/BoxCesiumRtc/glTF/BoxCesiumRtc.gltf"; + const boxBackFaceCullingUrl = + "./Data/Models/glTF-2.0/BoxBackFaceCulling/glTF/BoxBackFaceCulling.gltf"; + + let scene; + beforeAll(function () { + scene = createScene(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + afterEach(function () { + scene.frameState.mode = SceneMode.SCENE3D; + scene.primitives.removeAll(); + }); + + it("throws without model", function () { + expect(() => pickModel()).toThrowDeveloperError(); + }); + + it("throws without ray", async function () { + const model = await Model.fromGltfAsync({ + url: boxTexturedGltfUrl, + }); + expect(() => pickModel(model)).toThrowDeveloperError(); + }); + + it("throws without frameState", async function () { + const model = await Model.fromGltfAsync({ + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }); + const ray = new Ray(); + expect(() => pickModel(model, ray)).toThrowDeveloperError(); + }); + + it("returns undefined if model is not ready", async function () { + const model = await Model.fromGltfAsync({ + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }); + const ray = new Ray(); + expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); + }); + + it("returns undefined if ray does not intersect model surface", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = new Ray(); + expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); + }); + + it("returns position of intersection between ray and model surface", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.5, 0, 0.5); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("returns position of intersection between ray and model surface with enablePick in WebGL 1", async function () { + const sceneWithWebgl1 = createScene({ + contextOptions: { + requestWebgl1: true, + }, + }); + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: true, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.5, 0, 0.5); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + + sceneWithWebgl1.destroyForSpecs(); + }); + + it("returns position of intersection accounting for node transforms", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxWithOffsetUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.0, 5.5, -0.5); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("returns position of intersection with RTC model", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxCesiumRtcUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(6378137.5, 0.0, -0.499999996649); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON8 + ); + }); + + it("returns position of intersection with quantzed model", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxWithQuantizedAttributes, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.5, 0, 0.5); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("returns position of intersection with mixed compression model", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxWithMixedCompression, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(1.0, 0, 1.0); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("returns position of intersection with instanced model", async function () { + // None of the 4 instanced cubes are in the center of the model's bounding + // sphere, so set up a camera view that focuses in on one of them. + const offset = new HeadingPitchRange( + CesiumMath.PI_OVER_TWO, + -CesiumMath.PI_OVER_FOUR, + 1 + ); + + const model = await loadAndZoomToModelAsync( + { + url: boxInstanced, + enablePick: !scene.frameState.context.webgl2, + offset, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0, -0.5, 0.5); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("returns undefined for point cloud", async function () { + const model = await loadAndZoomToModelAsync( + { + url: pointCloudUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); + }); + + it("cullsBackFaces by default", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + ray.origin = model.boundingSphere.center; + + expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); + }); + + it("includes back faces results when model disbales backface culling", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxBackFaceCullingUrl, + enablePick: !scene.frameState.context.webgl2, + backFaceCulling: false, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + ray.origin = model.boundingSphere.center; + + const expected = new Cartesian3( + -0.9999998807907355, + 0, + -0.9999998807907104 + ); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("uses result parameter if specified", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const result = new Cartesian3(); + const expected = new Cartesian3(0.5, 0, 0.5); + const returned = pickModel(model, ray, scene.frameState, result); + expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON12); + expect(returned).toBe(result); + }); + + it("returns undefined when morphing", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + scene.frameState.mode = SceneMode.MORPHING; + expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); + }); +}); diff --git a/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js b/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js index 4d888da1355d..2d13eba58fb1 100644 --- a/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js +++ b/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js @@ -208,7 +208,7 @@ describe( }); }); - it("raises error event when environment map fails to load.", function () { + it("raises error event when environment map fails to load.", async function () { if (!OctahedralProjectedCubeMap.isSupported(context)) { return; } @@ -217,17 +217,22 @@ describe( const frameState = createFrameState(context); let error; - const removeListener = projection.errorEvent.addEventListener((e) => { - error = e; - expect(error).toBeDefined(); - expect(projection.ready).toEqual(false); - removeListener(); + const promise = new Promise((resolve, reject) => { + const removeListener = projection.errorEvent.addEventListener((e) => { + error = e; + expect(error).toBeDefined(); + expect(projection.ready).toEqual(false); + removeListener(); + resolve(); + }); }); - return pollToPromise(function () { + await pollToPromise(function () { projection.update(frameState); return defined(error); }); + + return promise; }); }, "WebGL" diff --git a/packages/engine/Specs/test.mjs b/packages/engine/Specs/test.mjs index 23f6bbf07cc3..1479b34b2dd2 100644 --- a/packages/engine/Specs/test.mjs +++ b/packages/engine/Specs/test.mjs @@ -1,9 +1,9 @@ -import { Cartographic, CesiumTerrainProvider, sampleTerrain } from "@cesium/engine"; +import { Cartographic, createWorldTerrainAsync, sampleTerrain } from "@cesium/engine"; import assert from "node:assert"; // NodeJS smoke screen test async function test() { - const provider = await CesiumTerrainProvider.fromUrl("https://s3.amazonaws.com/cesiumjs/smallTerrain"); + const provider = await createWorldTerrainAsync(); const results = await sampleTerrain(provider, 11, [ Cartographic.fromDegrees(86.925145, 27.988257), Cartographic.fromDegrees(87.0, 28.0), diff --git a/packages/engine/package.json b/packages/engine/package.json index 32a25763dcf6..ee1c7b7a0681 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -1,6 +1,6 @@ { "name": "@cesium/engine", - "version": "6.0.0", + "version": "6.1.1", "description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "keywords": [ "3D", @@ -45,7 +45,7 @@ "ktx-parse": "^0.6.0", "lerc": "^2.0.0", "mersenne-twister": "^1.1.0", - "meshoptimizer": "^0.19.0", + "meshoptimizer": "^0.20.0", "pako": "^2.0.4", "protobufjs": "^7.1.0", "rbush": "^3.0.1", diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 6357fe6992d6..5b121d5c1689 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -1,6 +1,6 @@ { "name": "@cesium/widgets", - "version": "4.2.0", + "version": "4.3.1", "description": "A widgets library for use with CesiumJS. CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "keywords": [ "3D", @@ -28,7 +28,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@cesium/engine": "^6.0.0", + "@cesium/engine": "^6.1.1", "nosleep.js": "^0.12.0" }, "type": "module",