diff --git a/Apps/Sandcastle/gallery/Bathymetry.html b/Apps/Sandcastle/gallery/Bathymetry.html index 3a98117ed331..edca677a751a 100644 --- a/Apps/Sandcastle/gallery/Bathymetry.html +++ b/Apps/Sandcastle/gallery/Bathymetry.html @@ -100,6 +100,9 @@ const scene = viewer.scene; + // Prevent the user from tilting beyond the ellipsoid surface + scene.screenSpaceCameraController.maximumTiltAngle = Math.PI / 2.0; + const globe = scene.globe; globe.enableLighting = true; globe.maximumScreenSpaceError = 1.0; // Load higher resolution tiles for better seafloor shading diff --git a/CHANGES.md b/CHANGES.md index fc914d1a4267..806d9b219076 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # Change Log +### 1.123 - 2024-11-01 + +#### @cesium/engine + +##### Additions :tada: + +- Added `ScreenSpaceCameraController.maximumTiltAngle` to limit how much the camera can tilt. [#12169](https://github.com/CesiumGS/cesium/pull/12169) + ### 1.122 - 2024-10-01 #### @cesium/engine diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d9dbdf94d7f2..5bd6a8a5cdaf 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -186,6 +186,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Tang Xiaofei](https://github.com/vtxf) - [Maxar](https://www.maxar.com) - [Erik Dahlström](https://github.com/erikdahlstrom) + - [Albin Ekberg](https://github.com/albek446) - [Novatron Oy](https://novatron.fi/en/) - [Jussi Hirvonen](https://github.com/VsKatshuma) - [Sierra Nevada Corp](https://www.sncorp.com/) diff --git a/packages/engine/Source/Scene/ScreenSpaceCameraController.js b/packages/engine/Source/Scene/ScreenSpaceCameraController.js index 54e7aee3e113..869bb9ac1c93 100644 --- a/packages/engine/Source/Scene/ScreenSpaceCameraController.js +++ b/packages/engine/Source/Scene/ScreenSpaceCameraController.js @@ -271,6 +271,16 @@ function ScreenSpaceCameraController(scene) { * @default true */ this.enableCollisionDetection = true; + /** + * The angle, relative to the ellipsoid normal, restricting the maximum amount that the user can tilt the camera. If undefined, the angle of the camera tilt is unrestricted. + * @type {number|undefined} + * @default undefined + * + * @example + * // Prevent the camera from tilting below the ellipsoid surface + * viewer.scene.screenSpaceCameraController.maximumTiltAngle = Math.PI / 2.0; + */ + this.maximumTiltAngle = undefined; this._scene = scene; this._globe = undefined; @@ -2063,7 +2073,16 @@ function rotate3D( ); const deltaPhi = rotateRate * phiWindowRatio * Math.PI * 2.0; - const deltaTheta = rotateRate * thetaWindowRatio * Math.PI; + let deltaTheta = rotateRate * thetaWindowRatio * Math.PI; + + if (defined(constrainedAxis) && defined(controller.maximumTiltAngle)) { + const maximumTiltAngle = controller.maximumTiltAngle; + const dotProduct = Cartesian3.dot(camera.direction, constrainedAxis); + const tilt = Math.PI - Math.acos(dotProduct) + deltaTheta; + if (tilt > maximumTiltAngle) { + deltaTheta -= tilt - maximumTiltAngle; + } + } if (!rotateOnlyVertical) { camera.rotateRight(deltaPhi); diff --git a/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js b/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js index 593ca54b10f9..f46ed5410c4b 100644 --- a/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js +++ b/packages/engine/Specs/Scene/ScreenSpaceCameraControllerSpec.js @@ -1568,6 +1568,79 @@ describe("Scene/ScreenSpaceCameraController", function () { expect(camera.direction).not.toEqual(direction); }); + it("maximum tilt angle is 0 in 3D", function () { + setUp3D(); + const originalCameraPosition = Cartesian3.clone(camera.position); + const originalCameraDirection = Cartesian3.clone(camera.direction); + controller.maximumTiltAngle = 0; + const startPosition = new Cartesian2( + canvas.clientWidth / 2, + canvas.clientHeight / 2, + ); + const endPosition = new Cartesian2( + canvas.clientWidth / 2, + canvas.clientHeight / 4, + ); + + moveMouse(MouseButtons.MIDDLE, startPosition, endPosition); + updateController(); + + expect(camera.position).toEqualEpsilon( + originalCameraPosition, + CesiumMath.EPSILON8, + ); + expect(camera.position).not.toEqualEpsilon( + originalCameraDirection, + CesiumMath.EPSILON8, + ); + + const dotProduct = Cartesian3.dot( + camera.direction, + originalCameraDirection, + ); + const acos = Math.acos(dotProduct); + const angle = (acos * 180) / Math.PI; + expect(angle).toEqual(0); + }); + + it("maximum tilt angle is 45 degrees in 3D", function () { + setUp3D(); + const originalCameraPosition = Cartesian3.clone(camera.position); + const originalCameraDirection = Cartesian3.clone(camera.direction); + controller.maximumTiltAngle = (45 * Math.PI) / 180; + const startPosition = new Cartesian2( + canvas.clientWidth / 2, + canvas.clientHeight / 2, + ); + const endPosition = new Cartesian2( + canvas.clientWidth / 2, + canvas.clientHeight / 4, + ); + + moveMouse(MouseButtons.MIDDLE, startPosition, endPosition); + updateController(); + moveMouse(MouseButtons.MIDDLE, startPosition, endPosition); + updateController(); + + expect(camera.position).not.toEqualEpsilon( + originalCameraPosition, + CesiumMath.EPSILON8, + ); + expect(camera.position).not.toEqualEpsilon( + originalCameraDirection, + CesiumMath.EPSILON8, + ); + + const dotProduct = Cartesian3.dot( + camera.direction, + originalCameraDirection, + ); + const acos = Math.acos(dotProduct); + const angle = (acos * 180) / Math.PI; + expect(angle).toBeLessThanOrEqual(45); + expect(angle).toBeGreaterThan(44); + }); + it("looks in 3D", function () { setUp3D(); const position = Cartesian3.clone(camera.position);