import BoundingRectangle from "../Core/BoundingRectangle.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import Cartographic from "../Core/Cartographic.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import CesiumMath from "../Core/Math.js"; import Matrix4 from "../Core/Matrix4.js"; import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js"; import Transforms from "../Core/Transforms.js"; import SceneMode from "./SceneMode.js"; /** * Functions that do scene-dependent transforms between rendering-related coordinate systems. * * @namespace SceneTransforms */ var SceneTransforms = {}; var actualPositionScratch = new Cartesian4(0, 0, 0, 1); var positionCC = new Cartesian4(); var scratchViewport = new BoundingRectangle(); var scratchWindowCoord0 = new Cartesian2(); var scratchWindowCoord1 = new Cartesian2(); /** * Transforms a position in WGS84 coordinates to window coordinates. This is commonly used to place an * HTML element at the same screen position as an object in the scene. * * @param {Scene} scene The scene. * @param {Cartesian3} position The position in WGS84 (world) coordinates. * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates. * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be <code>undefined</code> if the input position is near the center of the ellipsoid. * * @example * // Output the window position of longitude/latitude (0, 0) every time the mouse moves. * var scene = widget.scene; * var ellipsoid = scene.globe.ellipsoid; * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0); * var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); * handler.setInputAction(function(movement) { * console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position)); * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); */ SceneTransforms.wgs84ToWindowCoordinates = function (scene, position, result) { return SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates( scene, position, Cartesian3.ZERO, result ); }; var scratchCartesian4 = new Cartesian4(); var scratchEyeOffset = new Cartesian3(); function worldToClip(position, eyeOffset, camera, result) { var viewMatrix = camera.viewMatrix; var positionEC = Matrix4.multiplyByVector( viewMatrix, Cartesian4.fromElements( position.x, position.y, position.z, 1, scratchCartesian4 ), scratchCartesian4 ); var zEyeOffset = Cartesian3.multiplyComponents( eyeOffset, Cartesian3.normalize(positionEC, scratchEyeOffset), scratchEyeOffset ); positionEC.x += eyeOffset.x + zEyeOffset.x; positionEC.y += eyeOffset.y + zEyeOffset.y; positionEC.z += zEyeOffset.z; return Matrix4.multiplyByVector( camera.frustum.projectionMatrix, positionEC, result ); } var scratchMaxCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO); var scratchProjectedCartesian = new Cartesian3(); var scratchCameraPosition = new Cartesian3(); /** * @private */ SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function ( scene, position, eyeOffset, result ) { //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError("scene is required."); } if (!defined(position)) { throw new DeveloperError("position is required."); } //>>includeEnd('debug'); // Transform for 3D, 2D, or Columbus view var frameState = scene.frameState; var actualPosition = SceneTransforms.computeActualWgs84Position( frameState, position, actualPositionScratch ); if (!defined(actualPosition)) { return undefined; } // Assuming viewport takes up the entire canvas... var canvas = scene.canvas; var viewport = scratchViewport; viewport.x = 0; viewport.y = 0; viewport.width = canvas.clientWidth; viewport.height = canvas.clientHeight; var camera = scene.camera; var cameraCentered = false; if (frameState.mode === SceneMode.SCENE2D) { var projection = scene.mapProjection; var maxCartographic = scratchMaxCartographic; var maxCoord = projection.project( maxCartographic, scratchProjectedCartesian ); var cameraPosition = Cartesian3.clone( camera.position, scratchCameraPosition ); var frustum = camera.frustum.clone(); var viewportTransformation = Matrix4.computeViewportTransformation( viewport, 0.0, 1.0, new Matrix4() ); var projectionMatrix = camera.frustum.projectionMatrix; var x = camera.positionWC.y; var eyePoint = Cartesian3.fromElements( CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x ); var windowCoordinates = Transforms.pointToGLWindowCoordinates( projectionMatrix, viewportTransformation, eyePoint ); if ( x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= canvas.clientWidth ) { cameraCentered = true; } else { if (windowCoordinates.x > canvas.clientWidth * 0.5) { viewport.width = windowCoordinates.x; camera.frustum.right = maxCoord.x - x; positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); SceneTransforms.clipToGLWindowCoordinates( viewport, positionCC, scratchWindowCoord0 ); viewport.x += windowCoordinates.x; camera.position.x = -camera.position.x; var right = camera.frustum.right; camera.frustum.right = -camera.frustum.left; camera.frustum.left = -right; positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); SceneTransforms.clipToGLWindowCoordinates( viewport, positionCC, scratchWindowCoord1 ); } else { viewport.x += windowCoordinates.x; viewport.width -= windowCoordinates.x; camera.frustum.left = -maxCoord.x - x; positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); SceneTransforms.clipToGLWindowCoordinates( viewport, positionCC, scratchWindowCoord0 ); viewport.x = viewport.x - viewport.width; camera.position.x = -camera.position.x; var left = camera.frustum.left; camera.frustum.left = -camera.frustum.right; camera.frustum.right = -left; positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); SceneTransforms.clipToGLWindowCoordinates( viewport, positionCC, scratchWindowCoord1 ); } Cartesian3.clone(cameraPosition, camera.position); camera.frustum = frustum.clone(); result = Cartesian2.clone(scratchWindowCoord0, result); if (result.x < 0.0 || result.x > canvas.clientWidth) { result.x = scratchWindowCoord1.x; } } } if (frameState.mode !== SceneMode.SCENE2D || cameraCentered) { // View-projection matrix to transform from world coordinates to clip coordinates positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); if ( positionCC.z < 0 && !(camera.frustum instanceof OrthographicFrustum) && !(camera.frustum instanceof OrthographicOffCenterFrustum) ) { return undefined; } result = SceneTransforms.clipToGLWindowCoordinates( viewport, positionCC, result ); } result.y = canvas.clientHeight - result.y; return result; }; /** * Transforms a position in WGS84 coordinates to drawing buffer coordinates. This may produce different * results from SceneTransforms.wgs84ToWindowCoordinates when the browser zoom is not 100%, or on high-DPI displays. * * @param {Scene} scene The scene. * @param {Cartesian3} position The position in WGS84 (world) coordinates. * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates. * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be <code>undefined</code> if the input position is near the center of the ellipsoid. * * @example * // Output the window position of longitude/latitude (0, 0) every time the mouse moves. * var scene = widget.scene; * var ellipsoid = scene.globe.ellipsoid; * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0); * var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); * handler.setInputAction(function(movement) { * console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position)); * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); */ SceneTransforms.wgs84ToDrawingBufferCoordinates = function ( scene, position, result ) { result = SceneTransforms.wgs84ToWindowCoordinates(scene, position, result); if (!defined(result)) { return undefined; } return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result); }; var projectedPosition = new Cartesian3(); var positionInCartographic = new Cartographic(); /** * @private */ SceneTransforms.computeActualWgs84Position = function ( frameState, position, result ) { var mode = frameState.mode; if (mode === SceneMode.SCENE3D) { return Cartesian3.clone(position, result); } var projection = frameState.mapProjection; var cartographic = projection.ellipsoid.cartesianToCartographic( position, positionInCartographic ); if (!defined(cartographic)) { return undefined; } projection.project(cartographic, projectedPosition); if (mode === SceneMode.COLUMBUS_VIEW) { return Cartesian3.fromElements( projectedPosition.z, projectedPosition.x, projectedPosition.y, result ); } if (mode === SceneMode.SCENE2D) { return Cartesian3.fromElements( 0.0, projectedPosition.x, projectedPosition.y, result ); } // mode === SceneMode.MORPHING var morphTime = frameState.morphTime; return Cartesian3.fromElements( CesiumMath.lerp(projectedPosition.z, position.x, morphTime), CesiumMath.lerp(projectedPosition.x, position.y, morphTime), CesiumMath.lerp(projectedPosition.y, position.z, morphTime), result ); }; var positionNDC = new Cartesian3(); var positionWC = new Cartesian3(); var viewportTransform = new Matrix4(); /** * @private */ SceneTransforms.clipToGLWindowCoordinates = function ( viewport, position, result ) { // Perspective divide to transform from clip coordinates to normalized device coordinates Cartesian3.divideByScalar(position, position.w, positionNDC); // Viewport transform to transform from clip coordinates to window coordinates Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform); Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC); return Cartesian2.fromCartesian3(positionWC, result); }; /** * @private */ SceneTransforms.transformWindowToDrawingBuffer = function ( scene, windowPosition, result ) { var canvas = scene.canvas; var xScale = scene.drawingBufferWidth / canvas.clientWidth; var yScale = scene.drawingBufferHeight / canvas.clientHeight; return Cartesian2.fromElements( windowPosition.x * xScale, windowPosition.y * yScale, result ); }; var scratchNDC = new Cartesian4(); var scratchWorldCoords = new Cartesian4(); /** * @private */ SceneTransforms.drawingBufferToWgs84Coordinates = function ( scene, drawingBufferPosition, depth, result ) { var context = scene.context; var uniformState = context.uniformState; var currentFrustum = uniformState.currentFrustum; var near = currentFrustum.x; var far = currentFrustum.y; if (scene.frameState.useLogDepth) { // transforming logarithmic depth of form // log2(z + 1) / log2( far + 1); // to perspective form // (far - far * near / z) / (far - near) var log2Depth = depth * uniformState.log2FarDepthFromNearPlusOne; var depthFromNear = Math.pow(2.0, log2Depth) - 1.0; depth = (far * (1.0 - near / (depthFromNear + near))) / (far - near); } var viewport = scene.view.passState.viewport; var ndc = Cartesian4.clone(Cartesian4.UNIT_W, scratchNDC); ndc.x = ((drawingBufferPosition.x - viewport.x) / viewport.width) * 2.0 - 1.0; ndc.y = ((drawingBufferPosition.y - viewport.y) / viewport.height) * 2.0 - 1.0; ndc.z = depth * 2.0 - 1.0; ndc.w = 1.0; var worldCoords; var frustum = scene.camera.frustum; if (!defined(frustum.fovy)) { if (defined(frustum._offCenterFrustum)) { frustum = frustum._offCenterFrustum; } worldCoords = scratchWorldCoords; worldCoords.x = (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; worldCoords.y = (ndc.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; worldCoords.z = (ndc.z * (near - far) - near - far) * 0.5; worldCoords.w = 1.0; worldCoords = Matrix4.multiplyByVector( uniformState.inverseView, worldCoords, worldCoords ); } else { worldCoords = Matrix4.multiplyByVector( uniformState.inverseViewProjection, ndc, scratchWorldCoords ); // Reverse perspective divide var w = 1.0 / worldCoords.w; Cartesian3.multiplyByScalar(worldCoords, w, worldCoords); } return Cartesian3.fromCartesian4(worldCoords, result); }; export default SceneTransforms;