diff --git a/.github/actions/check-for-CLA/index.js b/.github/actions/check-for-CLA/index.js index e1ee29e17d2f..e8c91f867ba9 100644 --- a/.github/actions/check-for-CLA/index.js +++ b/.github/actions/check-for-CLA/index.js @@ -32,7 +32,7 @@ const main = async () => { const response = await postCommentOnPullRequest( hasSignedCLA, - errorFoundOnCLACheck + errorFoundOnCLACheck, ); }; @@ -49,7 +49,7 @@ const checkIfUserHasSignedAnyCLA = async () => { const checkIfIndividualCLAFound = async () => { const response = await getValuesFromGoogleSheet( GOOGLE_SHEETS_INFO.individualCLASheetId, - "D2:D" + "D2:D", ); const rows = response.data.values; @@ -70,7 +70,7 @@ const checkIfIndividualCLAFound = async () => { const checkIfCorporateCLAFound = async () => { const response = await getValuesFromGoogleSheet( GOOGLE_SHEETS_INFO.corporateCLASheetId, - "H2:H" + "H2:H", ); const rows = response.data.values; @@ -132,14 +132,14 @@ const postCommentOnPullRequest = async (hasSignedCLA, errorFoundOnCLACheck) => { accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", }, - } + }, ); }; const getCommentBody = (hasSignedCLA, errorFoundOnCLACheck) => { const commentTemplate = fs.readFileSync( "./.github/actions/check-for-CLA/templates/pullRequestComment.hbs", - "utf-8" + "utf-8", ); const getCommentFromTemplate = Handlebars.compile(commentTemplate); diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 610927976ada..4ca9ddbf3d8b 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -16,12 +16,12 @@ jobs: with: node-version: '20' - name: install npm packages - run: npm install googleapis @octokit/core handlebars fs-extra + run: npm install googleapis @octokit/core handlebars fs-extra - name: run script run: node .github/actions/check-for-CLA/index.js env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PULL_REQUEST_ID: ${{ github.event.number }} + PULL_REQUEST_ID: ${{ github.event.number }} GOOGLE_KEYS: ${{ secrets.GOOGLE_KEYS }} - INDIVIDUAL_CLA_SHEET_ID: ${{ secrets.INDIVIDUAL_CLA_SHEET_ID }} - CORPORATE_CLA_SHEET_ID: ${{ secrets.CORPORATE_CLA_SHEET_ID }} \ No newline at end of file + INDIVIDUAL_CLA_SHEET_ID: ${{ secrets.INDIVIDUAL_CLA_SHEET_ID }} + CORPORATE_CLA_SHEET_ID: ${{ secrets.CORPORATE_CLA_SHEET_ID }} diff --git a/.gitignore b/.gitignore index f86fac7a600e..50fe932f1bca 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,4 @@ yarn.lock .idea/shelf # Used in the CLA checking GitHub workflow -GoogleConfig.json \ No newline at end of file +GoogleConfig.json diff --git a/CHANGES.md b/CHANGES.md index 7a557856fdaa..ec929e05b322 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -54,7 +54,6 @@ ##### Additions :tada: -- Added `HeightReference.CLAMP_TO_TERRAIN`, `HeightReference.RELATIVE_TO_TERRAIN`, `HeightReference.CLAMP_TO_3D_TILE`, and `HeightReference.RELATIVE_TO_3D_TILE` to position relatve to terrain or 3D tilesets exclusively.[#11604](https://github.com/CesiumGS/cesium/pull/11604) - 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.disableCollision` to allow the camera from to go inside or below a 3D tileset, for instance, to be used with 3D Tiles interiors. [#11581](https://github.com/CesiumGS/cesium/pull/11581) - Fog rendering now applies to glTF models and 3D Tiles. This can be configured using `scene.fog` and `scene.atmosphere`. [#11744](https://github.com/CesiumGS/cesium/pull/11744) @@ -335,6 +334,7 @@ try { ##### Additions :tada: - Added `Cesium3DTileset.cacheBytes` and `Cesium3DTileset.maximumCacheOverflowBytes` to better control memory usage. To replicate previous behavior, convert `maximumMemoryUsage` from MB to bytes, assign the value to `cacheBytes`, and set `maximumCacheOverflowBytes = Number.MAX_VALUE` +- Added `Cesium.Scene.webXRContext` and initial support for integrating Cesium into the WebXR redering cycle. ##### Fixes :wrench: @@ -1968,7 +1968,7 @@ _This is an npm-only release to fix a publishing issue_. - This is to make clipping planes' coordinates always relative to the object they're attached to. So if you were positioning the clipping planes as in the example below, this is no longer necessary: ```javascript clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame( - tileset.boundingSphere.center + tileset.boundingSphere.center, ); ``` - This also fixes several issues with clipping planes not using the correct transform for tilesets with children. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8850724dfca6..9a1b9f1b4075 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -381,3 +381,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Harsh Lakhara](https://github.com/harshlakhara) - [Pavlo Skakun](https://github.com/p-skakun) - [Taylor Huffman](https://github.com/huffmantayler) +- [Arturo Espinosa Aldama](https://github.com/pupitetris) diff --git a/package.json b/package.json index 4c8066d844ca..644c0993654c 100644 --- a/package.json +++ b/package.json @@ -160,4 +160,4 @@ "packages/engine", "packages/widgets" ] -} \ No newline at end of file +} diff --git a/packages/engine/Source/Core/PerspectiveOffCenterFrustum.js b/packages/engine/Source/Core/PerspectiveOffCenterFrustum.js index 00339b199d60..1654f2e4ee71 100644 --- a/packages/engine/Source/Core/PerspectiveOffCenterFrustum.js +++ b/packages/engine/Source/Core/PerspectiveOffCenterFrustum.js @@ -92,6 +92,50 @@ function PerspectiveOffCenterFrustum(options) { this._infinitePerspective = new Matrix4(); } +/** + * Obtains a PerspectiveOffCenterFrustum from a projection matrix. + * + * @param {Matrix4} projectionMatrix The matrix representing the projection tranformation. + * @param {PerspectiveOffCenterFrustum} [result] The object into which to store the result. + * @returns {PerspectiveOffCenterFrustum} The modified result parameter or a new PerspectiveOffCenterFrustum instance if one was not provided. + */ +PerspectiveOffCenterFrustum.fromProjectionMatrix = function ( + projectionMatrix, + result +) { + if (!defined(result)) { + result = new PerspectiveOffCenterFrustum(); + } + + const near = + projectionMatrix[Matrix4.COLUMN3ROW2] / + (projectionMatrix[Matrix4.COLUMN2ROW2] - 1); + + result.near = near; + + result.far = + projectionMatrix[Matrix4.COLUMN3ROW2] / + (1 + projectionMatrix[Matrix4.COLUMN2ROW2]); + + result.right = + (near * (1 + projectionMatrix[Matrix4.COLUMN2ROW0])) / + projectionMatrix[Matrix4.COLUMN0ROW0]; + + result.left = + (near * (projectionMatrix[Matrix4.COLUMN2ROW0] - 1)) / + projectionMatrix[Matrix4.COLUMN0ROW0]; + + result.top = + (near * (1 + projectionMatrix[Matrix4.COLUMN2ROW1])) / + projectionMatrix[Matrix4.COLUMN1ROW1]; + + result.bottom = + (near * (projectionMatrix[Matrix4.COLUMN2ROW1] - 1)) / + projectionMatrix[Matrix4.COLUMN1ROW1]; + + return result; +}; + function update(frustum) { //>>includeStart('debug', pragmas.debug); if ( diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index f1849eaddbd3..86efda73bf17 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -228,7 +228,7 @@ function Cesium3DTileset(options) { this._cullWithChildrenBounds = defaultValue( options.cullWithChildrenBounds, - true + true, ); this._allTilesAdditive = true; @@ -239,7 +239,7 @@ function Cesium3DTileset(options) { this._maximumScreenSpaceError = defaultValue( options.maximumScreenSpaceError, - 16 + 16, ); this._memoryAdjustedScreenSpaceError = this._maximumScreenSpaceError; @@ -250,13 +250,13 @@ function Cesium3DTileset(options) { const maximumCacheOverflowBytes = defaultValue( options.maximumCacheOverflowBytes, - 512 * 1024 * 1024 + 512 * 1024 * 1024, ); //>>includeStart('debug', pragmas.debug); Check.typeOf.number.greaterThanOrEquals( "maximumCacheOverflowBytes", maximumCacheOverflowBytes, - 0 + 0, ); //>>includeEnd('debug'); this._maximumCacheOverflowBytes = maximumCacheOverflowBytes; @@ -293,7 +293,7 @@ function Cesium3DTileset(options) { reverseScreenSpaceError: Number.MAX_VALUE, }; this._heatmap = new Cesium3DTilesetHeatmap( - options.debugHeatmapTilePropertyName + options.debugHeatmapTilePropertyName, ); /** @@ -304,7 +304,7 @@ function Cesium3DTileset(options) { */ this.cullRequestsWhileMoving = defaultValue( options.cullRequestsWhileMoving, - true + true, ); this._cullRequestsWhileMoving = false; @@ -316,7 +316,7 @@ function Cesium3DTileset(options) { */ this.cullRequestsWhileMovingMultiplier = defaultValue( options.cullRequestsWhileMovingMultiplier, - 60.0 + 60.0, ); /** @@ -328,7 +328,7 @@ function Cesium3DTileset(options) { this.progressiveResolutionHeightFraction = CesiumMath.clamp( defaultValue(options.progressiveResolutionHeightFraction, 0.3), 0.0, - 0.5 + 0.5, ); /** @@ -354,12 +354,12 @@ function Cesium3DTileset(options) { this._vectorClassificationOnly = defaultValue( options.vectorClassificationOnly, - false + false, ); this._vectorKeepDecodedPositions = defaultValue( options.vectorKeepDecodedPositions, - false + false, ); /** @@ -378,7 +378,7 @@ function Cesium3DTileset(options) { */ this.preloadFlightDestinations = defaultValue( options.preloadFlightDestinations, - true + true, ); this._pass = undefined; // Cesium3DTilePass @@ -394,7 +394,7 @@ function Cesium3DTileset(options) { */ this.dynamicScreenSpaceError = defaultValue( options.dynamicScreenSpaceError, - true + true, ); /** @@ -407,12 +407,12 @@ function Cesium3DTileset(options) { */ this.foveatedScreenSpaceError = defaultValue( options.foveatedScreenSpaceError, - true + true, ); this._foveatedConeSize = defaultValue(options.foveatedConeSize, 0.1); this._foveatedMinimumScreenSpaceErrorRelaxation = defaultValue( options.foveatedMinimumScreenSpaceErrorRelaxation, - 0.0 + 0.0, ); /** @@ -423,7 +423,7 @@ function Cesium3DTileset(options) { */ this.foveatedInterpolationCallback = defaultValue( options.foveatedInterpolationCallback, - CesiumMath.lerp + CesiumMath.lerp, ); /** @@ -460,7 +460,7 @@ function Cesium3DTileset(options) { */ this.dynamicScreenSpaceErrorDensity = defaultValue( options.dynamicScreenSpaceErrorDensity, - 2.0e-4 + 2.0e-4, ); /** @@ -481,7 +481,7 @@ function Cesium3DTileset(options) { */ this.dynamicScreenSpaceErrorFactor = defaultValue( options.dynamicScreenSpaceErrorFactor, - 24.0 + 24.0, ); /** @@ -495,7 +495,7 @@ function Cesium3DTileset(options) { */ this.dynamicScreenSpaceErrorHeightFalloff = defaultValue( options.dynamicScreenSpaceErrorHeightFalloff, - 0.25 + 0.25, ); // Updated based on the camera position and direction @@ -758,7 +758,7 @@ function Cesium3DTileset(options) { */ this.skipScreenSpaceErrorFactor = defaultValue( options.skipScreenSpaceErrorFactor, - 16 + 16, ); /** @@ -785,7 +785,7 @@ function Cesium3DTileset(options) { */ this.immediatelyLoadDesiredLevelOfDetail = defaultValue( options.immediatelyLoadDesiredLevelOfDetail, - false + false, ); /** @@ -861,7 +861,7 @@ function Cesium3DTileset(options) { */ this.splitDirection = defaultValue( options.splitDirection, - SplitDirection.NONE + SplitDirection.NONE, ); /** @@ -875,7 +875,7 @@ function Cesium3DTileset(options) { if (defined(options.disableCollision)) { deprecationWarning( "Cesium3DTileset options.disableCollision", - "Cesium3DTileset options.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use options.enableCollision instead." + "Cesium3DTileset options.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use options.enableCollision instead.", ); this.enableCollision = !options.disableCollision; @@ -912,7 +912,7 @@ function Cesium3DTileset(options) { this._enableDebugWireframe = defaultValue( options.enableDebugWireframe, - false + false, ); /** @@ -930,7 +930,7 @@ function Cesium3DTileset(options) { if (this.debugWireframe === true && this._enableDebugWireframe === false) { oneTimeWarning( "tileset-debug-wireframe-ignored", - "enableDebugWireframe must be set to true in the Cesium3DTileset constructor, otherwise debugWireframe will be ignored." + "enableDebugWireframe must be set to true in the Cesium3DTileset constructor, otherwise debugWireframe will be ignored.", ); } @@ -947,7 +947,7 @@ function Cesium3DTileset(options) { */ this.debugShowBoundingVolume = defaultValue( options.debugShowBoundingVolume, - false + false, ); /** @@ -962,7 +962,7 @@ function Cesium3DTileset(options) { */ this.debugShowContentBoundingVolume = defaultValue( options.debugShowContentBoundingVolume, - false + false, ); /** @@ -976,7 +976,7 @@ function Cesium3DTileset(options) { */ this.debugShowViewerRequestVolume = defaultValue( options.debugShowViewerRequestVolume, - false + false, ); /** @@ -999,7 +999,7 @@ function Cesium3DTileset(options) { */ this.debugShowGeometricError = defaultValue( options.debugShowGeometricError, - false + false, ); /** @@ -1013,7 +1013,7 @@ function Cesium3DTileset(options) { */ this.debugShowRenderingStatistics = defaultValue( options.debugShowRenderingStatistics, - false + false, ); /** @@ -1062,7 +1062,7 @@ function Cesium3DTileset(options) { let instanceFeatureIdLabel = defaultValue( options.instanceFeatureIdLabel, - "instanceFeatureId_0" + "instanceFeatureId_0", ); if (typeof instanceFeatureIdLabel === "number") { instanceFeatureIdLabel = `instanceFeatureId_${instanceFeatureIdLabel}`; @@ -1096,7 +1096,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { get: function () { deprecationWarning( "Cesium3DTileset.disableCollision", - "Cesium3DTileset.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use Cesium3DTileset.enableCollision instead." + "Cesium3DTileset.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use Cesium3DTileset.enableCollision instead.", ); return !this.enableCollision; @@ -1104,7 +1104,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { set: function (value) { deprecationWarning( "Cesium3DTileset.disableCollision", - "Cesium3DTileset.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use Cesium3DTileset.enableCollision instead." + "Cesium3DTileset.disableCollision has been deprecated in CesiumJS 1.115. It will be removed in CesiumJS 1.116. Use Cesium3DTileset.enableCollision instead.", ); this.enableCollision = !value; @@ -1230,7 +1230,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { get: function () { deprecationWarning( "Cesium3DTileset.basePath", - "Cesium3DTileset.basePath has been deprecated. All tiles are relative to the url of the tileset JSON file that contains them. Use the url property instead." + "Cesium3DTileset.basePath has been deprecated. All tiles are relative to the url of the tileset JSON file that contains them. Use the url property instead.", ); return this._basePath; }, @@ -1446,7 +1446,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { Check.typeOf.number.greaterThanOrEquals( "maximumScreenSpaceError", value, - 0 + 0, ); //>>includeEnd('debug'); @@ -1681,7 +1681,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { Matrix4.multiply( this.root.computedTransform, this._initialClippingPlanesOriginMatrix, - this._clippingPlanesOriginMatrix + this._clippingPlanesOriginMatrix, ); this._clippingPlanesOriginMatrixDirty = false; } @@ -1807,12 +1807,12 @@ Object.defineProperties(Cesium3DTileset.prototype, { Check.typeOf.number.greaterThanOrEquals( "foveatedMinimumScreenSpaceErrorRelaxation", value, - 0.0 + 0.0, ); Check.typeOf.number.lessThanOrEquals( "foveatedMinimumScreenSpaceErrorRelaxation", value, - this.maximumScreenSpaceError + this.maximumScreenSpaceError, ); //>>includeEnd('debug'); @@ -2086,7 +2086,7 @@ Cesium3DTileset.fromUrl = async function (url, options) { const tilesetJson = await Cesium3DTileset.loadJson(resource); const metadataExtension = await processMetadataExtension( resource, - tilesetJson + tilesetJson, ); const tileset = new Cesium3DTileset(options); @@ -2124,27 +2124,25 @@ Cesium3DTileset.fromUrl = async function (url, options) { // the tile transform and model matrix at run time const boundingVolume = tileset._root.createBoundingVolume( tilesetJson.root.boundingVolume, - Matrix4.IDENTITY + Matrix4.IDENTITY, ); const clippingPlanesOrigin = boundingVolume.boundingSphere.center; // If this origin is above the surface of the earth // we want to apply an ENU orientation as our best guess of orientation. // Otherwise, we assume it gets its position/orientation completely from the // root tile transform and the tileset's model matrix - const originCartographic = tileset._ellipsoid.cartesianToCartographic( - clippingPlanesOrigin - ); + const originCartographic = + tileset._ellipsoid.cartesianToCartographic(clippingPlanesOrigin); if ( defined(originCartographic) && originCartographic.height > ApproximateTerrainHeights._defaultMinTerrainHeight ) { - tileset._initialClippingPlanesOriginMatrix = Transforms.eastNorthUpToFixedFrame( - clippingPlanesOrigin - ); + tileset._initialClippingPlanesOriginMatrix = + Transforms.eastNorthUpToFixedFrame(clippingPlanesOrigin); } tileset._clippingPlanesOriginMatrix = Matrix4.clone( - tileset._initialClippingPlanesOriginMatrix + tileset._initialClippingPlanesOriginMatrix, ); return tileset; @@ -2180,7 +2178,7 @@ Cesium3DTileset.prototype.makeStyleDirty = function () { Cesium3DTileset.prototype.loadTileset = function ( resource, tilesetJson, - parentTile + parentTile, ) { const asset = tilesetJson.asset; if (!defined(asset)) { @@ -2192,7 +2190,7 @@ Cesium3DTileset.prototype.loadTileset = function ( asset.version !== "1.1" ) { throw new RuntimeError( - "The tileset must be 3D Tiles version 0.0, 1.0, or 1.1" + "The tileset must be 3D Tiles version 0.0, 1.0, or 1.1", ); } @@ -2275,7 +2273,7 @@ function makeTile(tileset, baseResource, tileHeader, parentTile) { const implicitTileset = new ImplicitTileset( baseResource, tileHeader, - metadataSchema + metadataSchema, ); const rootCoordinates = new ImplicitTileCoordinates({ subdivisionScheme: implicitTileset.subdivisionScheme, @@ -2391,21 +2389,21 @@ function updateDynamicScreenSpaceError(tileset, frameState) { // Transform camera position and direction into the local coordinate system of the tileset const transformLocal = Matrix4.inverseTransformation( root.computedTransform, - scratchMatrix + scratchMatrix, ); const ellipsoid = frameState.mapProjection.ellipsoid; const boundingVolume = tileBoundingVolume.boundingVolume; const centerLocal = Matrix4.multiplyByPoint( transformLocal, boundingVolume.center, - scratchCenter + scratchCenter, ); if (Cartesian3.magnitude(centerLocal) > ellipsoid.minimumRadius) { // The tileset is defined in WGS84. Approximate the minimum and maximum height. const centerCartographic = Cartographic.fromCartesian( centerLocal, ellipsoid, - scratchCartographic + scratchCartographic, ); up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal); direction = camera.directionWC; @@ -2417,13 +2415,13 @@ function updateDynamicScreenSpaceError(tileset, frameState) { const positionLocal = Matrix4.multiplyByPoint( transformLocal, camera.positionWC, - scratchPosition + scratchPosition, ); up = Cartesian3.UNIT_Z; direction = Matrix4.multiplyByPointAsVector( transformLocal, camera.directionWC, - scratchDirection + scratchDirection, ); direction = Cartesian3.normalize(direction, direction); height = positionLocal.z; @@ -2433,7 +2431,7 @@ function updateDynamicScreenSpaceError(tileset, frameState) { const halfHeightVector = Matrix3.getColumn( boundingVolume.halfAxes, 2, - scratchHalfHeight + scratchHalfHeight, ); const halfHeight = Cartesian3.magnitude(halfHeightVector); minimumHeight = centerLocal.z - halfHeight; @@ -2455,7 +2453,7 @@ function updateDynamicScreenSpaceError(tileset, frameState) { const t = CesiumMath.clamp( (height - heightClose) / (heightFar - heightClose), 0.0, - 1.0 + 1.0, ); // Increase density as the camera tilts towards the horizon @@ -2563,7 +2561,7 @@ Cesium3DTileset.prototype.prePassesUpdate = function (frameState) { } this._timeSinceLoad = Math.max( JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, - 0.0 + 0.0, ); if (this.dynamicScreenSpaceError) { @@ -2689,12 +2687,12 @@ function processUpdateHeight(tileset, tile, frameState) { const ellipsoid = callbackData.ellipsoid; const positionCartographic = Cartographic.clone( callbackData.positionCartographic, - scratchUpdateHeightCartographic + scratchUpdateHeightCartographic, ); const centerCartographic = Cartographic.fromCartesian( boundingSphere.center, ellipsoid, - scratchUpdateHeightCartographic2 + scratchUpdateHeightCartographic2, ); // This can be undefined when the bounding sphere is at the origin @@ -2705,7 +2703,7 @@ function processUpdateHeight(tileset, tile, frameState) { const position = Cartographic.toCartesian( positionCartographic, ellipsoid, - scratchUpdateHeightCartesian + scratchUpdateHeightCartesian, ); if ( Cartesian3.distance(position, boundingSphere.center) <= @@ -2769,7 +2767,7 @@ function increaseScreenSpaceError(tileset) { "increase-screenSpaceError", `The tiles needed to meet maximumScreenSpaceError would use more memory than allocated for this tileset. The tileset will be rendered with a larger screen space error (see memoryAdjustedScreenSpaceError). - Consider using larger values for cacheBytes and maximumCacheOverflowBytes.` + Consider using larger values for cacheBytes and maximumCacheOverflowBytes.`, ); //>>includeEnd('debug'); @@ -2784,7 +2782,7 @@ function increaseScreenSpaceError(tileset) { function decreaseScreenSpaceError(tileset) { tileset._memoryAdjustedScreenSpaceError = Math.max( tileset.memoryAdjustedScreenSpaceError / 1.02, - tileset.maximumScreenSpaceError + tileset.maximumScreenSpaceError, ); } @@ -2825,7 +2823,7 @@ function computeTileLabelPosition(tile) { normal = Cartesian3.multiplyByScalar( normal, 0.75 * radius, - scratchCartesian + scratchCartesian, ); position = Cartesian3.add(normal, center, scratchCartesian); } @@ -2871,10 +2869,10 @@ function addTileDebugLabel(tile, tileset, position) { if (tileset.debugShowMemoryUsage) { labelString += `\nTexture Memory: ${formatMemoryString( - tile.content.texturesByteLength + tile.content.texturesByteLength, )}`; labelString += `\nGeometry Memory: ${formatMemoryString( - tile.content.geometryByteLength + tile.content.geometryByteLength, )}`; attributes += 2; } @@ -2924,7 +2922,7 @@ function updateTileDebugLabels(tileset, frameState) { const label = addTileDebugLabel( tileset.debugPickedTile, tileset, - position + position, ); label.pixelOffset = new Cartesian2(15, -15); // Offset to avoid picking the label. } @@ -3064,7 +3062,7 @@ function updateTiles(tileset, frameState, passOptions) { frameState, numberOfInitialCommands, tileset.pointCloudShading, - tileset.boundingSphere + tileset.boundingSphere, ); } @@ -3168,7 +3166,7 @@ function raiseLoadProgressEvent(tileset, frameState) { frameState.afterRender.push(function () { tileset.loadProgress.raiseEvent( numberOfPendingRequests, - numberOfTilesProcessing + numberOfTilesProcessing, ); return true; @@ -3230,12 +3228,12 @@ function detectModelMatrixChanged(tileset, frameState) { tileset._updatedModelMatrixFrame = frameState.frameNumber; tileset._modelMatrixChanged = !Matrix4.equals( tileset.modelMatrix, - tileset._previousModelMatrix + tileset._previousModelMatrix, ); if (tileset._modelMatrixChanged) { tileset._previousModelMatrix = Matrix4.clone( tileset.modelMatrix, - tileset._previousModelMatrix + tileset._previousModelMatrix, ); } } @@ -3325,7 +3323,7 @@ function createCredits(tileset) { credits.forEach( (credit) => (credit.showOnScreen = - credit.showOnScreen || tileset._showCreditsOnScreen) + credit.showOnScreen || tileset._showCreditsOnScreen), ); tileset._credits = credits; @@ -3364,7 +3362,7 @@ Cesium3DTileset.prototype.update = function (frameState) { */ Cesium3DTileset.prototype.updateForPass = function ( frameState, - tilesetPassState + tilesetPassState, ) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("frameState", frameState); @@ -3396,7 +3394,7 @@ Cesium3DTileset.prototype.updateForPass = function ( const commandList = defaultValue( tilesetPassState.commandList, - originalCommandList + originalCommandList, ); const commandStart = commandList.length; @@ -3404,7 +3402,7 @@ Cesium3DTileset.prototype.updateForPass = function ( frameState.camera = defaultValue(tilesetPassState.camera, originalCamera); frameState.cullingVolume = defaultValue( tilesetPassState.cullingVolume, - originalCullingVolume + originalCullingVolume, ); const passStatistics = this._statisticsPerPass[pass]; @@ -3415,7 +3413,7 @@ Cesium3DTileset.prototype.updateForPass = function ( this, frameState, passStatistics, - passOptions + passOptions, ); } @@ -3527,7 +3525,7 @@ Cesium3DTileset.checkSupportedExtensions = function (extensionsRequired) { for (let i = 0; i < extensionsRequired.length; i++) { if (!Cesium3DTileset.supportedExtensions[extensionsRequired[i]]) { throw new RuntimeError( - `Unsupported 3D Tiles Extension: ${extensionsRequired[i]}` + `Unsupported 3D Tiles Extension: ${extensionsRequired[i]}`, ); } } @@ -3564,7 +3562,7 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { const ray = scratchGetHeightRay; const position = ellipsoid.cartographicToCartesian( cartographic, - ray.direction + ray.direction, ); Cartesian3.normalize(ray.direction, ray.direction); @@ -3573,7 +3571,7 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { ray.origin = Cartesian3.multiplyByScalar( ray.direction, -2 * ellipsoid.maximumRadius, - ray.origin + ray.origin, ); const intersection = this.pick(ray, scene.frameState, scratchIntersection); @@ -3583,7 +3581,7 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { return ellipsoid.cartesianToCartographic( intersection, - scratchGetHeightCartographic + scratchGetHeightCartographic, )?.height; }; @@ -3602,7 +3600,7 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { Cesium3DTileset.prototype.updateHeight = function ( cartographic, callback, - ellipsoid + ellipsoid, ) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); @@ -3659,7 +3657,7 @@ Cesium3DTileset.prototype.pick = function (ray, frameState, result) { const boundsIntersection = IntersectionTests.raySphere( ray, tile.contentBoundingVolume.boundingSphere, - scratchSphereIntersection + scratchSphereIntersection, ); if (!defined(boundsIntersection) || !defined(tile.content)) { continue; @@ -3672,11 +3670,11 @@ Cesium3DTileset.prototype.pick = function (ray, frameState, result) { candidates.sort((a, b) => { const aDist = BoundingSphere.distanceSquaredTo( a.contentBoundingVolume.boundingSphere, - ray.origin + ray.origin, ); const bDist = BoundingSphere.distanceSquaredTo( b.contentBoundingVolume.boundingSphere, - ray.origin + ray.origin, ); return aDist - bDist; @@ -3688,7 +3686,7 @@ Cesium3DTileset.prototype.pick = function (ray, frameState, result) { const candidate = tile.content.pick( ray, frameState, - scratchPickIntersection + scratchPickIntersection, ); if (defined(candidate)) { diff --git a/packages/engine/Source/Scene/I3SDataProvider.js b/packages/engine/Source/Scene/I3SDataProvider.js index c4cd9cd4578e..89e87643ef26 100644 --- a/packages/engine/Source/Scene/I3SDataProvider.js +++ b/packages/engine/Source/Scene/I3SDataProvider.js @@ -149,14 +149,14 @@ function I3SDataProvider(options) { this._showFeatures = defaultValue(options.showFeatures, false); this._adjustMaterialAlphaMode = defaultValue( options.adjustMaterialAlphaMode, - false + false, ); this._applySymbology = defaultValue(options.applySymbology, false); this._calculateNormals = defaultValue(options.calculateNormals, false); this._cesium3dTilesetOptions = defaultValue( options.cesium3dTilesetOptions, - defaultValue.EMPTY_OBJECT + defaultValue.EMPTY_OBJECT, ); this._layers = []; @@ -452,7 +452,7 @@ async function addLayers(provider, data, options) { provider, buildingLayerUrl, data.sublayers[i], - provider + provider, ); promises.push(promise); } @@ -476,7 +476,7 @@ async function addLayers(provider, data, options) { data.fullExtent.xmin, data.fullExtent.ymin, data.fullExtent.xmax, - data.fullExtent.ymax + data.fullExtent.ymax, ); provider._layersExtent.push(extent); } @@ -500,7 +500,7 @@ async function addLayers(provider, data, options) { } else { // Filter other scene layer types out console.log( - `${data.layerType} layer ${data.name} is skipped as not supported.` + `${data.layerType} layer ${data.name} is skipped as not supported.`, ); } } @@ -571,7 +571,7 @@ I3SDataProvider.fromUrl = async function (url, options) { const layerPromises = []; for (let i = 0; i < provider._layers.length; i++) { layerPromises.push( - provider._layers[i].load(options.cesium3dTilesetOptions) + provider._layers[i].load(options.cesium3dTilesetOptions), ); } @@ -646,7 +646,7 @@ I3SDataProvider.prototype._binarizeGltf = function (rawGltf) { chunkData: new Uint8Array( binaryGltfData.buffer, 20, - rawGltfData.byteLength + rawGltfData.byteLength, ), }; @@ -680,7 +680,7 @@ function getCoveredTiles(terrainProvider, extent) { const minCornerXY = tilingScheme.positionToTileXY(topLeftCorner, maxLevel); const maxCornerXY = tilingScheme.positionToTileXY( bottomRightCorner, - maxLevel + maxLevel, ); // Get all the tiles in between @@ -711,7 +711,7 @@ function getCoveredTiles(terrainProvider, extent) { const requestPromise = tileRequest.terrainProvider.requestTileGeometry( tileRequest.x, tileRequest.y, - tileRequest.level + tileRequest.level, ); tilePromises.push(requestPromise); @@ -739,7 +739,7 @@ function getCoveredTiles(terrainProvider, extent) { nativeExtent: tilingScheme.tileXYToNativeRectangle( options.x, options.y, - options.level + options.level, ), height: heightMap._height, width: heightMap._width, @@ -772,12 +772,12 @@ async function loadGeoidData(provider) { try { const heightMaps = await getCoveredTiles( geoidTerrainProvider, - provider._extent + provider._extent, ); provider._geoidDataList = heightMaps; } catch (error) { console.log( - "Error retrieving Geoid Terrain tiles - no geoid conversion will be performed." + "Error retrieving Geoid Terrain tiles - no geoid conversion will be performed.", ); } } diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 02acfe64cbd3..c2290ba86cc4 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -72,6 +72,7 @@ import SunLight from "./SunLight.js"; import SunPostProcess from "./SunPostProcess.js"; import TweenCollection from "./TweenCollection.js"; import View from "./View.js"; +import VRPose from "./VRPose.js"; import DebugInspector from "./DebugInspector.js"; import VoxelCell from "./VoxelCell.js"; import VoxelPrimitive from "./VoxelPrimitive.js"; @@ -154,7 +155,7 @@ function Scene(options) { this._frameState = new FrameState( context, new CreditDisplay(creditContainer, " • ", creditViewport), - this._jobScheduler + this._jobScheduler, ); this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false); this._removeCreditContainer = !hasCreditContainer; @@ -564,7 +565,7 @@ function Scene(options) { this.invertClassificationColor = Color.clone(Color.WHITE); this._actualInvertClassificationColor = Color.clone( - this._invertClassificationColor + this._invertClassificationColor, ); this._invertClassification = new InvertClassification(); @@ -622,11 +623,13 @@ function Scene(options) { usePostProcess: false, usePostProcessSelected: false, useWebVR: false, + useWebXR: true, }; this._useWebVR = false; - this._cameraVR = undefined; - this._aspectRatioVR = undefined; + this._useWebXR = true; + this._webXR = undefined; + this._poseVR = undefined; /** * When true, rendering a frame will only occur when needed as determined by changes within the scene. @@ -660,17 +663,19 @@ function Scene(options) { */ this.maximumRenderTimeChange = defaultValue( options.maximumRenderTimeChange, - 0.0 + 0.0, ); this._lastRenderTime = undefined; this._frameRateMonitor = undefined; - this._removeRequestListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( - requestRenderAfterFrame(this) - ); - this._removeTaskProcessorListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener( - requestRenderAfterFrame(this) - ); + this._removeRequestListenerCallback = + RequestScheduler.requestCompletedEvent.addEventListener( + requestRenderAfterFrame(this), + ); + this._removeTaskProcessorListenerCallback = + TaskProcessor.taskCompletedEvent.addEventListener( + requestRenderAfterFrame(this), + ); this._removeGlobeCallbacks = []; this._removeTerrainProviderReadyListener = undefined; @@ -678,7 +683,7 @@ function Scene(options) { 0, 0, context.drawingBufferWidth, - context.drawingBufferHeight + context.drawingBufferHeight, ); const camera = new Camera(this); @@ -751,13 +756,13 @@ function updateGlobeListeners(scene, globe) { if (defined(globe)) { removeGlobeCallbacks.push( globe.imageryLayersUpdatedEvent.addEventListener( - requestRenderAfterFrame(scene) - ) + requestRenderAfterFrame(scene), + ), ); removeGlobeCallbacks.push( globe.terrainProviderChanged.addEventListener( - requestRenderAfterFrame(scene) - ) + requestRenderAfterFrame(scene), + ), ); } scene._removeGlobeCallbacks = removeGlobeCallbacks; @@ -1368,7 +1373,7 @@ Object.defineProperties(Scene.prototype, { //>>includeStart('debug', pragmas.debug); if (this.scene3DOnly && value !== SceneMode.SCENE3D) { throw new DeveloperError( - "Only SceneMode.SCENE3D is valid when scene3DOnly is true." + "Only SceneMode.SCENE3D is valid when scene3DOnly is true.", ); } //>>includeEnd('debug'); @@ -1381,7 +1386,7 @@ Object.defineProperties(Scene.prototype, { //>>includeStart('debug', pragmas.debug); } else { throw new DeveloperError( - "value must be a valid SceneMode enumeration." + "value must be a valid SceneMode enumeration.", ); //>>includeEnd('debug'); } @@ -1417,7 +1422,7 @@ Object.defineProperties(Scene.prototype, { /** * When true, splits the scene into two viewports with steroscopic views for the left and right eyes. - * Used for cardboard and WebVR. + * Used for cardboard, WebVR and WebXR. * @memberof Scene.prototype * @type {boolean} * @default false @@ -1430,35 +1435,89 @@ Object.defineProperties(Scene.prototype, { //>>includeStart('debug', pragmas.debug); if (this.camera.frustum instanceof OrthographicFrustum) { throw new DeveloperError( - "VR is unsupported with an orthographic projection." + "VR is unsupported with an orthographic projection.", ); } //>>includeEnd('debug'); this._useWebVR = value; if (this._useWebVR) { this._frameState.creditDisplay.container.style.visibility = "hidden"; - this._cameraVR = new Camera(this); + this._poseVR = new VRPose(this); if (!defined(this._deviceOrientationCameraController)) { - this._deviceOrientationCameraController = new DeviceOrientationCameraController( - this - ); + this._deviceOrientationCameraController = + new DeviceOrientationCameraController(this); } - - this._aspectRatioVR = this.camera.frustum.aspectRatio; } else { this._frameState.creditDisplay.container.style.visibility = "visible"; - this._cameraVR = undefined; + this._poseVR = undefined; this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy(); - - this.camera.frustum.aspectRatio = this._aspectRatioVR; - this.camera.frustum.xOffset = 0.0; } }, }, + /** + * When false, ignores the presence of the WebXR API and falls back to WebVR behavior. + * @memberof Scene.prototype + * @type {boolean} + * @default true + */ + useWebXR: { + get: function () { + return this._useWebXR; + }, + set: function (value) { + this._useWebXR = value; + }, + }, + + /** + * An object from which WebXR information is pulled. + *

+ * Member refSpace should be set when + * XRSession:requestReferenceSpace is resolved. If + * refSpace is not set, an inline reference space will + * be assumed. + *

+ *

+ * Member frame must be set with the + * XRFrame parameter received at the callback scheduled + * with XRSession:requestAnimationFrame. + *

+ * + * @memberof Scene.prototype + * @type {object} + * @default undefined + * + * @see Scene#useWebVR + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestReferenceSpace|requestReferenceSpace} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestAnimationFrame|requestAnimationFrame} + * + * @example + * const scene = new Cesium.Scene({ canvas: canvas }); + * scene.useWebVR = true; + * scene.webXRContext = { refSpace: undefined, frame: undefined }; + * navigator.xr.requestSession("immersive-vr").then((xrSession) => { + * xrSession.requestReferenceSpace("local").then((xrRefSpace) => { + * scene.webXRContext.refSpace = xrRefSpace; + * xrSession.requestAnimationFrame((time, xrFrame) => { + * scene.webXRContext.frame = xrFrame; + * scene.render(); + * }); + * }); + * }); + */ + webXRContext: { + get: function () { + return this._webXR; + }, + set: function (value) { + this._webXR = value; + }, + }, + /** * Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction. * @memberof Scene.prototype @@ -1503,7 +1562,7 @@ Object.defineProperties(Scene.prototype, { //>>includeStart('debug', pragmas.debug); if (!defined(value) || value < 0.0) { throw new DeveloperError( - "minimumDisableDepthTestDistance must be greater than or equal to 0.0." + "minimumDisableDepthTestDistance must be greater than or equal to 0.0.", ); } //>>includeEnd('debug'); @@ -1698,7 +1757,7 @@ function updateDerivedCommands(scene, command, shadowsDirty) { scene, command, context, - derivedCommands.picking + derivedCommands.picking, ); } @@ -1707,7 +1766,7 @@ function updateDerivedCommands(scene, command, shadowsDirty) { scene, command, context, - derivedCommands.depth + derivedCommands.depth, ); } @@ -1717,7 +1776,7 @@ function updateDerivedCommands(scene, command, shadowsDirty) { derivedCommands.hdr = DerivedCommand.createHdrCommand( command, context, - derivedCommands.hdr + derivedCommands.hdr, ); command = derivedCommands.hdr.command; derivedCommands = command.derivedCommands; @@ -1729,7 +1788,7 @@ function updateDerivedCommands(scene, command, shadowsDirty) { command, shadowsDirty, context, - derivedCommands.shadows + derivedCommands.shadows, ); } @@ -1741,13 +1800,13 @@ function updateDerivedCommands(scene, command, shadowsDirty) { derivedCommands.oit.shadows = oit.createDerivedCommands( derivedCommands.shadows.receiveCommand, context, - derivedCommands.oit.shadows + derivedCommands.oit.shadows, ); } else { derivedCommands.oit = oit.createDerivedCommands( command, context, - derivedCommands.oit + derivedCommands.oit, ); } } @@ -1801,7 +1860,7 @@ Scene.prototype.updateDerivedCommands = function (command) { command, shadowsDirty, context, - derivedCommands.shadows + derivedCommands.shadows, ); } @@ -1809,12 +1868,12 @@ Scene.prototype.updateDerivedCommands = function (command) { derivedCommands.logDepth = DerivedCommand.createLogDepthCommand( command, context, - derivedCommands.logDepth + derivedCommands.logDepth, ); updateDerivedCommands( this, derivedCommands.logDepth.command, - shadowsDirty + shadowsDirty, ); } if (hasDerivedCommands || needsDerivedCommands) { @@ -1860,7 +1919,7 @@ function getOccluder(scene) { scratchOccluder = Occluder.fromBoundingSphere( scratchOccluderBoundingSphere, scene.camera.positionWC, - scratchOccluder + scratchOccluder, ); return scratchOccluder; } @@ -1905,11 +1964,12 @@ Scene.prototype.updateFrameState = function () { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); frameState.occluder = getOccluder(this); frameState.minimumTerrainHeight = 0.0; - frameState.minimumDisableDepthTestDistance = this._minimumDisableDepthTestDistance; + frameState.minimumDisableDepthTestDistance = + this._minimumDisableDepthTestDistance; frameState.invertClassification = this.invertClassification; frameState.useLogDepth = this._logDepthBuffer && @@ -1931,14 +1991,17 @@ Scene.prototype.updateFrameState = function () { globe._terrainExaggerationChanged = false; } frameState.verticalExaggeration = this.verticalExaggeration; - frameState.verticalExaggerationRelativeHeight = this.verticalExaggerationRelativeHeight; + frameState.verticalExaggerationRelativeHeight = + this.verticalExaggerationRelativeHeight; if ( defined(this._specularEnvironmentMapAtlas) && this._specularEnvironmentMapAtlas.ready ) { - frameState.specularEnvironmentMaps = this._specularEnvironmentMapAtlas.texture; - frameState.specularEnvironmentMapsMaximumLOD = this._specularEnvironmentMapAtlas.maximumMipmapLevel; + frameState.specularEnvironmentMaps = + this._specularEnvironmentMapAtlas.texture; + frameState.specularEnvironmentMapsMaximumLOD = + this._specularEnvironmentMapAtlas.maximumMipmapLevel; } else { frameState.specularEnvironmentMaps = undefined; frameState.specularEnvironmentMapsMaximumLOD = undefined; @@ -1948,7 +2011,7 @@ Scene.prototype.updateFrameState = function () { this._actualInvertClassificationColor = Color.clone( this.invertClassificationColor, - this._actualInvertClassificationColor + this._actualInvertClassificationColor, ); if (!InvertClassification.isTranslucencySupported(this._context)) { this._actualInvertClassificationColor.alpha = 1.0; @@ -1999,11 +2062,11 @@ let transformFrom2D = new Matrix4( 0.0, 0.0, 0.0, - 1.0 + 1.0, ); transformFrom2D = Matrix4.inverseTransformation( transformFrom2D, - transformFrom2D + transformFrom2D, ); function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) { @@ -2035,8 +2098,8 @@ function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) { new EllipsoidGeometry({ radii: new Cartesian3(radius, radius, radius), vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, - }) - ) + }), + ), ); scene._debugVolume = new Primitive({ @@ -2061,8 +2124,8 @@ function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) { BoxGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0), vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, - }) - ) + }), + ), ); scene._debugVolume = new Primitive({ @@ -2071,7 +2134,7 @@ function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) { modelMatrix: Matrix4.fromRotationTranslation( halfAxes, center, - new Matrix4() + new Matrix4(), ), attributes: { color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0), @@ -2163,7 +2226,7 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { scene._debugInspector.executeDebugShowFrustumsCommand( scene, command, - passState + passState, ); return; } @@ -2224,7 +2287,7 @@ function executeTranslucentCommandsBackToFront( executeFunction, passState, commands, - invertClassification + invertClassification, ) { const context = scene.context; @@ -2235,7 +2298,7 @@ function executeTranslucentCommandsBackToFront( invertClassification.unclassifiedCommand, scene, context, - passState + passState, ); } @@ -2250,7 +2313,7 @@ function executeTranslucentCommandsFrontToBack( executeFunction, passState, commands, - invertClassification + invertClassification, ) { const context = scene.context; @@ -2261,7 +2324,7 @@ function executeTranslucentCommandsFrontToBack( invertClassification.unclassifiedCommand, scene, context, - passState + passState, ); } @@ -2332,7 +2395,7 @@ function executeCommands(scene, passState) { environmentState.skyAtmosphereCommand, scene, context, - passState + passState, ); } @@ -2368,7 +2431,7 @@ function executeCommands(scene, passState) { executeFunction, passState, commands, - invertClassification + invertClassification, ) { view.globeDepth.prepareColorTextures(context); view.oit.executeCommands( @@ -2376,7 +2439,7 @@ function executeCommands(scene, passState) { executeFunction, passState, commands, - invertClassification + invertClassification, ); }; } @@ -2443,7 +2506,7 @@ function executeCommands(scene, passState) { executeCommand, globeTranslucencyFramebuffer, scene, - passState + passState, ); } else { for (j = 0; j < length; ++j) { @@ -2468,7 +2531,7 @@ function executeCommands(scene, passState) { executeCommand, globeTranslucencyFramebuffer, scene, - passState + passState, ); } else { for (j = 0; j < length; ++j) { @@ -2508,7 +2571,7 @@ function executeCommands(scene, passState) { context, passState, clearGlobeDepth, - globeDepth.depthStencilTexture + globeDepth.depthStencilTexture, ); } @@ -2575,7 +2638,7 @@ function executeCommands(scene, passState) { context, passState, clearGlobeDepth, - scene._invertClassification._fbo.getDepthStencilTexture() + scene._invertClassification._fbo.getDepthStencilTexture(), ); } @@ -2656,7 +2719,7 @@ function executeCommands(scene, passState) { executeCommand, passState, commands, - invertClassification + invertClassification, ); // Classification for translucent 3D Tiles @@ -2671,13 +2734,13 @@ function executeCommands(scene, passState) { executeCommand, passState, commands, - globeDepth.depthStencilTexture + globeDepth.depthStencilTexture, ); view.translucentTileClassification.executeClassificationCommands( scene, executeCommand, passState, - frustumCommands + frustumCommands, ); } @@ -2719,7 +2782,7 @@ function executeCommands(scene, passState) { executeIdCommand, globeTranslucencyFramebuffer, scene, - passState + passState, ); } else { for (j = 0; j < length; ++j) { @@ -2878,21 +2941,19 @@ function executeShadowMapCastCommands(scene) { command.derivedCommands.shadows.castCommands[i], scene, context, - pass.passState + pass.passState, ); } } } } -const scratchEyeTranslation = new Cartesian3(); - /** * @private */ Scene.prototype.updateAndExecuteCommands = function ( passState, - backgroundColor + backgroundColor, ) { const frameState = this._frameState; const mode = frameState.mode; @@ -2913,64 +2974,62 @@ Scene.prototype.updateAndExecuteCommands = function ( function executeWebVRCommands(scene, passState, backgroundColor) { const view = scene._view; - const camera = view.camera; const environmentState = scene._environmentState; - const renderTranslucentDepthForPick = - environmentState.renderTranslucentDepthForPick; - updateAndClearFramebuffers(scene, passState, backgroundColor); + const camera = view.camera; + const pose = scene._poseVR; + if (!pose.prepare(camera, passState.viewport)) { + // TODO: we should handle this gracefully. + // No need to do any further processing if a pose was not obtained. + return; + } + updateAndClearFramebuffers(scene, passState, backgroundColor); updateAndRenderPrimitives(scene); - view.createPotentiallyVisibleSet(scene); - executeComputeCommands(scene); - - if (!renderTranslucentDepthForPick) { + if (!environmentState.renderTranslucentDepthForPick) { executeShadowMapCastCommands(scene); } - // Based on Calculating Stereo pairs by Paul Bourke - // http://paulbourke.net/stereographics/stereorender/ - const viewport = passState.viewport; - viewport.x = 0; - viewport.y = 0; - viewport.width = viewport.width * 0.5; - - const savedCamera = Camera.clone(camera, scene._cameraVR); - savedCamera.frustum = camera.frustum; - - const near = camera.frustum.near; - const fo = near * defaultValue(scene.focalLength, 5.0); - const eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0); - const eyeTranslation = Cartesian3.multiplyByScalar( - savedCamera.right, - eyeSeparation * 0.5, - scratchEyeTranslation - ); - - camera.frustum.aspectRatio = viewport.width / viewport.height; - - const offset = (0.5 * eyeSeparation * near) / fo; - - Cartesian3.add(savedCamera.position, eyeTranslation, camera.position); - camera.frustum.xOffset = offset; - - executeCommands(scene, passState); - - viewport.x = viewport.width; - - Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position); - camera.frustum.xOffset = -offset; + pose.apply(() => { + executeCommands(scene, passState); + }); - executeCommands(scene, passState); + // Now manually blit into the framebuffer provided by WebXR, which + // is the framebuffer for the HMD's displays. - Camera.clone(savedCamera, camera); + // When the WebXR Emulator is used, xrLayer has no framebuffer. + if ( + pose.isWebXR && + defined(pose.xrLayer) && + defined(pose.xrLayer.framebuffer) + ) { + const xrLayer = pose.xrLayer; + const srcFb = passState.framebuffer; + const gl = srcFb._gl; + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, srcFb._framebuffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, xrLayer.framebuffer); + gl.blitFramebuffer( + 0, + 0, + xrLayer.framebufferWidth, + xrLayer.framebufferHeight, + 0, + 0, + xrLayer.framebufferWidth, + xrLayer.framebufferHeight, + gl.COLOR_BUFFER_BIT, + gl.NEAREST, + ); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); + } } const scratch2DViewportCartographic = new Cartographic( Math.PI, - CesiumMath.PI_OVER_TWO + CesiumMath.PI_OVER_TWO, ); const scratch2DViewportMaxCoord = new Cartesian3(); const scratch2DViewportSavedPosition = new Cartesian3(); @@ -2997,11 +3056,11 @@ function execute2DViewportCommands(scene, passState) { const position = Cartesian3.clone( camera.position, - scratch2DViewportSavedPosition + scratch2DViewportSavedPosition, ); const transform = Matrix4.clone( camera.transform, - scratch2DViewportCameraTransform + scratch2DViewportCameraTransform, ); const frustum = camera.frustum.clone(); @@ -3011,7 +3070,7 @@ function execute2DViewportCommands(scene, passState) { viewport, 0.0, 1.0, - scratch2DViewportTransform + scratch2DViewportTransform, ); const projectionMatrix = camera.frustum.projectionMatrix; @@ -3020,13 +3079,13 @@ function execute2DViewportCommands(scene, passState) { CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, - scratch2DViewportEyePoint + scratch2DViewportEyePoint, ); const windowCoordinates = Transforms.pointToGLWindowCoordinates( projectionMatrix, viewportTransformation, eyePoint, - scratch2DViewportWindowCoords + scratch2DViewportWindowCoords, ); windowCoordinates.x = Math.floor(windowCoordinates.x); @@ -3052,7 +3111,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3068,7 +3127,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3082,7 +3141,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3099,7 +3158,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3114,7 +3173,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3131,7 +3190,7 @@ function execute2DViewportCommands(scene, passState) { frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, - camera.upWC + camera.upWC, ); context.uniformState.update(frameState); @@ -3148,7 +3207,7 @@ function executeCommandsInViewport( firstViewport, scene, passState, - backgroundColor + backgroundColor, ) { const environmentState = scene._environmentState; const view = scene._view; @@ -3209,7 +3268,7 @@ Scene.prototype.updateEnvironment = function () { if (defined(skyAtmosphere)) { if (defined(globe)) { skyAtmosphere.setDynamicLighting( - DynamicAtmosphereLightingType.fromGlobeFlags(globe) + DynamicAtmosphereLightingType.fromGlobeFlags(globe), ); environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || @@ -3223,7 +3282,7 @@ Scene.prototype.updateEnvironment = function () { environmentState.skyAtmosphereCommand = skyAtmosphere.update( frameState, - globe + globe, ); if (defined(environmentState.skyAtmosphereCommand)) { this.updateDerivedCommands(environmentState.skyAtmosphereCommand); @@ -3289,12 +3348,12 @@ Scene.prototype.updateEnvironment = function () { environmentState.isSunVisible = this.isVisible( environmentState.sunDrawCommand, cullingVolume, - occluder + occluder, ); environmentState.isMoonVisible = this.isVisible( environmentState.moonCommand, cullingVolume, - occluder + occluder, ); const envMaps = this.specularEnvironmentMaps; @@ -3439,9 +3498,8 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { // Update globe depth rendering based on the current context and clear the globe depth framebuffer. // Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives. - const useGlobeDepthFramebuffer = (environmentState.useGlobeDepthFramebuffer = defined( - view.globeDepth - )); + const useGlobeDepthFramebuffer = (environmentState.useGlobeDepthFramebuffer = + defined(view.globeDepth)); if (useGlobeDepthFramebuffer) { view.globeDepth.update( context, @@ -3449,7 +3507,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { view.viewport, scene.msaaSamples, scene._hdr, - environmentState.clearGlobeDepth + environmentState.clearGlobeDepth, ); view.globeDepth.clear(context, passState, clearColor); } @@ -3464,7 +3522,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { passState, view.globeDepth.colorFramebufferManager, scene._hdr, - scene.msaaSamples + scene.msaaSamples, ); oit.clear(context, passState, clearColor); environmentState.useOIT = oit.isSupported(); @@ -3484,7 +3542,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { context, view.viewport, scene._hdr, - scene.msaaSamples + scene.msaaSamples, ); view.sceneFramebuffer.clear(context, passState, clearColor); @@ -3524,7 +3582,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { scene._invertClassification.update( context, scene.msaaSamples, - view.globeDepth.colorFramebufferManager + view.globeDepth.colorFramebufferManager, ); scene._invertClassification.clear(context, passState); @@ -3534,7 +3592,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { derivedCommands.oit = oit.createDerivedCommands( command, context, - derivedCommands.oit + derivedCommands.oit, ); } } else { @@ -3547,7 +3605,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { scene._hdr, view.viewport, context, - passState + passState, ); } } @@ -3602,7 +3660,7 @@ Scene.prototype.resolveFramebuffers = function (passState) { const idTexture = idFramebuffer.getColorTexture(0); const depthTexture = defaultValue( globeFramebuffer, - sceneFramebuffer + sceneFramebuffer, ).getDepthStencilTexture(); postProcess.execute(context, colorTexture, depthTexture, idTexture); postProcess.copy(context, defaultFramebuffer); @@ -3712,7 +3770,7 @@ const updateHeightScratchCartographic = new Cartographic(); Scene.prototype.updateHeight = function ( cartographic, callback, - heightReference + heightReference, ) { //>>includeStart('debug', pragmas.debug); Check.typeOf.func("callback", callback); @@ -3740,7 +3798,7 @@ Scene.prototype.updateHeight = function ( if (!ignoreTerrain && defined(this.globe)) { terrainRemoveCallback = this.globe._surface.updateHeight( cartographic, - callbackWrapper + callbackWrapper, ); } @@ -3758,7 +3816,7 @@ Scene.prototype.updateHeight = function ( const tilesetRemoveCallback = primitive.updateHeight( cartographic, callbackWrapper, - ellipsoid + ellipsoid, ); tilesetRemoveCallbacks[primitive.id] = tilesetRemoveCallback; }; @@ -3772,10 +3830,10 @@ Scene.prototype.updateHeight = function ( } const removeAddedListener = this.primitives.primitiveAdded.addEventListener( - createPrimitiveEventListener + createPrimitiveEventListener, ); - const removeRemovedListener = this.primitives.primitiveRemoved.addEventListener( - (primitive) => { + const removeRemovedListener = + this.primitives.primitiveRemoved.addEventListener((primitive) => { if (primitive.isDestroyed() || !primitive.isCesium3DTileset) { return; } @@ -3783,13 +3841,12 @@ Scene.prototype.updateHeight = function ( tilesetRemoveCallbacks[primitive.id](); } delete tilesetRemoveCallbacks[primitive.id]; - } - ); + }); const removeCallback = () => { terrainRemoveCallback = terrainRemoveCallback && terrainRemoveCallback(); Object.values(tilesetRemoveCallbacks).forEach((tilesetRemoveCallback) => - tilesetRemoveCallback() + tilesetRemoveCallback(), ); tilesetRemoveCallbacks = {}; removeAddedListener(); @@ -3854,7 +3911,7 @@ Scene.prototype.initializeFrame = function () { } this._globeHeight = updatedCartographic.height; - } + }, ); } this._cameraUnderground = isCameraUnderground(this); @@ -3890,7 +3947,7 @@ function updateDebugShowFramesPerSecond(scene, renderedThisFrame) { scene._performanceDisplay = scene._performanceDisplay && scene._performanceDisplay.destroy(); scene._performanceContainer.parentNode.removeChild( - scene._performanceContainer + scene._performanceContainer, ); } } @@ -4051,7 +4108,7 @@ Scene.prototype.render = function (time) { defined(this._lastRenderTime) ) { const difference = Math.abs( - JulianDate.secondsDifference(this._lastRenderTime, time) + JulianDate.secondsDifference(this._lastRenderTime, time), ); shouldRender = shouldRender || difference > this.maximumRenderTimeChange; } @@ -4065,7 +4122,7 @@ Scene.prototype.render = function (time) { const frameNumber = CesiumMath.incrementWrap( frameState.frameNumber, 15000000.0, - 1.0 + 1.0, ); updateFrameNumber(this, frameNumber, time); frameState.newFrame = true; @@ -4141,7 +4198,7 @@ Scene.prototype.requestRender = function () { Scene.prototype.clampLineWidth = function (width) { return Math.max( ContextLimits.minimumAliasedLineWidth, - Math.min(width, ContextLimits.maximumAliasedLineWidth) + Math.min(width, ContextLimits.maximumAliasedLineWidth), ); }; @@ -4204,7 +4261,7 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { this, windowPosition, width, - height + height, ); // Look up the keyframeNode containing this picked cell const tileIndex = 255 * voxelCoordinate[0] + voxelCoordinate[1]; @@ -4220,7 +4277,7 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { voxelPrimitive, tileIndex, sampleIndex, - keyframeNode + keyframeNode, ); }; @@ -4243,12 +4300,12 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { */ Scene.prototype.pickPositionWorldCoordinates = function ( windowPosition, - result + result, ) { return this._picking.pickPositionWorldCoordinates( this, windowPosition, - result + result, ); }; @@ -4325,7 +4382,7 @@ function updateRequestRenderModeDeferCheckPass(scene) { // Check if any ignored requests are ready to go (to wake rendering up again) scene.primitives.updateForPass( scene._frameState, - requestRenderModeDeferCheckPassState + requestRenderModeDeferCheckPassState, ); } @@ -4377,14 +4434,14 @@ Scene.prototype.drillPickFromRay = function ( ray, limit, objectsToExclude, - width + width, ) { return this._picking.drillPickFromRay( this, ray, limit, objectsToExclude, - width + width, ); }; @@ -4404,13 +4461,13 @@ Scene.prototype.drillPickFromRay = function ( Scene.prototype.pickFromRayMostDetailed = function ( ray, objectsToExclude, - width + width, ) { return this._picking.pickFromRayMostDetailed( this, ray, objectsToExclude, - width + width, ); }; @@ -4432,14 +4489,14 @@ Scene.prototype.drillPickFromRayMostDetailed = function ( ray, limit, objectsToExclude, - width + width, ) { return this._picking.drillPickFromRayMostDetailed( this, ray, limit, objectsToExclude, - width + width, ); }; @@ -4504,14 +4561,14 @@ Scene.prototype.clampToHeight = function ( cartesian, objectsToExclude, width, - result + result, ) { return this._picking.clampToHeight( this, cartesian, objectsToExclude, width, - result + result, ); }; @@ -4546,13 +4603,13 @@ Scene.prototype.clampToHeight = function ( Scene.prototype.sampleHeightMostDetailed = function ( positions, objectsToExclude, - width + width, ) { return this._picking.sampleHeightMostDetailed( this, positions, objectsToExclude, - width + width, ); }; @@ -4586,13 +4643,13 @@ Scene.prototype.sampleHeightMostDetailed = function ( Scene.prototype.clampToHeightMostDetailed = function ( cartesians, objectsToExclude, - width + width, ) { return this._picking.clampToHeightMostDetailed( this, cartesians, objectsToExclude, - width + width, ); }; @@ -4688,15 +4745,14 @@ function setTerrain(scene, terrain) { } // Otherwise, set a placeholder scene.globe.terrainProvider = undefined; - scene._removeTerrainProviderReadyListener = terrain.readyEvent.addEventListener( - (provider) => { + scene._removeTerrainProviderReadyListener = + terrain.readyEvent.addEventListener((provider) => { if (defined(scene) && defined(scene.globe)) { scene.globe.terrainProvider = provider; } scene._removeTerrainProviderReadyListener(); - } - ); + }); } /** @@ -4806,7 +4862,7 @@ Scene.prototype.destroy = function () { this._performanceDisplay = this._performanceDisplay && this._performanceDisplay.destroy(); this._performanceContainer.parentNode.removeChild( - this._performanceContainer + this._performanceContainer, ); } diff --git a/packages/engine/Source/Scene/VRPose.js b/packages/engine/Source/Scene/VRPose.js new file mode 100644 index 000000000000..f909dd304689 --- /dev/null +++ b/packages/engine/Source/Scene/VRPose.js @@ -0,0 +1,463 @@ +import BoundingRectangle from "../Core/BoundingRectangle.js"; +import Camera from "./Camera.js"; +import Cartesian3 from "../Core/Cartesian3.js"; +import defaultValue from "../Core/defaultValue.js"; +import defined from "../Core/defined.js"; +import DeveloperError from "../Core/DeveloperError.js"; +import Matrix4 from "../Core/Matrix4.js"; +import PerspectiveFrustum from "../Core/PerspectiveFrustum.js"; +import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js"; +import Scene from "./Scene.js"; + +function prepareViewportLegacy(pose, eye) { + const viewport = pose.viewports[eye]; + const width = pose._passStateViewport.width * 0.5; + + viewport.x = eye === "right" ? width : 0; + viewport.y = 0; + viewport.width = width; + viewport.height = pose._passStateViewport.height; +} + +function prepareViewportsLegacy(pose) { + if (!pose._paramsChanged) { + return; + } + + prepareViewportLegacy(pose, "left"); + prepareViewportLegacy(pose, "right"); +} + +// Based on Calculating Stereo pairs by Paul Bourke +// http://paulbourke.net/stereographics/stereorender/ +function preparePoseCameraParamsLegacy(pose, camera) { + const cameraParams = pose.cameraParams; + + const near = camera.frustum.near; + const fo = near * defaultValue(pose.scene.focalLength, 5.0); + const eyeSeparation = defaultValue(pose.scene.eyeSeparation, fo / 30.0); + const frustumXOffset = (0.5 * eyeSeparation * near) / fo; + + if ( + !pose._paramsChanged && + fo === cameraParams._fo && + eyeSeparation === cameraParams._eyeSeparation + ) { + return; + } + + const left = cameraParams.left; + left.translationOp = Cartesian3.add; + left.frustumXOffset = frustumXOffset; + left.frustumAspectRatio = + pose.viewports.left.width / pose.viewports.left.height; + + const right = cameraParams.right; + right.translationOp = Cartesian3.subtract; + right.frustumXOffset = -frustumXOffset; + right.frustumAspectRatio = + pose.viewports.right.width / pose.viewports.right.height; + + Cartesian3.multiplyByScalar( + camera.right, + eyeSeparation * 0.5, + cameraParams._eyeTranslation + ); + cameraParams._frustumNear = near; + cameraParams._fo = fo; + cameraParams._eyeSeparation = eyeSeparation; +} + +function applyPoseParamsToCameraLegacy(pose, params, camera) { + params.translationOp( + pose._savedCamera.position, + pose.cameraParams._eyeTranslation, + camera.position + ); + + camera.frustum.xOffset = params.frustumXOffset; + camera.frustum.aspectRatio = params.frustumAspectRatio; + + return true; +} + +function prepareViewportsXR(pose, xrPose) { + for (const xrView of xrPose.views) { + // Try to dynamically do viewport scaling based on the browser's + // recommendation. This allows the browser to designate a smaller + // framebuffer area for the render, degrading quality of the image + // but maintaining refresh rate in cases such as high system load. + // https://developer.mozilla.org/en-US/docs/Web/API/XRView/requestViewportScale + if (defined(xrView.requestViewPortScale)) { + xrView.requestViewportScale(xrView.recommendedViewportScale); + } + + const xrViewport = pose.xrLayer.getViewport(xrView); + const eyeViewport = pose.viewports[xrView.eye]; + eyeViewport.x = xrViewport.x; + eyeViewport.y = xrViewport.y; + eyeViewport.width = xrViewport.width; + eyeViewport.height = xrViewport.height; + } +} + +function arrayEquals(a, b) { + //>>includeStart('debug', pragmas.debug); + if (!defined(a) || !(a instanceof Float32Array) || a.length !== 16) { + throw new DeveloperError( + "a array must be a 16 element valid Float32Array." + ); + } + if (!defined(b) || !(b instanceof Float32Array) || b.length !== 16) { + throw new DeveloperError( + "b array must be a 16 element valid Float32Array." + ); + } + //>>includeEnd('debug'); + const differs = (value, idx) => value !== b[idx]; + return !a.some(differs); +} + +// convert a PerspectiveOffCenterFrustum to a PerspectiveFrustum asuming the +// pocFrustum represents a symetrical PerspectiveFrustum +function convertPerspectiveOffCenterFrustum(pocFrustum, result) { + if (!defined(result)) { + result = new PerspectiveFrustum(); + } + + // Calculate the half width and half height of the frustum at the near plane + const halfNearWidth = (pocFrustum.right - pocFrustum.left) / 2; + const halfNearHeight = (pocFrustum.top - pocFrustum.bottom) / 2; + + // Calculate the center position of the frustum's base at the near plane + const centerX = (pocFrustum.right + pocFrustum.left) / 2; + const centerY = (pocFrustum.top + pocFrustum.bottom) / 2; + + // Calculate the field of view (FOV) using the half height at the near plane + const fov = 2 * Math.atan(halfNearHeight / pocFrustum.near); + + const aspectRatio = halfNearWidth / halfNearHeight; + + result.aspectRatio = aspectRatio; + result.fov = fov; + result.near = pocFrustum.near; + result.far = pocFrustum.far; + result.xOffset = centerX; + result.yOffset = centerY; + + return result; +} + +function prepareCameraParamsXR(camera, xrPose, xrView, params) { + // Get the delta of Pose - View transformations (delta(A,B) = A_inv * B) + const xrPoseTransform = Matrix4.fromArray( + xrPose.transform.inverse.matrix, + 0, + params.xrPoseTransform + ); + const xrViewInvTransform = Matrix4.fromArray( + xrView.transform.inverse.matrix, + 0, + params.xrViewInvTransform + ); + const deltaTransform = Matrix4.multiply( + xrPoseTransform, + xrViewInvTransform, + params.deltaTransform + ); + + Matrix4.multiply(camera.viewMatrix, deltaTransform, params.viewTransform); + + const projectionMatrix = xrView.projectionMatrix; + if ( + defined(params._projectionMatrix) && + arrayEquals(params._projectionMatrix, projectionMatrix) + ) { + return; + } + params._projectionMatrix = Float32Array.from(projectionMatrix); + + PerspectiveOffCenterFrustum.fromProjectionMatrix( + Matrix4.fromArray(xrView.projectionMatrix, 0, params.projectionTransform), + params.pocFrustum + ); + convertPerspectiveOffCenterFrustum(params.pocFrustum, params.frustum); +} + +function preparePoseCameraParamsXR(pose, camera, xrPose) { + pose.cameraParams._frustumNear = camera.frustum.near; + pose.cameraParams._frustumFar = camera.frustum.far; + + for (const xrView of xrPose.views) { + prepareCameraParamsXR( + camera, + xrPose, + xrView, + pose.cameraParams[xrView.eye] + ); + } +} + +// WebXR provides the viewports indicating where to render for each display. +// https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Rendering#frames_poses_views_and_framebuffers +function prepareXR(pose, camera) { + const xr = pose.scene.webXRContext; + if (!defined(xr)) { + // scene.webXRContext not set yet + return false; + } + + const xrFrame = xr.frame; + if (!defined(xrFrame)) { + // XR session is not ready yet + return false; + } + + const xrPose = xrFrame.getViewerPose(xr.refSpace); + if (!defined(xrPose)) { + // Getting the pose may fail if, for example, tracking is lost. + // TODO: we should handle this gracefully. + return false; + } + + const xrLayer = xrFrame.session.renderState.baseLayer; + pose.xrLayer = xrLayer; // Used after command execution for bitblit. + + if (!pose._initialized) { + // Can't do this earlier because we need a valid xrPose + // to know how many views the XR session has. + pose._initialized = true; + for (const xrView of xrPose.views) { + const eye = xrView.eye; + pose.viewports[eye] = new BoundingRectangle(); + pose.cameraParams[eye] = { + xrPoseTransform: new Matrix4(), + xrViewInvTransform: new Matrix4(), + deltaTransform: new Matrix4(), + viewTransform: new Matrix4(), + projectionTransform: new Matrix4(), + pocFrustum: new PerspectiveOffCenterFrustum(), + frustum: new PerspectiveFrustum(), + }; + } + } + + prepareViewportsXR(pose, xrPose); + preparePoseCameraParamsXR(pose, camera, xrPose); + return true; +} + +function applyPoseParamsToCameraXR(pose, params, camera) { + camera.lookAtTransform(params.viewTransform); + + const frustum = camera.frustum; + frustum.aspectRatio = params.frustum.aspectRatio; + frustum.fov = params.frustum.fov; + frustum.near = pose.cameraParams._frustumNear; + frustum.far = pose.cameraParams._frustumFar; + frustum.xOffset = params.frustum.xOffset; + frustum.yOffset = params.frustum.yOffset; + + return true; +} + +// eye can be "left", "right" or "none" (for monoscopic). +// https://developer.mozilla.org/en-US/docs/Web/API/XRView#instance_properties +// +// return value indicates to caller whether commands are to be executed for +// this view or not. +function applyPoseToCamera(pose, eye, camera) { + const params = pose.cameraParams[eye]; + if (!defined(params)) { + console.warn(`Unrecognized eye ${eye}`); + return false; + } + + if ( + eye === "none" && + (defined(pose.viewports["left"]) || defined(pose.viewports["right"])) + ) { + // don't execute commands for a monoscopic view if binocular views are present. + return false; + } + + if (pose.isWebXR) { + return applyPoseParamsToCameraXR(pose, params, camera); + } + + // Plain old WebVR. + return applyPoseParamsToCameraLegacy(pose, params, camera); +} + +/** + * Processor allows for a scene's Camera to be altered in + * translation and frustum according to VR parameters and also + * provides the viewports where rendering is to be performed for + * each viewpoint required by the VR session (typically side-by-side + * stereographic) + * + * @constructor + * + * @param {Scene} scene The scene for which the pose will perform computations. + * + * @private + */ +function VRPose(scene) { + //>>includeStart('debug', pragmas.debug); + if (!defined(scene) || !(scene instanceof Scene)) { + throw new DeveloperError("scene must be a valid Scene."); + } + //>>includeEnd('debug'); + + this.isWebXR = scene.useWebXR && defined(scene.webXRContext); + this.scene = scene; + this.xrLayer = null; + this.viewports = {}; + this.cameraParams = {}; + this._passStateViewport = null; + this._paramsChanged = true; + + if (this.isWebXR) { + // Initialization is postponed until we get a valid xrPose. + this._initialized = false; + } else { + this.viewports.left = new BoundingRectangle(); + this.viewports.right = new BoundingRectangle(); + this.cameraParams.left = {}; + this.cameraParams.right = {}; + this.cameraParams._eyeTranslation = new Cartesian3(); + } +} + +Object.defineProperties(VRPose.prototype, { + /** + * The viewport of the PassState for the commands that will be executed for + * the render. + *

+ * On prepare for WebVR, it determines each eye's view + * geometry. + *

+ *

+ * On apply, it is set to each eye viewport before the + * corresponding render. + *

+ * + * @memberof VRPose.prototype + * @type {BoundingRectangle} + * @default null + */ + passStateViewport: { + get: function () { + return this._passStateViewport; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value) || !(value instanceof BoundingRectangle)) { + throw new DeveloperError("value must be a valid BoundingRectangle."); + } + //>>includeEnd('debug'); + if ( + !this.isWebXR && + this._passStateViewport !== value && + !BoundingRectangle.equals(this._paramsChanged, value) + ) { + this._paramsChanged = true; + } + this._passStateViewport = value; + }, + }, + + /** + * The main scene camera from where the VR camera parameters will be derived. + * On apply, this is the camera that will be modified before + * each VR view render. + * + * @memberof VRPose.prototype + * @type {Camera} + * @default null + */ + camera: { + get: function () { + return this._camera; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value) || !(value instanceof Camera)) { + throw new DeveloperError("value must be a valid Camera."); + } + //>>includeEnd('debug'); + if ( + !this.isWebXR && + this.cameraParams._frustumNear !== value.frustum.near + ) { + this._paramsChanged = true; + } + this._camera = value; + }, + }, +}); + +/** + * Perform calculations of the relative translation and frustum for + * each of the VR views. As little calculations as possible are performed, by + * detecting if there are changes in the basic parameters. + * + * @memberof VRPose.prototype + * + * @param {Camera} camera The main camera used as a base for the VR view parameters. + * @param {BoundingRectangle} passStateViewport Viewport of the passState that will be set to determine where the render will be performed when apply is called. Used also for WebVR (Legacy) calculations. + * @returns {boolean} Whether calculations were successfully done. In case of WebXR, depends if a VR session is running and a pose was successfully obtained. WebVR (Legacy) always succeeds. + */ +VRPose.prototype.prepare = function (camera, passStateViewport) { + // These two will be used for apply + this.camera = camera; + this.passStateViewport = passStateViewport; + + let validPose; + + if (this.isWebXR) { + validPose = prepareXR(this, camera); + } else { + // Plain old WebVR. + prepareViewportsLegacy(this); + preparePoseCameraParamsLegacy(this, camera); + validPose = true; + } + + if (validPose) { + this._paramsChanged = false; + } + return validPose; +}; + +/** + * For each of the views present for the VR session (typically one for the left + * and one for the right eye), apply the prepared parameters to the camera and + * passState viewport that were provided to the prepare method and + * call the provided callback (so typically, the callback is called twice). + * + * @memberof VRPose.prototype + * + * @param {function} execute_cb Execution callback. Receives no parameters. + */ +VRPose.prototype.apply = function (execute_cb) { + const camera = this._camera; + const savedCamera = Camera.clone(camera, this._savedCamera); + const savedViewport = BoundingRectangle.clone( + this._passStateViewport, + this._savedViewport + ); + + for (const eye of Object.keys(this.viewports)) { + if (applyPoseToCamera(this, eye, camera)) { + BoundingRectangle.clone(this.viewports[eye], this._passStateViewport); + execute_cb(); + } + } + + BoundingRectangle.clone(savedViewport, this._passStateViewport); + savedCamera.frustum = camera.frustum; + Camera.clone(savedCamera, camera); +}; + +export default VRPose; diff --git a/server.js b/server.js index 590470ef1ba0..fd0311dbbce8 100644 --- a/server.js +++ b/server.js @@ -105,7 +105,7 @@ async function generateDevelopmentBuild() { }); console.log( - `Cesium built in ${formatTimeSinceInSeconds(startTime)} seconds.` + `Cesium built in ${formatTimeSinceInSeconds(startTime)} seconds.`, ); return contexts; @@ -141,7 +141,7 @@ async function generateDevelopmentBuild() { ], "text/plain": ["glsl"], }, - true + true, ); const app = express(); @@ -151,7 +151,7 @@ async function generateDevelopmentBuild() { res.header("Access-Control-Allow-Origin", "*"); res.header( "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept" + "Origin, X-Requested-With, Content-Type, Accept", ); next(); }); @@ -194,19 +194,19 @@ async function generateDevelopmentBuild() { "Cesium.js", "/Build/CesiumUnminified/Cesium.js*", contexts.iife, - [iifeWorkersCache] + [iifeWorkersCache], ); const esmCache = createRoute( app, "index.js", "/Build/CesiumUnminified/index.js*", - contexts.esm + contexts.esm, ); const workersCache = createRoute( app, "Workers/*", "/Build/CesiumUnminified/Workers/*.js", - contexts.workers + contexts.workers, ); const glslWatcher = chokidar.watch(shaderFiles, { ignoreInitial: true }); @@ -233,7 +233,7 @@ async function generateDevelopmentBuild() { app, "TestWorkers/*", "/Build/Specs/TestWorkers/*", - contexts.testWorkers + contexts.testWorkers, ); chokidar .watch(["Specs/TestWorkers/*.js"], { ignoreInitial: true }) @@ -243,7 +243,7 @@ async function generateDevelopmentBuild() { app, "Specs/*", "/Build/Specs/*", - contexts.specs + contexts.specs, ); const specWatcher = chokidar.watch(specFiles, { ignoreInitial: true }); specWatcher.on("all", async (event) => { @@ -255,21 +255,24 @@ async function generateDevelopmentBuild() { }); // Rebuild jsHintOptions as needed and serve as-is - app.get("/Apps/Sandcastle/jsHintOptions.js", async function ( - //eslint-disable-next-line no-unused-vars - req, - res, - //eslint-disable-next-line no-unused-vars - next - ) { - if (!jsHintOptionsCache) { - jsHintOptionsCache = await createJsHintOptions(); - } + app.get( + "/Apps/Sandcastle/jsHintOptions.js", + async function ( + //eslint-disable-next-line no-unused-vars + req, + res, + //eslint-disable-next-line no-unused-vars + next, + ) { + if (!jsHintOptionsCache) { + jsHintOptionsCache = await createJsHintOptions(); + } - res.append("Cache-Control", "max-age=0"); - res.append("Content-Type", "application/javascript"); - res.send(jsHintOptionsCache); - }); + res.append("Cache-Control", "max-age=0"); + res.append("Content-Type", "application/javascript"); + res.send(jsHintOptionsCache); + }, + ); // Serve any static files starting with "Build/CesiumUnminified" from the // development build instead. That way, previous build output is preserved @@ -294,7 +297,8 @@ async function generateDevelopmentBuild() { return remoteUrl; } - const dontProxyHeaderRegex = /^(?:Host|Proxy-Connection|Connection|Keep-Alive|Transfer-Encoding|TE|Trailer|Proxy-Authorization|Proxy-Authenticate|Upgrade)$/i; + const dontProxyHeaderRegex = + /^(?:Host|Proxy-Connection|Connection|Keep-Alive|Transfer-Encoding|TE|Trailer|Proxy-Authorization|Proxy-Authenticate|Upgrade)$/i; //eslint-disable-next-line no-unused-vars function filterHeaders(req, headers) { @@ -361,7 +365,7 @@ async function generateDevelopmentBuild() { } res.status(code).send(body); - } + }, ); }); @@ -372,28 +376,28 @@ async function generateDevelopmentBuild() { if (argv.public) { console.log( "Cesium development server running publicly. Connect to http://localhost:%d/", - server.address().port + server.address().port, ); } else { console.log( "Cesium development server running locally. Connect to http://localhost:%d/", - server.address().port + server.address().port, ); } - } + }, ); server.on("error", function (e) { if (e.code === "EADDRINUSE") { console.log( "Error: Port %d is already in use, select a different port.", - argv.port + argv.port, ); console.log("Example: node server.js --port %d", argv.port + 1); } else if (e.code === "EACCES") { console.log( "Error: This process does not have permission to listen on port %d.", - argv.port + argv.port, ); if (argv.port < 1024) { console.log("Try a port number higher than 1024.");