CameraFlightPath.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. /*global define*/
  2. define([
  3. '../Core/Cartesian2',
  4. '../Core/Cartesian3',
  5. '../Core/Cartographic',
  6. '../Core/clone',
  7. '../Core/defaultValue',
  8. '../Core/defined',
  9. '../Core/DeveloperError',
  10. '../Core/EasingFunction',
  11. '../Core/HermiteSpline',
  12. '../Core/LinearSpline',
  13. '../Core/Math',
  14. '../Core/Matrix3',
  15. '../Core/Matrix4',
  16. '../Core/Quaternion',
  17. '../Core/QuaternionSpline',
  18. './PerspectiveFrustum',
  19. './PerspectiveOffCenterFrustum',
  20. './SceneMode'
  21. ], function(
  22. Cartesian2,
  23. Cartesian3,
  24. Cartographic,
  25. clone,
  26. defaultValue,
  27. defined,
  28. DeveloperError,
  29. EasingFunction,
  30. HermiteSpline,
  31. LinearSpline,
  32. CesiumMath,
  33. Matrix3,
  34. Matrix4,
  35. Quaternion,
  36. QuaternionSpline,
  37. PerspectiveFrustum,
  38. PerspectiveOffCenterFrustum,
  39. SceneMode) {
  40. "use strict";
  41. /**
  42. * Creates tweens for camera flights.
  43. * <br /><br />
  44. * Mouse interaction is disabled during flights.
  45. *
  46. * @private
  47. */
  48. var CameraFlightPath = {
  49. };
  50. var c3destination = new Cartesian3();
  51. var rotMatrix = new Matrix3();
  52. var viewMat = new Matrix3();
  53. var cqRight = new Cartesian3();
  54. var cqUp = new Cartesian3();
  55. function createQuaternion(direction, up, result) {
  56. Cartesian3.cross(direction, up, cqRight);
  57. Cartesian3.cross(cqRight, direction, cqUp);
  58. viewMat[0] = cqRight.x;
  59. viewMat[1] = cqUp.x;
  60. viewMat[2] = -direction.x;
  61. viewMat[3] = cqRight.y;
  62. viewMat[4] = cqUp.y;
  63. viewMat[5] = -direction.y;
  64. viewMat[6] = cqRight.z;
  65. viewMat[7] = cqUp.z;
  66. viewMat[8] = -direction.z;
  67. return Quaternion.fromRotationMatrix(viewMat, result);
  68. }
  69. function getAltitude(frustum, dx, dy) {
  70. var near;
  71. var top;
  72. var right;
  73. if (frustum instanceof PerspectiveFrustum) {
  74. var tanTheta = Math.tan(0.5 * frustum.fovy);
  75. near = frustum.near;
  76. top = frustum.near * tanTheta;
  77. right = frustum.aspectRatio * top;
  78. return Math.max(dx * near / right, dy * near / top);
  79. } else if (frustum instanceof PerspectiveOffCenterFrustum) {
  80. near = frustum.near;
  81. top = frustum.top;
  82. right = frustum.right;
  83. return Math.max(dx * near / right, dy * near / top);
  84. }
  85. return Math.max(dx, dy);
  86. }
  87. var scratchCart = new Cartesian3();
  88. var scratchCart2 = new Cartesian3();
  89. var scratchCart3 = new Cartesian3();
  90. var scratchCart4 = new Cartesian3();
  91. var rotMatrixScratch = new Matrix3();
  92. function createPath3D(camera, ellipsoid, start, up, right, end, duration) {
  93. // get minimum altitude from which the whole ellipsoid is visible
  94. var radius = ellipsoid.maximumRadius;
  95. var frustum = camera.frustum;
  96. var maxStartAlt = getAltitude(frustum, radius, radius);
  97. var dot = Cartesian3.dot(Cartesian3.normalize(start, scratchCart), Cartesian3.normalize(end, scratchCart2));
  98. var points;
  99. var altitude;
  100. var incrementPercentage;
  101. if (Cartesian3.magnitude(start) > maxStartAlt) {
  102. altitude = radius + 0.6 * (maxStartAlt - radius);
  103. incrementPercentage = 0.35;
  104. } else {
  105. var diff = Cartesian3.subtract(start, end, scratchCart);
  106. altitude = Cartesian3.magnitude(Cartesian3.add(Cartesian3.multiplyByScalar(diff, 0.5, scratchCart2), end, scratchCart2));
  107. var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2));
  108. var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2));
  109. altitude += getAltitude(frustum, verticalDistance, horizontalDistance);
  110. incrementPercentage = CesiumMath.clamp(dot + 1.0, 0.25, 0.5);
  111. }
  112. var aboveEnd = Cartesian3.multiplyByScalar(Cartesian3.normalize(end, scratchCart2), altitude, scratchCart2);
  113. var afterStart = Cartesian3.multiplyByScalar(Cartesian3.normalize(start, scratchCart), altitude, scratchCart);
  114. var axis, angle, rotation;
  115. var middle = new Cartesian3();
  116. if (Cartesian3.magnitude(end) > maxStartAlt && dot > 0.75) {
  117. middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, end, middle), 0.5, middle), end, middle);
  118. points = [ start, middle, end ];
  119. } else if (Cartesian3.magnitude(start) > maxStartAlt && dot > 0) {
  120. middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, aboveEnd, middle), 0.5, middle), aboveEnd, middle);
  121. points = [ start, middle, end ];
  122. } else {
  123. points = [ start ];
  124. angle = CesiumMath.acosClamped(Cartesian3.dot(Cartesian3.normalize(afterStart, scratchCart3), Cartesian3.normalize(aboveEnd, scratchCart4)));
  125. axis = Cartesian3.cross(aboveEnd, afterStart, scratchCart3);
  126. if (Cartesian3.equalsEpsilon(axis, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
  127. axis = Cartesian3.UNIT_Z;
  128. }
  129. var increment = incrementPercentage * angle;
  130. var startCondition = angle - increment;
  131. for ( var i = startCondition; i > 0.0; i = i - increment) {
  132. rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, i), rotMatrixScratch);
  133. points.push(Matrix3.multiplyByVector(rotation, aboveEnd, new Cartesian3()));
  134. }
  135. points.push(end);
  136. }
  137. var times = new Array(points.length);
  138. var scalar = duration / (points.length - 1);
  139. for ( var k = 0; k < points.length; ++k) {
  140. times[k] = k * scalar;
  141. }
  142. return HermiteSpline.createNaturalCubic({
  143. points : points,
  144. times : times
  145. });
  146. }
  147. var direction3D = new Cartesian3();
  148. var right3D = new Cartesian3();
  149. var up3D = new Cartesian3();
  150. var quat3D = new Quaternion();
  151. function createOrientations3D(path, startDirection, startUp, endDirection, endUp) {
  152. var points = path.points;
  153. var orientations = new Array(points.length);
  154. orientations[0] = createQuaternion(startDirection, startUp);
  155. var point;
  156. var length = points.length - 1;
  157. for (var i = 1; i < length; ++i) {
  158. point = points[i];
  159. Cartesian3.normalize(Cartesian3.negate(point, direction3D), direction3D);
  160. Cartesian3.normalize(Cartesian3.cross(direction3D, Cartesian3.UNIT_Z, right3D), right3D);
  161. Cartesian3.cross(right3D, direction3D, up3D);
  162. orientations[i] = createQuaternion(direction3D, up3D, quat3D);
  163. }
  164. point = points[length];
  165. if (defined(endDirection) && defined(endUp)) {
  166. orientations[length] = createQuaternion(endDirection, endUp);
  167. } else {
  168. Cartesian3.normalize(Cartesian3.negate(point, direction3D), direction3D);
  169. Cartesian3.normalize(Cartesian3.cross(direction3D, Cartesian3.UNIT_Z, right3D), right3D);
  170. Cartesian3.cross(right3D, direction3D, up3D);
  171. orientations[length] = createQuaternion(direction3D, up3D, quat3D);
  172. }
  173. return new QuaternionSpline({
  174. points : orientations,
  175. times : path.times
  176. });
  177. }
  178. var scratchStartPosition = new Cartesian3();
  179. var scratchStartDirection = new Cartesian3();
  180. var scratchStartUp = new Cartesian3();
  181. var scratchStartRight = new Cartesian3();
  182. var currentFrame = new Matrix4();
  183. function createUpdate3D(scene, destination, duration, direction, up) {
  184. var camera = scene.camera;
  185. var ellipsoid = scene.mapProjection.ellipsoid;
  186. var start = camera.cameraToWorldCoordinatesPoint(camera.position, scratchStartPosition);
  187. var startDirection = camera.cameraToWorldCoordinatesVector(camera.direction, scratchStartDirection);
  188. var startUp = camera.cameraToWorldCoordinatesVector(camera.up, scratchStartUp);
  189. var startRight = Cartesian3.cross(startDirection, startUp, scratchStartRight);
  190. var path = createPath3D(camera, ellipsoid, start, startUp, startRight, destination, duration);
  191. var orientations = createOrientations3D(path, startDirection, startUp, direction, up);
  192. var update = function(value) {
  193. var time = value.time;
  194. var orientation = orientations.evaluate(time);
  195. Matrix3.fromQuaternion(orientation, rotMatrix);
  196. Matrix4.clone(camera.transform, currentFrame);
  197. Matrix4.clone(Matrix4.IDENTITY, camera.transform);
  198. camera.position = path.evaluate(time, camera.position);
  199. camera.right = Matrix3.getRow(rotMatrix, 0, camera.right);
  200. camera.up = Matrix3.getRow(rotMatrix, 1, camera.up);
  201. camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction);
  202. camera.setTransform(currentFrame);
  203. };
  204. return update;
  205. }
  206. var cartScratch1 = new Cartesian3();
  207. function createPath2D(camera, ellipsoid, start, end, duration) {
  208. if (CesiumMath.equalsEpsilon(Cartesian2.magnitude(start), Cartesian2.magnitude(end), 10000.0)) {
  209. return new LinearSpline({
  210. points : [start, end],
  211. times : [0.0, duration]
  212. });
  213. }
  214. // get minimum altitude from which the whole map is visible
  215. var radius = ellipsoid.maximumRadius;
  216. var frustum = camera.frustum;
  217. var maxStartAlt = getAltitude(frustum, Math.PI * radius, CesiumMath.PI_OVER_TWO * radius);
  218. var points;
  219. var altitude;
  220. var incrementPercentage = 0.5;
  221. if (start.z > maxStartAlt) {
  222. altitude = 0.6 * maxStartAlt;
  223. } else {
  224. var diff = Cartesian3.subtract(start, end, cartScratch1);
  225. altitude = getAltitude(frustum, Math.abs(diff.y), Math.abs(diff.x));
  226. }
  227. var aboveEnd = Cartesian3.clone(end);
  228. aboveEnd.z = altitude;
  229. var afterStart = Cartesian3.clone(start);
  230. afterStart.z = altitude;
  231. var middle = new Cartesian3();
  232. if (end.z > maxStartAlt) {
  233. middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, end, middle), 0.5, middle), end, middle);
  234. points = [ start, middle, end ];
  235. } else if (start.z > maxStartAlt) {
  236. middle = Cartesian3.add(Cartesian3.multiplyByScalar(Cartesian3.subtract(start, aboveEnd, middle), 0.5, middle), aboveEnd, middle);
  237. points = [ start, middle, end ];
  238. } else {
  239. points = [ start ];
  240. var v = Cartesian3.subtract(afterStart, aboveEnd, cartScratch1);
  241. var distance = Cartesian3.magnitude(v);
  242. Cartesian3.normalize(v, v);
  243. var increment = incrementPercentage * distance;
  244. var startCondition = distance - increment;
  245. for ( var i = startCondition; i > 0.0; i = i - increment) {
  246. var p = new Cartesian3();
  247. points.push(Cartesian3.add(Cartesian3.multiplyByScalar(v, i, p), aboveEnd, p));
  248. }
  249. points.push(end);
  250. }
  251. var times = new Array(points.length);
  252. var scalar = duration / (points.length - 1);
  253. for ( var k = 0; k < points.length; ++k) {
  254. times[k] = k * scalar;
  255. }
  256. return HermiteSpline.createNaturalCubic({
  257. points : points,
  258. times : times
  259. });
  260. }
  261. var direction2D = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
  262. var right2D = new Cartesian3();
  263. right2D = Cartesian3.normalize(Cartesian3.cross(direction2D, Cartesian3.UNIT_Y, right2D), right2D);
  264. var up2D = Cartesian3.cross(right2D, direction2D, new Cartesian3());
  265. var quat = createQuaternion(direction2D, up2D);
  266. function createOrientations2D(camera, path, endDirection, endUp) {
  267. var points = path.points;
  268. var orientations = new Array(points.length);
  269. orientations[0] = createQuaternion(camera.direction, camera.up);
  270. var length = points.length - 1;
  271. for (var i = 1; i < length; ++i) {
  272. orientations[i] = quat;
  273. }
  274. if (defined(endDirection) && defined(endUp)) {
  275. orientations[length] = createQuaternion(endDirection, endUp);
  276. } else {
  277. orientations[length] = quat;
  278. }
  279. return new QuaternionSpline({
  280. points : orientations,
  281. times : path.times
  282. });
  283. }
  284. function createUpdateCV(scene, destination, duration, direction, up) {
  285. var camera = scene.camera;
  286. var ellipsoid = scene.mapProjection.ellipsoid;
  287. var path = createPath2D(camera, ellipsoid, Cartesian3.clone(camera.position), destination, duration);
  288. var orientations = createOrientations2D(camera, path, direction, up);
  289. var update = function(value) {
  290. var time = value.time;
  291. var orientation = orientations.evaluate(time);
  292. Matrix3.fromQuaternion(orientation, rotMatrix);
  293. Matrix4.clone(camera.transform, currentFrame);
  294. Matrix4.clone(Matrix4.IDENTITY, camera.transform);
  295. camera.position = path.evaluate(time, camera.position);
  296. camera.right = Matrix3.getRow(rotMatrix, 0, camera.right);
  297. camera.up = Matrix3.getRow(rotMatrix, 1, camera.up);
  298. camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction);
  299. camera.setTransform(currentFrame);
  300. };
  301. return update;
  302. }
  303. function createUpdate2D(scene, destination, duration, direction, up) {
  304. var camera = scene.camera;
  305. var ellipsoid = scene.mapProjection.ellipsoid;
  306. var start = Cartesian3.clone(camera.position);
  307. start.z = camera.frustum.right - camera.frustum.left;
  308. var path = createPath2D(camera, ellipsoid, start, destination, duration);
  309. var orientations = createOrientations2D(camera, path, Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), up);
  310. var height = camera.position.z;
  311. var update = function(value) {
  312. var time = value.time;
  313. var orientation = orientations.evaluate(time);
  314. Matrix3.fromQuaternion(orientation, rotMatrix);
  315. camera.position = path.evaluate(time);
  316. var zoom = camera.position.z;
  317. camera.position.z = height;
  318. camera.right = Matrix3.getRow(rotMatrix, 0, camera.right);
  319. camera.up = Matrix3.getRow(rotMatrix, 1, camera.up);
  320. camera.direction = Cartesian3.negate(Matrix3.getRow(rotMatrix, 2, camera.direction), camera.direction);
  321. var frustum = camera.frustum;
  322. var ratio = frustum.top / frustum.right;
  323. var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
  324. frustum.right += incrementAmount;
  325. frustum.left -= incrementAmount;
  326. frustum.top = ratio * frustum.right;
  327. frustum.bottom = -frustum.top;
  328. };
  329. return update;
  330. }
  331. var dirScratch = new Cartesian3();
  332. var rightScratch = new Cartesian3();
  333. var upScratch = new Cartesian3();
  334. var scratchCartographic = new Cartographic();
  335. var scratchDestination = new Cartesian3();
  336. CameraFlightPath.createTween = function(scene, options) {
  337. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  338. var destination = options.destination;
  339. var direction = options.direction;
  340. var up = options.up;
  341. //>>includeStart('debug', pragmas.debug);
  342. if (!defined(scene)) {
  343. throw new DeveloperError('scene is required.');
  344. }
  345. if (!defined(destination)) {
  346. throw new DeveloperError('destination is required.');
  347. }
  348. if ((defined(direction) && !defined(up)) || (defined(up) && !defined(direction))) {
  349. throw new DeveloperError('If either direction or up is given, then both are required.');
  350. }
  351. //>>includeEnd('debug');
  352. if (scene.mode === SceneMode.MORPHING) {
  353. return {
  354. startObject : {},
  355. stopObject: {},
  356. duration : 0.0
  357. };
  358. }
  359. var convert = defaultValue(options.convert, true);
  360. if (convert && scene.mode !== SceneMode.SCENE3D) {
  361. var projection = scene.mapProjection;
  362. var ellipsoid = projection.ellipsoid;
  363. ellipsoid.cartesianToCartographic(destination, scratchCartographic);
  364. destination = projection.project(scratchCartographic, scratchDestination);
  365. }
  366. var duration = defaultValue(options.duration, 3.0);
  367. var controller = scene.screenSpaceCameraController;
  368. controller.enableInputs = false;
  369. var wrapCallback = function(cb) {
  370. var wrapped = function() {
  371. if (typeof cb === 'function') {
  372. cb();
  373. }
  374. controller.enableInputs = true;
  375. };
  376. return wrapped;
  377. };
  378. var complete = wrapCallback(options.complete);
  379. var cancel = wrapCallback(options.cancel);
  380. var camera = scene.camera;
  381. var transform = options.endTransform;
  382. if (defined(transform)) {
  383. camera.setTransform(transform);
  384. }
  385. var frustum = camera.frustum;
  386. if (scene.mode === SceneMode.SCENE2D) {
  387. if (Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6) && (CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6))) {
  388. return {
  389. startObject : {},
  390. stopObject: {},
  391. duration : 0.0,
  392. complete : complete,
  393. cancel: cancel
  394. };
  395. }
  396. } else if (Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON6)) {
  397. return {
  398. startObject : {},
  399. stopObject: {},
  400. duration : 0.0,
  401. complete : complete,
  402. cancel: cancel
  403. };
  404. }
  405. if (duration <= 0.0) {
  406. var newOnComplete = function() {
  407. var position = destination;
  408. if (scene.mode === SceneMode.SCENE3D) {
  409. if (!defined(options.direction) && !defined(options.up)){
  410. dirScratch = Cartesian3.normalize(Cartesian3.negate(position, dirScratch), dirScratch);
  411. rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, Cartesian3.UNIT_Z, rightScratch), rightScratch);
  412. } else {
  413. dirScratch = options.direction;
  414. rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, options.up, rightScratch), rightScratch);
  415. }
  416. upScratch = defaultValue(options.up, Cartesian3.cross(rightScratch, dirScratch, upScratch));
  417. } else {
  418. if (!defined(options.direction) && !defined(options.up)){
  419. dirScratch = Cartesian3.negate(Cartesian3.UNIT_Z, dirScratch);
  420. rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, Cartesian3.UNIT_Y, rightScratch), rightScratch);
  421. } else {
  422. dirScratch = options.direction;
  423. rightScratch = Cartesian3.normalize(Cartesian3.cross(dirScratch, options.up, rightScratch), rightScratch);
  424. }
  425. upScratch = defaultValue(options.up, Cartesian3.cross(rightScratch, dirScratch, upScratch));
  426. }
  427. Cartesian3.clone(position, camera.position);
  428. Cartesian3.clone(dirScratch, camera.direction);
  429. Cartesian3.clone(upScratch, camera.up);
  430. Cartesian3.clone(rightScratch, camera.right);
  431. if (scene.mode === SceneMode.SCENE2D) {
  432. var zoom = camera.position.z;
  433. var ratio = frustum.top / frustum.right;
  434. var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
  435. frustum.right += incrementAmount;
  436. frustum.left -= incrementAmount;
  437. frustum.top = ratio * frustum.right;
  438. frustum.bottom = -frustum.top;
  439. }
  440. if (typeof complete === 'function') {
  441. complete();
  442. }
  443. };
  444. return {
  445. startObject : {},
  446. stopObject: {},
  447. duration : 0.0,
  448. complete : newOnComplete,
  449. cancel: cancel
  450. };
  451. }
  452. var update;
  453. if (scene.mode === SceneMode.SCENE3D) {
  454. update = createUpdate3D(scene, destination, duration, direction, up);
  455. } else if (scene.mode === SceneMode.SCENE2D) {
  456. update = createUpdate2D(scene, destination, duration, direction, up);
  457. } else {
  458. update = createUpdateCV(scene, destination, duration, direction, up);
  459. }
  460. return {
  461. duration : duration,
  462. easingFunction : EasingFunction.SINUSOIDAL_IN_OUT,
  463. startObject : {
  464. time : 0.0
  465. },
  466. stopObject : {
  467. time : duration
  468. },
  469. update : update,
  470. complete : complete,
  471. cancel: cancel
  472. };
  473. };
  474. CameraFlightPath.createTweenRectangle = function(scene, options) {
  475. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  476. var rectangle = options.destination;
  477. //>>includeStart('debug', pragmas.debug);
  478. if (!defined(scene)) {
  479. throw new DeveloperError('scene is required.');
  480. }
  481. if (!defined(rectangle)) {
  482. throw new DeveloperError('options.destination is required.');
  483. }
  484. //>>includeEnd('debug');
  485. var createAnimationoptions = clone(options);
  486. scene.camera.getRectangleCameraCoordinates(rectangle, c3destination);
  487. createAnimationoptions.destination = c3destination;
  488. createAnimationoptions.convert = false;
  489. return this.createTween(scene, createAnimationoptions);
  490. };
  491. return CameraFlightPath;
  492. });