SceneTransforms.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import BoundingRectangle from "../Core/BoundingRectangle.js";
  2. import Cartesian2 from "../Core/Cartesian2.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartesian4 from "../Core/Cartesian4.js";
  5. import Cartographic from "../Core/Cartographic.js";
  6. import defined from "../Core/defined.js";
  7. import DeveloperError from "../Core/DeveloperError.js";
  8. import CesiumMath from "../Core/Math.js";
  9. import Matrix4 from "../Core/Matrix4.js";
  10. import OrthographicFrustum from "../Core/OrthographicFrustum.js";
  11. import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
  12. import Transforms from "../Core/Transforms.js";
  13. import SceneMode from "./SceneMode.js";
  14. /**
  15. * Functions that do scene-dependent transforms between rendering-related coordinate systems.
  16. *
  17. * @namespace SceneTransforms
  18. */
  19. const SceneTransforms = {};
  20. const actualPositionScratch = new Cartesian4(0, 0, 0, 1);
  21. let positionCC = new Cartesian4();
  22. const scratchViewport = new BoundingRectangle();
  23. const scratchWindowCoord0 = new Cartesian2();
  24. const scratchWindowCoord1 = new Cartesian2();
  25. /**
  26. * Transforms a position in WGS84 coordinates to window coordinates. This is commonly used to place an
  27. * HTML element at the same screen position as an object in the scene.
  28. *
  29. * @param {Scene} scene The scene.
  30. * @param {Cartesian3} position The position in WGS84 (world) coordinates.
  31. * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
  32. * @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.
  33. *
  34. * @example
  35. * // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
  36. * const scene = widget.scene;
  37. * const ellipsoid = scene.globe.ellipsoid;
  38. * const position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
  39. * const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
  40. * handler.setInputAction(function(movement) {
  41. * console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
  42. * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  43. */
  44. SceneTransforms.wgs84ToWindowCoordinates = function (scene, position, result) {
  45. return SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(
  46. scene,
  47. position,
  48. Cartesian3.ZERO,
  49. result
  50. );
  51. };
  52. const scratchCartesian4 = new Cartesian4();
  53. const scratchEyeOffset = new Cartesian3();
  54. function worldToClip(position, eyeOffset, camera, result) {
  55. const viewMatrix = camera.viewMatrix;
  56. const positionEC = Matrix4.multiplyByVector(
  57. viewMatrix,
  58. Cartesian4.fromElements(
  59. position.x,
  60. position.y,
  61. position.z,
  62. 1,
  63. scratchCartesian4
  64. ),
  65. scratchCartesian4
  66. );
  67. const zEyeOffset = Cartesian3.multiplyComponents(
  68. eyeOffset,
  69. Cartesian3.normalize(positionEC, scratchEyeOffset),
  70. scratchEyeOffset
  71. );
  72. positionEC.x += eyeOffset.x + zEyeOffset.x;
  73. positionEC.y += eyeOffset.y + zEyeOffset.y;
  74. positionEC.z += zEyeOffset.z;
  75. return Matrix4.multiplyByVector(
  76. camera.frustum.projectionMatrix,
  77. positionEC,
  78. result
  79. );
  80. }
  81. const scratchMaxCartographic = new Cartographic(
  82. Math.PI,
  83. CesiumMath.PI_OVER_TWO
  84. );
  85. const scratchProjectedCartesian = new Cartesian3();
  86. const scratchCameraPosition = new Cartesian3();
  87. /**
  88. * @private
  89. */
  90. SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function (
  91. scene,
  92. position,
  93. eyeOffset,
  94. result
  95. ) {
  96. //>>includeStart('debug', pragmas.debug);
  97. if (!defined(scene)) {
  98. throw new DeveloperError("scene is required.");
  99. }
  100. if (!defined(position)) {
  101. throw new DeveloperError("position is required.");
  102. }
  103. //>>includeEnd('debug');
  104. // Transform for 3D, 2D, or Columbus view
  105. const frameState = scene.frameState;
  106. const actualPosition = SceneTransforms.computeActualWgs84Position(
  107. frameState,
  108. position,
  109. actualPositionScratch
  110. );
  111. if (!defined(actualPosition)) {
  112. return undefined;
  113. }
  114. // Assuming viewport takes up the entire canvas...
  115. const canvas = scene.canvas;
  116. const viewport = scratchViewport;
  117. viewport.x = 0;
  118. viewport.y = 0;
  119. viewport.width = canvas.clientWidth;
  120. viewport.height = canvas.clientHeight;
  121. const camera = scene.camera;
  122. let cameraCentered = false;
  123. if (frameState.mode === SceneMode.SCENE2D) {
  124. const projection = scene.mapProjection;
  125. const maxCartographic = scratchMaxCartographic;
  126. const maxCoord = projection.project(
  127. maxCartographic,
  128. scratchProjectedCartesian
  129. );
  130. const cameraPosition = Cartesian3.clone(
  131. camera.position,
  132. scratchCameraPosition
  133. );
  134. const frustum = camera.frustum.clone();
  135. const viewportTransformation = Matrix4.computeViewportTransformation(
  136. viewport,
  137. 0.0,
  138. 1.0,
  139. new Matrix4()
  140. );
  141. const projectionMatrix = camera.frustum.projectionMatrix;
  142. const x = camera.positionWC.y;
  143. const eyePoint = Cartesian3.fromElements(
  144. CesiumMath.sign(x) * maxCoord.x - x,
  145. 0.0,
  146. -camera.positionWC.x
  147. );
  148. const windowCoordinates = Transforms.pointToGLWindowCoordinates(
  149. projectionMatrix,
  150. viewportTransformation,
  151. eyePoint
  152. );
  153. if (
  154. x === 0.0 ||
  155. windowCoordinates.x <= 0.0 ||
  156. windowCoordinates.x >= canvas.clientWidth
  157. ) {
  158. cameraCentered = true;
  159. } else {
  160. if (windowCoordinates.x > canvas.clientWidth * 0.5) {
  161. viewport.width = windowCoordinates.x;
  162. camera.frustum.right = maxCoord.x - x;
  163. positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
  164. SceneTransforms.clipToGLWindowCoordinates(
  165. viewport,
  166. positionCC,
  167. scratchWindowCoord0
  168. );
  169. viewport.x += windowCoordinates.x;
  170. camera.position.x = -camera.position.x;
  171. const right = camera.frustum.right;
  172. camera.frustum.right = -camera.frustum.left;
  173. camera.frustum.left = -right;
  174. positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
  175. SceneTransforms.clipToGLWindowCoordinates(
  176. viewport,
  177. positionCC,
  178. scratchWindowCoord1
  179. );
  180. } else {
  181. viewport.x += windowCoordinates.x;
  182. viewport.width -= windowCoordinates.x;
  183. camera.frustum.left = -maxCoord.x - x;
  184. positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
  185. SceneTransforms.clipToGLWindowCoordinates(
  186. viewport,
  187. positionCC,
  188. scratchWindowCoord0
  189. );
  190. viewport.x = viewport.x - viewport.width;
  191. camera.position.x = -camera.position.x;
  192. const left = camera.frustum.left;
  193. camera.frustum.left = -camera.frustum.right;
  194. camera.frustum.right = -left;
  195. positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
  196. SceneTransforms.clipToGLWindowCoordinates(
  197. viewport,
  198. positionCC,
  199. scratchWindowCoord1
  200. );
  201. }
  202. Cartesian3.clone(cameraPosition, camera.position);
  203. camera.frustum = frustum.clone();
  204. result = Cartesian2.clone(scratchWindowCoord0, result);
  205. if (result.x < 0.0 || result.x > canvas.clientWidth) {
  206. result.x = scratchWindowCoord1.x;
  207. }
  208. }
  209. }
  210. if (frameState.mode !== SceneMode.SCENE2D || cameraCentered) {
  211. // View-projection matrix to transform from world coordinates to clip coordinates
  212. positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
  213. if (
  214. positionCC.z < 0 &&
  215. !(camera.frustum instanceof OrthographicFrustum) &&
  216. !(camera.frustum instanceof OrthographicOffCenterFrustum)
  217. ) {
  218. return undefined;
  219. }
  220. result = SceneTransforms.clipToGLWindowCoordinates(
  221. viewport,
  222. positionCC,
  223. result
  224. );
  225. }
  226. result.y = canvas.clientHeight - result.y;
  227. return result;
  228. };
  229. /**
  230. * Transforms a position in WGS84 coordinates to drawing buffer coordinates. This may produce different
  231. * results from SceneTransforms.wgs84ToWindowCoordinates when the browser zoom is not 100%, or on high-DPI displays.
  232. *
  233. * @param {Scene} scene The scene.
  234. * @param {Cartesian3} position The position in WGS84 (world) coordinates.
  235. * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
  236. * @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.
  237. *
  238. * @example
  239. * // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
  240. * const scene = widget.scene;
  241. * const ellipsoid = scene.globe.ellipsoid;
  242. * const position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
  243. * const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
  244. * handler.setInputAction(function(movement) {
  245. * console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
  246. * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  247. */
  248. SceneTransforms.wgs84ToDrawingBufferCoordinates = function (
  249. scene,
  250. position,
  251. result
  252. ) {
  253. result = SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
  254. if (!defined(result)) {
  255. return undefined;
  256. }
  257. return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result);
  258. };
  259. const projectedPosition = new Cartesian3();
  260. const positionInCartographic = new Cartographic();
  261. /**
  262. * @private
  263. */
  264. SceneTransforms.computeActualWgs84Position = function (
  265. frameState,
  266. position,
  267. result
  268. ) {
  269. const mode = frameState.mode;
  270. if (mode === SceneMode.SCENE3D) {
  271. return Cartesian3.clone(position, result);
  272. }
  273. const projection = frameState.mapProjection;
  274. const cartographic = projection.ellipsoid.cartesianToCartographic(
  275. position,
  276. positionInCartographic
  277. );
  278. if (!defined(cartographic)) {
  279. return undefined;
  280. }
  281. projection.project(cartographic, projectedPosition);
  282. if (mode === SceneMode.COLUMBUS_VIEW) {
  283. return Cartesian3.fromElements(
  284. projectedPosition.z,
  285. projectedPosition.x,
  286. projectedPosition.y,
  287. result
  288. );
  289. }
  290. if (mode === SceneMode.SCENE2D) {
  291. return Cartesian3.fromElements(
  292. 0.0,
  293. projectedPosition.x,
  294. projectedPosition.y,
  295. result
  296. );
  297. }
  298. // mode === SceneMode.MORPHING
  299. const morphTime = frameState.morphTime;
  300. return Cartesian3.fromElements(
  301. CesiumMath.lerp(projectedPosition.z, position.x, morphTime),
  302. CesiumMath.lerp(projectedPosition.x, position.y, morphTime),
  303. CesiumMath.lerp(projectedPosition.y, position.z, morphTime),
  304. result
  305. );
  306. };
  307. const positionNDC = new Cartesian3();
  308. const positionWC = new Cartesian3();
  309. const viewportTransform = new Matrix4();
  310. /**
  311. * @private
  312. */
  313. SceneTransforms.clipToGLWindowCoordinates = function (
  314. viewport,
  315. position,
  316. result
  317. ) {
  318. // Perspective divide to transform from clip coordinates to normalized device coordinates
  319. Cartesian3.divideByScalar(position, position.w, positionNDC);
  320. // Viewport transform to transform from clip coordinates to window coordinates
  321. Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform);
  322. Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC);
  323. return Cartesian2.fromCartesian3(positionWC, result);
  324. };
  325. /**
  326. * @private
  327. */
  328. SceneTransforms.transformWindowToDrawingBuffer = function (
  329. scene,
  330. windowPosition,
  331. result
  332. ) {
  333. const canvas = scene.canvas;
  334. const xScale = scene.drawingBufferWidth / canvas.clientWidth;
  335. const yScale = scene.drawingBufferHeight / canvas.clientHeight;
  336. return Cartesian2.fromElements(
  337. windowPosition.x * xScale,
  338. windowPosition.y * yScale,
  339. result
  340. );
  341. };
  342. const scratchNDC = new Cartesian4();
  343. const scratchWorldCoords = new Cartesian4();
  344. /**
  345. * @private
  346. */
  347. SceneTransforms.drawingBufferToWgs84Coordinates = function (
  348. scene,
  349. drawingBufferPosition,
  350. depth,
  351. result
  352. ) {
  353. const context = scene.context;
  354. const uniformState = context.uniformState;
  355. const currentFrustum = uniformState.currentFrustum;
  356. const near = currentFrustum.x;
  357. const far = currentFrustum.y;
  358. if (scene.frameState.useLogDepth) {
  359. // transforming logarithmic depth of form
  360. // log2(z + 1) / log2( far + 1);
  361. // to perspective form
  362. // (far - far * near / z) / (far - near)
  363. const log2Depth = depth * uniformState.log2FarDepthFromNearPlusOne;
  364. const depthFromNear = Math.pow(2.0, log2Depth) - 1.0;
  365. depth = (far * (1.0 - near / (depthFromNear + near))) / (far - near);
  366. }
  367. const viewport = scene.view.passState.viewport;
  368. const ndc = Cartesian4.clone(Cartesian4.UNIT_W, scratchNDC);
  369. ndc.x = ((drawingBufferPosition.x - viewport.x) / viewport.width) * 2.0 - 1.0;
  370. ndc.y =
  371. ((drawingBufferPosition.y - viewport.y) / viewport.height) * 2.0 - 1.0;
  372. ndc.z = depth * 2.0 - 1.0;
  373. ndc.w = 1.0;
  374. let worldCoords;
  375. let frustum = scene.camera.frustum;
  376. if (!defined(frustum.fovy)) {
  377. if (defined(frustum._offCenterFrustum)) {
  378. frustum = frustum._offCenterFrustum;
  379. }
  380. worldCoords = scratchWorldCoords;
  381. worldCoords.x =
  382. (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) *
  383. 0.5;
  384. worldCoords.y =
  385. (ndc.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) *
  386. 0.5;
  387. worldCoords.z = (ndc.z * (near - far) - near - far) * 0.5;
  388. worldCoords.w = 1.0;
  389. worldCoords = Matrix4.multiplyByVector(
  390. uniformState.inverseView,
  391. worldCoords,
  392. worldCoords
  393. );
  394. } else {
  395. worldCoords = Matrix4.multiplyByVector(
  396. uniformState.inverseViewProjection,
  397. ndc,
  398. scratchWorldCoords
  399. );
  400. // Reverse perspective divide
  401. const w = 1.0 / worldCoords.w;
  402. Cartesian3.multiplyByScalar(worldCoords, w, worldCoords);
  403. }
  404. return Cartesian3.fromCartesian4(worldCoords, result);
  405. };
  406. export default SceneTransforms;