diff --git a/CHANGES.md b/CHANGES.md index 0cd0a34efc7f..3510b99f7e61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # Change Log +### 1.106 - 2023-06-01 + +#### @cesium/engine + +##### Fixes :wrench: + +- Improved camera controls when globe is off. [#7171](https://github.com/CesiumGS/cesium/issues/7171) + ### 1.105 - 2023-05-01 #### @cesium/engine diff --git a/packages/engine/Source/Scene/ScreenSpaceCameraController.js b/packages/engine/Source/Scene/ScreenSpaceCameraController.js index c08c3c298aae..5878c5492ab2 100644 --- a/packages/engine/Source/Scene/ScreenSpaceCameraController.js +++ b/packages/engine/Source/Scene/ScreenSpaceCameraController.js @@ -217,7 +217,7 @@ function ScreenSpaceCameraController(scene) { modifier: KeyboardEventModifier.SHIFT, }; /** - * The minimum height the camera must be before picking the terrain instead of the ellipsoid. + * The minimum height the camera must be before picking the terrain or scene content instead of the ellipsoid. * @type {number} * @default 150000.0 */ @@ -291,6 +291,8 @@ function ScreenSpaceCameraController(scene) { this._zoomMouseStart = new Cartesian2(-1.0, -1.0); this._zoomWorldPosition = new Cartesian3(); this._useZoomWorldPosition = false; + this._panLastMousePosition = new Cartesian2(); + this._panLastWorldPosition = new Cartesian3(); this._tiltCVOffMap = false; this._looking = false; this._rotating = false; @@ -611,19 +613,22 @@ function handleZoom( object._zoomMouseStart ); - if (defined(object._globe)) { - if (mode === SceneMode.SCENE2D) { - pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay) - .origin; - pickedPosition = Cartesian3.fromElements( - pickedPosition.y, - pickedPosition.z, - pickedPosition.x - ); - } else { - pickedPosition = pickGlobe(object, startPosition, scratchPickCartesian); - } + if (defined(object._globe) && mode === SceneMode.SCENE2D) { + pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay) + .origin; + pickedPosition = Cartesian3.fromElements( + pickedPosition.y, + pickedPosition.z, + pickedPosition.x + ); + } else { + pickedPosition = pickPosition( + object, + startPosition, + scratchPickCartesian + ); } + if (defined(pickedPosition)) { object._useZoomWorldPosition = true; object._zoomWorldPosition = Cartesian3.clone( @@ -709,7 +714,7 @@ function handleZoom( const centerPixel = scratchCenterPixel; centerPixel.x = canvas.clientWidth / 2; centerPixel.y = canvas.clientHeight / 2; - const centerPosition = pickGlobe( + const centerPosition = pickPosition( object, centerPixel, scratchCenterPosition @@ -1107,17 +1112,11 @@ const pickGlobeScratchRay = new Ray(); const scratchDepthIntersection = new Cartesian3(); const scratchRayIntersection = new Cartesian3(); -function pickGlobe(controller, mousePosition, result) { +function pickPosition(controller, mousePosition, result) { const scene = controller._scene; const globe = controller._globe; const camera = scene.camera; - if (!defined(globe)) { - return undefined; - } - - const cullBackFaces = !controller._cameraUnderground; - let depthIntersection; if (scene.pickPositionSupported) { depthIntersection = scene.pickPositionWorldCoordinates( @@ -1126,6 +1125,14 @@ function pickGlobe(controller, mousePosition, result) { ); } + if ( + !defined(globe) || + (defined(depthIntersection) && !globe.translucency.enabled) + ) { + return Cartesian3.clone(depthIntersection, result); + } + + const cullBackFaces = !controller._cameraUnderground; const ray = camera.getPickRay(mousePosition, pickGlobeScratchRay); const rayIntersection = globe.pickWorldCoordinates( ray, @@ -1289,7 +1296,8 @@ function translateCV(controller, startPosition, movement) { let globePos; if (camera.position.z < controller._minimumPickingTerrainHeight) { - globePos = pickGlobe(controller, startMouse, translateCVStartPos); + globePos = pickPosition(controller, startMouse, translateCVStartPos); + if (defined(globePos)) { origin.x = globePos.x; } @@ -1476,7 +1484,7 @@ function rotateCVOnTerrain(controller, startPosition, movement) { center = Cartesian3.clone(controller._tiltCenter, rotateCVCenter); } else { if (camera.position.z < controller._minimumPickingTerrainHeight) { - center = pickGlobe(controller, startPosition, rotateCVCenter); + center = pickPosition(controller, startPosition, rotateCVCenter); } if (!defined(center)) { @@ -1711,7 +1719,7 @@ function zoomCV(controller, startPosition, movement) { let intersection; if (height < controller._minimumPickingTerrainHeight) { - intersection = pickGlobe(controller, windowPosition, zoomCVIntersection); + intersection = pickPosition(controller, windowPosition, zoomCVIntersection); } let distance; @@ -1918,9 +1926,9 @@ function spin3D(controller, startPosition, movement) { const globe = controller._globe; if (defined(globe) && height < controller._minimumPickingTerrainHeight) { - const mousePos = pickGlobe( - controller, + const mousePos = camera.pickEllipsoid( movement.startPosition, + controller._ellipsoid, scratchMousePos ); if (defined(mousePos)) { @@ -2051,6 +2059,9 @@ const pan3DTemp2 = new Cartesian3(); const pan3DTemp3 = new Cartesian3(); const pan3DStartMousePosition = new Cartesian2(); const pan3DEndMousePosition = new Cartesian2(); +const pan3DDiffMousePosition = new Cartesian2(); +const pan3DPixelDimensions = new Cartesian2(); +const panRay = new Ray(); function pan3D(controller, startPosition, movement, ellipsoid) { const scene = controller._scene; @@ -2064,9 +2075,109 @@ function pan3D(controller, startPosition, movement, ellipsoid) { movement.endPosition, pan3DEndMousePosition ); + const height = ellipsoid.cartesianToCartographic( + camera.positionWC, + scratchCartographic + ).height; + + let p0, p1; + + if ( + !movement.inertiaEnabled && + height < controller._minimumPickingTerrainHeight + ) { + p0 = Cartesian3.clone(controller._panLastWorldPosition, pan3DP0); - let p0 = camera.pickEllipsoid(startMousePosition, ellipsoid, pan3DP0); - let p1 = camera.pickEllipsoid(endMousePosition, ellipsoid, pan3DP1); + // Use the last picked world position unless we're starting a new drag + if ( + !defined(controller._globe) && + !Cartesian2.equalsEpsilon( + startMousePosition, + controller._panLastMousePosition + ) + ) { + p0 = pickPosition(controller, startMousePosition, pan3DP0); + } + + if (!defined(controller._globe) && defined(p0)) { + const toCenter = Cartesian3.subtract(p0, camera.positionWC, pan3DTemp1); + const toCenterProj = Cartesian3.multiplyByScalar( + camera.directionWC, + Cartesian3.dot(camera.directionWC, toCenter), + pan3DTemp1 + ); + const distanceToNearPlane = Cartesian3.magnitude(toCenterProj); + const pixelDimensions = camera.frustum.getPixelDimensions( + scene.drawingBufferWidth, + scene.drawingBufferHeight, + distanceToNearPlane, + scene.pixelRatio, + pan3DPixelDimensions + ); + + const dragDelta = Cartesian2.subtract( + endMousePosition, + startMousePosition, + pan3DDiffMousePosition + ); + + // Move the camera to the the distance the cursor moved in worldspace + const right = Cartesian3.multiplyByScalar( + camera.rightWC, + dragDelta.x * pixelDimensions.x, + pan3DTemp1 + ); + + // Move the camera towards the picked position in worldspace as the camera is pointed towards a horizon view + const cameraPositionNormal = Cartesian3.normalize( + camera.positionWC, + scratchCameraPositionNormal + ); + const endPickDirection = camera.getPickRay(endMousePosition, panRay) + .direction; + const endPickProj = Cartesian3.subtract( + endPickDirection, + Cartesian3.projectVector(endPickDirection, camera.rightWC, pan3DTemp2), + pan3DTemp2 + ); + const angle = Cartesian3.angleBetween(endPickProj, camera.directionWC); + let forward = 1.0; + if (defined(camera.frustum.fov)) { + forward = Math.max(Math.tan(angle), 0.1); // Clamp so we don't make the magnitude infinitely large when the angle is small + } + let dot = Math.abs( + Cartesian3.dot(camera.directionWC, cameraPositionNormal) + ); + const magnitude = + ((-dragDelta.y * pixelDimensions.y * 2.0) / Math.sqrt(forward)) * + (1.0 - dot); + const direction = Cartesian3.multiplyByScalar( + endPickDirection, + magnitude, + pan3DTemp2 + ); + + // Move the camera up the distance the cursor moved in worldspace as the camera is pointed towards the center + dot = Math.abs(Cartesian3.dot(camera.upWC, cameraPositionNormal)); + const up = Cartesian3.multiplyByScalar( + camera.upWC, + -dragDelta.y * (1.0 - dot) * pixelDimensions.y, + pan3DTemp3 + ); + + p1 = Cartesian3.add(p0, right, pan3DP1); + p1 = Cartesian3.add(p1, direction, p1); + p1 = Cartesian3.add(p1, up, p1); + + Cartesian3.clone(p1, controller._panLastWorldPosition); + Cartesian2.clone(endMousePosition, controller._panLastMousePosition); + } + } + + if (!defined(p0) || !defined(p1)) { + p0 = camera.pickEllipsoid(startMousePosition, ellipsoid, pan3DP0); + p1 = camera.pickEllipsoid(endMousePosition, ellipsoid, pan3DP1); + } if (!defined(p0) || !defined(p1)) { controller._rotating = true; @@ -2205,7 +2316,7 @@ function zoom3D(controller, startPosition, movement) { ? approachingCollision : height < controller._minimumPickingTerrainHeight; if (needPickGlobe) { - intersection = pickGlobe(controller, windowPosition, zoomCVIntersection); + intersection = pickPosition(controller, windowPosition, zoomCVIntersection); } let distance; @@ -2397,7 +2508,7 @@ function tilt3DOnTerrain(controller, startPosition, movement) { if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) { center = Cartesian3.clone(controller._tiltCenter, tilt3DCenter); } else { - center = pickGlobe(controller, startPosition, tilt3DCenter); + center = pickPosition(controller, startPosition, tilt3DCenter); if (!defined(center)) { ray = camera.getPickRay(startPosition, tilt3DRay);