Camera.js 119 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952
  1. import BoundingSphere from "../Core/BoundingSphere.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 defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import EasingFunction from "../Core/EasingFunction.js";
  10. import Ellipsoid from "../Core/Ellipsoid.js";
  11. import EllipsoidGeodesic from "../Core/EllipsoidGeodesic.js";
  12. import Event from "../Core/Event.js";
  13. import getTimestamp from "../Core/getTimestamp.js";
  14. import HeadingPitchRange from "../Core/HeadingPitchRange.js";
  15. import HeadingPitchRoll from "../Core/HeadingPitchRoll.js";
  16. import Intersect from "../Core/Intersect.js";
  17. import IntersectionTests from "../Core/IntersectionTests.js";
  18. import CesiumMath from "../Core/Math.js";
  19. import Matrix3 from "../Core/Matrix3.js";
  20. import Matrix4 from "../Core/Matrix4.js";
  21. import OrthographicFrustum from "../Core/OrthographicFrustum.js";
  22. import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
  23. import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
  24. import Quaternion from "../Core/Quaternion.js";
  25. import Ray from "../Core/Ray.js";
  26. import Rectangle from "../Core/Rectangle.js";
  27. import Transforms from "../Core/Transforms.js";
  28. import CameraFlightPath from "./CameraFlightPath.js";
  29. import MapMode2D from "./MapMode2D.js";
  30. import SceneMode from "./SceneMode.js";
  31. /**
  32. * @typedef {object} DirectionUp
  33. *
  34. * An orientation given by a pair of unit vectors
  35. *
  36. * @property {Cartesian3} direction The unit "direction" vector
  37. * @property {Cartesian3} up The unit "up" vector
  38. **/
  39. /**
  40. * @typedef {object} HeadingPitchRollValues
  41. *
  42. * An orientation given by numeric heading, pitch, and roll
  43. *
  44. * @property {number} [heading=0.0] The heading in radians
  45. * @property {number} [pitch=-CesiumMath.PI_OVER_TWO] The pitch in radians
  46. * @property {number} [roll=0.0] The roll in meters
  47. **/
  48. /**
  49. * The camera is defined by a position, orientation, and view frustum.
  50. * <br /><br />
  51. * The orientation forms an orthonormal basis with a view, up and right = view x up unit vectors.
  52. * <br /><br />
  53. * The viewing frustum is defined by 6 planes.
  54. * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
  55. * define the unit vector normal to the plane, and the w component is the distance of the
  56. * plane from the origin/camera position.
  57. *
  58. * @alias Camera
  59. *
  60. * @constructor
  61. *
  62. * @param {Scene} scene The scene.
  63. *
  64. * @demo {@link https://sandcastle.cesium.com/index.html?src=Camera.html|Cesium Sandcastle Camera Demo}
  65. * @demo {@link https://sandcastle.cesium.com/index.html?src=Camera%20Tutorial.html|Cesium Sandcastle Camera Tutorial Example}
  66. * @demo {@link https://cesium.com/learn/cesiumjs-learn/cesiumjs-camera|Camera Tutorial}
  67. *
  68. * @example
  69. * // Create a camera looking down the negative z-axis, positioned at the origin,
  70. * // with a field of view of 60 degrees, and 1:1 aspect ratio.
  71. * const camera = new Cesium.Camera(scene);
  72. * camera.position = new Cesium.Cartesian3();
  73. * camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3());
  74. * camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y);
  75. * camera.frustum.fov = Cesium.Math.PI_OVER_THREE;
  76. * camera.frustum.near = 1.0;
  77. * camera.frustum.far = 2.0;
  78. */
  79. function Camera(scene) {
  80. //>>includeStart('debug', pragmas.debug);
  81. if (!defined(scene)) {
  82. throw new DeveloperError("scene is required.");
  83. }
  84. //>>includeEnd('debug');
  85. this._scene = scene;
  86. this._transform = Matrix4.clone(Matrix4.IDENTITY);
  87. this._invTransform = Matrix4.clone(Matrix4.IDENTITY);
  88. this._actualTransform = Matrix4.clone(Matrix4.IDENTITY);
  89. this._actualInvTransform = Matrix4.clone(Matrix4.IDENTITY);
  90. this._transformChanged = false;
  91. /**
  92. * The position of the camera.
  93. *
  94. * @type {Cartesian3}
  95. */
  96. this.position = new Cartesian3();
  97. this._position = new Cartesian3();
  98. this._positionWC = new Cartesian3();
  99. this._positionCartographic = new Cartographic();
  100. this._oldPositionWC = undefined;
  101. /**
  102. * The position delta magnitude.
  103. *
  104. * @private
  105. */
  106. this.positionWCDeltaMagnitude = 0.0;
  107. /**
  108. * The position delta magnitude last frame.
  109. *
  110. * @private
  111. */
  112. this.positionWCDeltaMagnitudeLastFrame = 0.0;
  113. /**
  114. * How long in seconds since the camera has stopped moving
  115. *
  116. * @private
  117. */
  118. this.timeSinceMoved = 0.0;
  119. this._lastMovedTimestamp = 0.0;
  120. /**
  121. * The view direction of the camera.
  122. *
  123. * @type {Cartesian3}
  124. */
  125. this.direction = new Cartesian3();
  126. this._direction = new Cartesian3();
  127. this._directionWC = new Cartesian3();
  128. /**
  129. * The up direction of the camera.
  130. *
  131. * @type {Cartesian3}
  132. */
  133. this.up = new Cartesian3();
  134. this._up = new Cartesian3();
  135. this._upWC = new Cartesian3();
  136. /**
  137. * The right direction of the camera.
  138. *
  139. * @type {Cartesian3}
  140. */
  141. this.right = new Cartesian3();
  142. this._right = new Cartesian3();
  143. this._rightWC = new Cartesian3();
  144. /**
  145. * The region of space in view.
  146. *
  147. * @type {PerspectiveFrustum|PerspectiveOffCenterFrustum|OrthographicFrustum}
  148. * @default PerspectiveFrustum()
  149. *
  150. * @see PerspectiveFrustum
  151. * @see PerspectiveOffCenterFrustum
  152. * @see OrthographicFrustum
  153. */
  154. this.frustum = new PerspectiveFrustum();
  155. this.frustum.aspectRatio =
  156. scene.drawingBufferWidth / scene.drawingBufferHeight;
  157. this.frustum.fov = CesiumMath.toRadians(60.0);
  158. /**
  159. * The default amount to move the camera when an argument is not
  160. * provided to the move methods.
  161. * @type {number}
  162. * @default 100000.0;
  163. */
  164. this.defaultMoveAmount = 100000.0;
  165. /**
  166. * The default amount to rotate the camera when an argument is not
  167. * provided to the look methods.
  168. * @type {number}
  169. * @default Math.PI / 60.0
  170. */
  171. this.defaultLookAmount = Math.PI / 60.0;
  172. /**
  173. * The default amount to rotate the camera when an argument is not
  174. * provided to the rotate methods.
  175. * @type {number}
  176. * @default Math.PI / 3600.0
  177. */
  178. this.defaultRotateAmount = Math.PI / 3600.0;
  179. /**
  180. * The default amount to move the camera when an argument is not
  181. * provided to the zoom methods.
  182. * @type {number}
  183. * @default 100000.0;
  184. */
  185. this.defaultZoomAmount = 100000.0;
  186. /**
  187. * If set, the camera will not be able to rotate past this axis in either direction.
  188. * @type {Cartesian3}
  189. * @default undefined
  190. */
  191. this.constrainedAxis = undefined;
  192. /**
  193. * The factor multiplied by the the map size used to determine where to clamp the camera position
  194. * when zooming out from the surface. The default is 1.5. Only valid for 2D and the map is rotatable.
  195. * @type {number}
  196. * @default 1.5
  197. */
  198. this.maximumZoomFactor = 1.5;
  199. this._moveStart = new Event();
  200. this._moveEnd = new Event();
  201. this._changed = new Event();
  202. this._changedPosition = undefined;
  203. this._changedDirection = undefined;
  204. this._changedFrustum = undefined;
  205. this._changedHeading = undefined;
  206. /**
  207. * The amount the camera has to change before the <code>changed</code> event is raised. The value is a percentage in the [0, 1] range.
  208. * @type {number}
  209. * @default 0.5
  210. */
  211. this.percentageChanged = 0.5;
  212. this._viewMatrix = new Matrix4();
  213. this._invViewMatrix = new Matrix4();
  214. updateViewMatrix(this);
  215. this._mode = SceneMode.SCENE3D;
  216. this._modeChanged = true;
  217. const projection = scene.mapProjection;
  218. this._projection = projection;
  219. this._maxCoord = projection.project(
  220. new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)
  221. );
  222. this._max2Dfrustum = undefined;
  223. // set default view
  224. rectangleCameraPosition3D(
  225. this,
  226. Camera.DEFAULT_VIEW_RECTANGLE,
  227. this.position,
  228. true
  229. );
  230. let mag = Cartesian3.magnitude(this.position);
  231. mag += mag * Camera.DEFAULT_VIEW_FACTOR;
  232. Cartesian3.normalize(this.position, this.position);
  233. Cartesian3.multiplyByScalar(this.position, mag, this.position);
  234. }
  235. /**
  236. * @private
  237. */
  238. Camera.TRANSFORM_2D = new Matrix4(
  239. 0.0,
  240. 0.0,
  241. 1.0,
  242. 0.0,
  243. 1.0,
  244. 0.0,
  245. 0.0,
  246. 0.0,
  247. 0.0,
  248. 1.0,
  249. 0.0,
  250. 0.0,
  251. 0.0,
  252. 0.0,
  253. 0.0,
  254. 1.0
  255. );
  256. /**
  257. * @private
  258. */
  259. Camera.TRANSFORM_2D_INVERSE = Matrix4.inverseTransformation(
  260. Camera.TRANSFORM_2D,
  261. new Matrix4()
  262. );
  263. /**
  264. * The default rectangle the camera will view on creation.
  265. * @type Rectangle
  266. */
  267. Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(
  268. -95.0,
  269. -20.0,
  270. -70.0,
  271. 90.0
  272. );
  273. /**
  274. * A scalar to multiply to the camera position and add it back after setting the camera to view the rectangle.
  275. * A value of zero means the camera will view the entire {@link Camera#DEFAULT_VIEW_RECTANGLE}, a value greater than zero
  276. * will move it further away from the extent, and a value less than zero will move it close to the extent.
  277. * @type {number}
  278. */
  279. Camera.DEFAULT_VIEW_FACTOR = 0.5;
  280. /**
  281. * The default heading/pitch/range that is used when the camera flies to a location that contains a bounding sphere.
  282. * @type HeadingPitchRange
  283. */
  284. Camera.DEFAULT_OFFSET = new HeadingPitchRange(
  285. 0.0,
  286. -CesiumMath.PI_OVER_FOUR,
  287. 0.0
  288. );
  289. function updateViewMatrix(camera) {
  290. Matrix4.computeView(
  291. camera._position,
  292. camera._direction,
  293. camera._up,
  294. camera._right,
  295. camera._viewMatrix
  296. );
  297. Matrix4.multiply(
  298. camera._viewMatrix,
  299. camera._actualInvTransform,
  300. camera._viewMatrix
  301. );
  302. Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix);
  303. }
  304. function updateCameraDeltas(camera) {
  305. if (!defined(camera._oldPositionWC)) {
  306. camera._oldPositionWC = Cartesian3.clone(
  307. camera.positionWC,
  308. camera._oldPositionWC
  309. );
  310. } else {
  311. camera.positionWCDeltaMagnitudeLastFrame = camera.positionWCDeltaMagnitude;
  312. const delta = Cartesian3.subtract(
  313. camera.positionWC,
  314. camera._oldPositionWC,
  315. camera._oldPositionWC
  316. );
  317. camera.positionWCDeltaMagnitude = Cartesian3.magnitude(delta);
  318. camera._oldPositionWC = Cartesian3.clone(
  319. camera.positionWC,
  320. camera._oldPositionWC
  321. );
  322. // Update move timers
  323. if (camera.positionWCDeltaMagnitude > 0.0) {
  324. camera.timeSinceMoved = 0.0;
  325. camera._lastMovedTimestamp = getTimestamp();
  326. } else {
  327. camera.timeSinceMoved =
  328. Math.max(getTimestamp() - camera._lastMovedTimestamp, 0.0) / 1000.0;
  329. }
  330. }
  331. }
  332. /**
  333. * Checks if there's a camera flight with preload for this camera.
  334. *
  335. * @returns {boolean} Whether or not this camera has a current flight with a valid preloadFlightCamera in scene.
  336. *
  337. * @private
  338. *
  339. */
  340. Camera.prototype.canPreloadFlight = function () {
  341. return defined(this._currentFlight) && this._mode !== SceneMode.SCENE2D;
  342. };
  343. Camera.prototype._updateCameraChanged = function () {
  344. const camera = this;
  345. updateCameraDeltas(camera);
  346. if (camera._changed.numberOfListeners === 0) {
  347. return;
  348. }
  349. const percentageChanged = camera.percentageChanged;
  350. const currentHeading = camera.heading;
  351. if (!defined(camera._changedHeading)) {
  352. camera._changedHeading = currentHeading;
  353. }
  354. let delta =
  355. Math.abs(camera._changedHeading - currentHeading) % CesiumMath.TWO_PI;
  356. delta = delta > CesiumMath.PI ? CesiumMath.TWO_PI - delta : delta;
  357. // Since delta is computed as the shortest distance between two angles
  358. // the percentage is relative to the half circle.
  359. const headingChangedPercentage = delta / Math.PI;
  360. if (headingChangedPercentage > percentageChanged) {
  361. camera._changed.raiseEvent(headingChangedPercentage);
  362. camera._changedHeading = currentHeading;
  363. }
  364. if (camera._mode === SceneMode.SCENE2D) {
  365. if (!defined(camera._changedFrustum)) {
  366. camera._changedPosition = Cartesian3.clone(
  367. camera.position,
  368. camera._changedPosition
  369. );
  370. camera._changedFrustum = camera.frustum.clone();
  371. return;
  372. }
  373. const position = camera.position;
  374. const lastPosition = camera._changedPosition;
  375. const frustum = camera.frustum;
  376. const lastFrustum = camera._changedFrustum;
  377. const x0 = position.x + frustum.left;
  378. const x1 = position.x + frustum.right;
  379. const x2 = lastPosition.x + lastFrustum.left;
  380. const x3 = lastPosition.x + lastFrustum.right;
  381. const y0 = position.y + frustum.bottom;
  382. const y1 = position.y + frustum.top;
  383. const y2 = lastPosition.y + lastFrustum.bottom;
  384. const y3 = lastPosition.y + lastFrustum.top;
  385. const leftX = Math.max(x0, x2);
  386. const rightX = Math.min(x1, x3);
  387. const bottomY = Math.max(y0, y2);
  388. const topY = Math.min(y1, y3);
  389. let areaPercentage;
  390. if (leftX >= rightX || bottomY >= y1) {
  391. areaPercentage = 1.0;
  392. } else {
  393. let areaRef = lastFrustum;
  394. if (x0 < x2 && x1 > x3 && y0 < y2 && y1 > y3) {
  395. areaRef = frustum;
  396. }
  397. areaPercentage =
  398. 1.0 -
  399. ((rightX - leftX) * (topY - bottomY)) /
  400. ((areaRef.right - areaRef.left) * (areaRef.top - areaRef.bottom));
  401. }
  402. if (areaPercentage > percentageChanged) {
  403. camera._changed.raiseEvent(areaPercentage);
  404. camera._changedPosition = Cartesian3.clone(
  405. camera.position,
  406. camera._changedPosition
  407. );
  408. camera._changedFrustum = camera.frustum.clone(camera._changedFrustum);
  409. }
  410. return;
  411. }
  412. if (!defined(camera._changedDirection)) {
  413. camera._changedPosition = Cartesian3.clone(
  414. camera.positionWC,
  415. camera._changedPosition
  416. );
  417. camera._changedDirection = Cartesian3.clone(
  418. camera.directionWC,
  419. camera._changedDirection
  420. );
  421. return;
  422. }
  423. const dirAngle = CesiumMath.acosClamped(
  424. Cartesian3.dot(camera.directionWC, camera._changedDirection)
  425. );
  426. let dirPercentage;
  427. if (defined(camera.frustum.fovy)) {
  428. dirPercentage = dirAngle / (camera.frustum.fovy * 0.5);
  429. } else {
  430. dirPercentage = dirAngle;
  431. }
  432. const distance = Cartesian3.distance(
  433. camera.positionWC,
  434. camera._changedPosition
  435. );
  436. const heightPercentage = distance / camera.positionCartographic.height;
  437. if (
  438. dirPercentage > percentageChanged ||
  439. heightPercentage > percentageChanged
  440. ) {
  441. camera._changed.raiseEvent(Math.max(dirPercentage, heightPercentage));
  442. camera._changedPosition = Cartesian3.clone(
  443. camera.positionWC,
  444. camera._changedPosition
  445. );
  446. camera._changedDirection = Cartesian3.clone(
  447. camera.directionWC,
  448. camera._changedDirection
  449. );
  450. }
  451. };
  452. function convertTransformForColumbusView(camera) {
  453. Transforms.basisTo2D(
  454. camera._projection,
  455. camera._transform,
  456. camera._actualTransform
  457. );
  458. }
  459. const scratchCartographic = new Cartographic();
  460. const scratchCartesian3Projection = new Cartesian3();
  461. const scratchCartesian3 = new Cartesian3();
  462. const scratchCartesian4Origin = new Cartesian4();
  463. const scratchCartesian4NewOrigin = new Cartesian4();
  464. const scratchCartesian4NewXAxis = new Cartesian4();
  465. const scratchCartesian4NewYAxis = new Cartesian4();
  466. const scratchCartesian4NewZAxis = new Cartesian4();
  467. function convertTransformFor2D(camera) {
  468. const projection = camera._projection;
  469. const ellipsoid = projection.ellipsoid;
  470. const origin = Matrix4.getColumn(
  471. camera._transform,
  472. 3,
  473. scratchCartesian4Origin
  474. );
  475. const cartographic = ellipsoid.cartesianToCartographic(
  476. origin,
  477. scratchCartographic
  478. );
  479. const projectedPosition = projection.project(
  480. cartographic,
  481. scratchCartesian3Projection
  482. );
  483. const newOrigin = scratchCartesian4NewOrigin;
  484. newOrigin.x = projectedPosition.z;
  485. newOrigin.y = projectedPosition.x;
  486. newOrigin.z = projectedPosition.y;
  487. newOrigin.w = 1.0;
  488. const newZAxis = Cartesian4.clone(
  489. Cartesian4.UNIT_X,
  490. scratchCartesian4NewZAxis
  491. );
  492. const xAxis = Cartesian4.add(
  493. Matrix4.getColumn(camera._transform, 0, scratchCartesian3),
  494. origin,
  495. scratchCartesian3
  496. );
  497. ellipsoid.cartesianToCartographic(xAxis, cartographic);
  498. projection.project(cartographic, projectedPosition);
  499. const newXAxis = scratchCartesian4NewXAxis;
  500. newXAxis.x = projectedPosition.z;
  501. newXAxis.y = projectedPosition.x;
  502. newXAxis.z = projectedPosition.y;
  503. newXAxis.w = 0.0;
  504. Cartesian3.subtract(newXAxis, newOrigin, newXAxis);
  505. newXAxis.x = 0.0;
  506. const newYAxis = scratchCartesian4NewYAxis;
  507. if (Cartesian3.magnitudeSquared(newXAxis) > CesiumMath.EPSILON10) {
  508. Cartesian3.cross(newZAxis, newXAxis, newYAxis);
  509. } else {
  510. const yAxis = Cartesian4.add(
  511. Matrix4.getColumn(camera._transform, 1, scratchCartesian3),
  512. origin,
  513. scratchCartesian3
  514. );
  515. ellipsoid.cartesianToCartographic(yAxis, cartographic);
  516. projection.project(cartographic, projectedPosition);
  517. newYAxis.x = projectedPosition.z;
  518. newYAxis.y = projectedPosition.x;
  519. newYAxis.z = projectedPosition.y;
  520. newYAxis.w = 0.0;
  521. Cartesian3.subtract(newYAxis, newOrigin, newYAxis);
  522. newYAxis.x = 0.0;
  523. if (Cartesian3.magnitudeSquared(newYAxis) < CesiumMath.EPSILON10) {
  524. Cartesian4.clone(Cartesian4.UNIT_Y, newXAxis);
  525. Cartesian4.clone(Cartesian4.UNIT_Z, newYAxis);
  526. }
  527. }
  528. Cartesian3.cross(newYAxis, newZAxis, newXAxis);
  529. Cartesian3.normalize(newXAxis, newXAxis);
  530. Cartesian3.cross(newZAxis, newXAxis, newYAxis);
  531. Cartesian3.normalize(newYAxis, newYAxis);
  532. Matrix4.setColumn(
  533. camera._actualTransform,
  534. 0,
  535. newXAxis,
  536. camera._actualTransform
  537. );
  538. Matrix4.setColumn(
  539. camera._actualTransform,
  540. 1,
  541. newYAxis,
  542. camera._actualTransform
  543. );
  544. Matrix4.setColumn(
  545. camera._actualTransform,
  546. 2,
  547. newZAxis,
  548. camera._actualTransform
  549. );
  550. Matrix4.setColumn(
  551. camera._actualTransform,
  552. 3,
  553. newOrigin,
  554. camera._actualTransform
  555. );
  556. }
  557. const scratchCartesian = new Cartesian3();
  558. function updateMembers(camera) {
  559. const mode = camera._mode;
  560. let heightChanged = false;
  561. let height = 0.0;
  562. if (mode === SceneMode.SCENE2D) {
  563. height = camera.frustum.right - camera.frustum.left;
  564. heightChanged = height !== camera._positionCartographic.height;
  565. }
  566. let position = camera._position;
  567. const positionChanged =
  568. !Cartesian3.equals(position, camera.position) || heightChanged;
  569. if (positionChanged) {
  570. position = Cartesian3.clone(camera.position, camera._position);
  571. }
  572. let direction = camera._direction;
  573. const directionChanged = !Cartesian3.equals(direction, camera.direction);
  574. if (directionChanged) {
  575. Cartesian3.normalize(camera.direction, camera.direction);
  576. direction = Cartesian3.clone(camera.direction, camera._direction);
  577. }
  578. let up = camera._up;
  579. const upChanged = !Cartesian3.equals(up, camera.up);
  580. if (upChanged) {
  581. Cartesian3.normalize(camera.up, camera.up);
  582. up = Cartesian3.clone(camera.up, camera._up);
  583. }
  584. let right = camera._right;
  585. const rightChanged = !Cartesian3.equals(right, camera.right);
  586. if (rightChanged) {
  587. Cartesian3.normalize(camera.right, camera.right);
  588. right = Cartesian3.clone(camera.right, camera._right);
  589. }
  590. const transformChanged = camera._transformChanged || camera._modeChanged;
  591. camera._transformChanged = false;
  592. if (transformChanged) {
  593. Matrix4.inverseTransformation(camera._transform, camera._invTransform);
  594. if (
  595. camera._mode === SceneMode.COLUMBUS_VIEW ||
  596. camera._mode === SceneMode.SCENE2D
  597. ) {
  598. if (Matrix4.equals(Matrix4.IDENTITY, camera._transform)) {
  599. Matrix4.clone(Camera.TRANSFORM_2D, camera._actualTransform);
  600. } else if (camera._mode === SceneMode.COLUMBUS_VIEW) {
  601. convertTransformForColumbusView(camera);
  602. } else {
  603. convertTransformFor2D(camera);
  604. }
  605. } else {
  606. Matrix4.clone(camera._transform, camera._actualTransform);
  607. }
  608. Matrix4.inverseTransformation(
  609. camera._actualTransform,
  610. camera._actualInvTransform
  611. );
  612. camera._modeChanged = false;
  613. }
  614. const transform = camera._actualTransform;
  615. if (positionChanged || transformChanged) {
  616. camera._positionWC = Matrix4.multiplyByPoint(
  617. transform,
  618. position,
  619. camera._positionWC
  620. );
  621. // Compute the Cartographic position of the camera.
  622. if (mode === SceneMode.SCENE3D || mode === SceneMode.MORPHING) {
  623. camera._positionCartographic = camera._projection.ellipsoid.cartesianToCartographic(
  624. camera._positionWC,
  625. camera._positionCartographic
  626. );
  627. } else {
  628. // The camera position is expressed in the 2D coordinate system where the Y axis is to the East,
  629. // the Z axis is to the North, and the X axis is out of the map. Express them instead in the ENU axes where
  630. // X is to the East, Y is to the North, and Z is out of the local horizontal plane.
  631. const positionENU = scratchCartesian;
  632. positionENU.x = camera._positionWC.y;
  633. positionENU.y = camera._positionWC.z;
  634. positionENU.z = camera._positionWC.x;
  635. // In 2D, the camera height is always 12.7 million meters.
  636. // The apparent height is equal to half the frustum width.
  637. if (mode === SceneMode.SCENE2D) {
  638. positionENU.z = height;
  639. }
  640. camera._projection.unproject(positionENU, camera._positionCartographic);
  641. }
  642. }
  643. if (directionChanged || upChanged || rightChanged) {
  644. const det = Cartesian3.dot(
  645. direction,
  646. Cartesian3.cross(up, right, scratchCartesian)
  647. );
  648. if (Math.abs(1.0 - det) > CesiumMath.EPSILON2) {
  649. //orthonormalize axes
  650. const invUpMag = 1.0 / Cartesian3.magnitudeSquared(up);
  651. const scalar = Cartesian3.dot(up, direction) * invUpMag;
  652. const w0 = Cartesian3.multiplyByScalar(
  653. direction,
  654. scalar,
  655. scratchCartesian
  656. );
  657. up = Cartesian3.normalize(
  658. Cartesian3.subtract(up, w0, camera._up),
  659. camera._up
  660. );
  661. Cartesian3.clone(up, camera.up);
  662. right = Cartesian3.cross(direction, up, camera._right);
  663. Cartesian3.clone(right, camera.right);
  664. }
  665. }
  666. if (directionChanged || transformChanged) {
  667. camera._directionWC = Matrix4.multiplyByPointAsVector(
  668. transform,
  669. direction,
  670. camera._directionWC
  671. );
  672. Cartesian3.normalize(camera._directionWC, camera._directionWC);
  673. }
  674. if (upChanged || transformChanged) {
  675. camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC);
  676. Cartesian3.normalize(camera._upWC, camera._upWC);
  677. }
  678. if (rightChanged || transformChanged) {
  679. camera._rightWC = Matrix4.multiplyByPointAsVector(
  680. transform,
  681. right,
  682. camera._rightWC
  683. );
  684. Cartesian3.normalize(camera._rightWC, camera._rightWC);
  685. }
  686. if (
  687. positionChanged ||
  688. directionChanged ||
  689. upChanged ||
  690. rightChanged ||
  691. transformChanged
  692. ) {
  693. updateViewMatrix(camera);
  694. }
  695. }
  696. function getHeading(direction, up) {
  697. let heading;
  698. if (
  699. !CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)
  700. ) {
  701. heading = Math.atan2(direction.y, direction.x) - CesiumMath.PI_OVER_TWO;
  702. } else {
  703. heading = Math.atan2(up.y, up.x) - CesiumMath.PI_OVER_TWO;
  704. }
  705. return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(heading);
  706. }
  707. function getPitch(direction) {
  708. return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(direction.z);
  709. }
  710. function getRoll(direction, up, right) {
  711. let roll = 0.0;
  712. if (
  713. !CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)
  714. ) {
  715. roll = Math.atan2(-right.z, up.z);
  716. roll = CesiumMath.zeroToTwoPi(roll + CesiumMath.TWO_PI);
  717. }
  718. return roll;
  719. }
  720. const scratchHPRMatrix1 = new Matrix4();
  721. const scratchHPRMatrix2 = new Matrix4();
  722. Object.defineProperties(Camera.prototype, {
  723. /**
  724. * Gets the camera's reference frame. The inverse of this transformation is appended to the view matrix.
  725. * @memberof Camera.prototype
  726. *
  727. * @type {Matrix4}
  728. * @readonly
  729. *
  730. * @default {@link Matrix4.IDENTITY}
  731. */
  732. transform: {
  733. get: function () {
  734. return this._transform;
  735. },
  736. },
  737. /**
  738. * Gets the inverse camera transform.
  739. * @memberof Camera.prototype
  740. *
  741. * @type {Matrix4}
  742. * @readonly
  743. *
  744. * @default {@link Matrix4.IDENTITY}
  745. */
  746. inverseTransform: {
  747. get: function () {
  748. updateMembers(this);
  749. return this._invTransform;
  750. },
  751. },
  752. /**
  753. * Gets the view matrix.
  754. * @memberof Camera.prototype
  755. *
  756. * @type {Matrix4}
  757. * @readonly
  758. *
  759. * @see Camera#inverseViewMatrix
  760. */
  761. viewMatrix: {
  762. get: function () {
  763. updateMembers(this);
  764. return this._viewMatrix;
  765. },
  766. },
  767. /**
  768. * Gets the inverse view matrix.
  769. * @memberof Camera.prototype
  770. *
  771. * @type {Matrix4}
  772. * @readonly
  773. *
  774. * @see Camera#viewMatrix
  775. */
  776. inverseViewMatrix: {
  777. get: function () {
  778. updateMembers(this);
  779. return this._invViewMatrix;
  780. },
  781. },
  782. /**
  783. * Gets the {@link Cartographic} position of the camera, with longitude and latitude
  784. * expressed in radians and height in meters. In 2D and Columbus View, it is possible
  785. * for the returned longitude and latitude to be outside the range of valid longitudes
  786. * and latitudes when the camera is outside the map.
  787. * @memberof Camera.prototype
  788. *
  789. * @type {Cartographic}
  790. * @readonly
  791. */
  792. positionCartographic: {
  793. get: function () {
  794. updateMembers(this);
  795. return this._positionCartographic;
  796. },
  797. },
  798. /**
  799. * Gets the position of the camera in world coordinates.
  800. * @memberof Camera.prototype
  801. *
  802. * @type {Cartesian3}
  803. * @readonly
  804. */
  805. positionWC: {
  806. get: function () {
  807. updateMembers(this);
  808. return this._positionWC;
  809. },
  810. },
  811. /**
  812. * Gets the view direction of the camera in world coordinates.
  813. * @memberof Camera.prototype
  814. *
  815. * @type {Cartesian3}
  816. * @readonly
  817. */
  818. directionWC: {
  819. get: function () {
  820. updateMembers(this);
  821. return this._directionWC;
  822. },
  823. },
  824. /**
  825. * Gets the up direction of the camera in world coordinates.
  826. * @memberof Camera.prototype
  827. *
  828. * @type {Cartesian3}
  829. * @readonly
  830. */
  831. upWC: {
  832. get: function () {
  833. updateMembers(this);
  834. return this._upWC;
  835. },
  836. },
  837. /**
  838. * Gets the right direction of the camera in world coordinates.
  839. * @memberof Camera.prototype
  840. *
  841. * @type {Cartesian3}
  842. * @readonly
  843. */
  844. rightWC: {
  845. get: function () {
  846. updateMembers(this);
  847. return this._rightWC;
  848. },
  849. },
  850. /**
  851. * Gets the camera heading in radians.
  852. * @memberof Camera.prototype
  853. *
  854. * @type {number}
  855. * @readonly
  856. */
  857. heading: {
  858. get: function () {
  859. if (this._mode !== SceneMode.MORPHING) {
  860. const ellipsoid = this._projection.ellipsoid;
  861. const oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
  862. const transform = Transforms.eastNorthUpToFixedFrame(
  863. this.positionWC,
  864. ellipsoid,
  865. scratchHPRMatrix2
  866. );
  867. this._setTransform(transform);
  868. const heading = getHeading(this.direction, this.up);
  869. this._setTransform(oldTransform);
  870. return heading;
  871. }
  872. return undefined;
  873. },
  874. },
  875. /**
  876. * Gets the camera pitch in radians.
  877. * @memberof Camera.prototype
  878. *
  879. * @type {number}
  880. * @readonly
  881. */
  882. pitch: {
  883. get: function () {
  884. if (this._mode !== SceneMode.MORPHING) {
  885. const ellipsoid = this._projection.ellipsoid;
  886. const oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
  887. const transform = Transforms.eastNorthUpToFixedFrame(
  888. this.positionWC,
  889. ellipsoid,
  890. scratchHPRMatrix2
  891. );
  892. this._setTransform(transform);
  893. const pitch = getPitch(this.direction);
  894. this._setTransform(oldTransform);
  895. return pitch;
  896. }
  897. return undefined;
  898. },
  899. },
  900. /**
  901. * Gets the camera roll in radians.
  902. * @memberof Camera.prototype
  903. *
  904. * @type {number}
  905. * @readonly
  906. */
  907. roll: {
  908. get: function () {
  909. if (this._mode !== SceneMode.MORPHING) {
  910. const ellipsoid = this._projection.ellipsoid;
  911. const oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
  912. const transform = Transforms.eastNorthUpToFixedFrame(
  913. this.positionWC,
  914. ellipsoid,
  915. scratchHPRMatrix2
  916. );
  917. this._setTransform(transform);
  918. const roll = getRoll(this.direction, this.up, this.right);
  919. this._setTransform(oldTransform);
  920. return roll;
  921. }
  922. return undefined;
  923. },
  924. },
  925. /**
  926. * Gets the event that will be raised at when the camera starts to move.
  927. * @memberof Camera.prototype
  928. * @type {Event}
  929. * @readonly
  930. */
  931. moveStart: {
  932. get: function () {
  933. return this._moveStart;
  934. },
  935. },
  936. /**
  937. * Gets the event that will be raised when the camera has stopped moving.
  938. * @memberof Camera.prototype
  939. * @type {Event}
  940. * @readonly
  941. */
  942. moveEnd: {
  943. get: function () {
  944. return this._moveEnd;
  945. },
  946. },
  947. /**
  948. * Gets the event that will be raised when the camera has changed by <code>percentageChanged</code>.
  949. * @memberof Camera.prototype
  950. * @type {Event}
  951. * @readonly
  952. */
  953. changed: {
  954. get: function () {
  955. return this._changed;
  956. },
  957. },
  958. });
  959. /**
  960. * @private
  961. */
  962. Camera.prototype.update = function (mode) {
  963. //>>includeStart('debug', pragmas.debug);
  964. if (!defined(mode)) {
  965. throw new DeveloperError("mode is required.");
  966. }
  967. if (
  968. mode === SceneMode.SCENE2D &&
  969. !(this.frustum instanceof OrthographicOffCenterFrustum)
  970. ) {
  971. throw new DeveloperError(
  972. "An OrthographicOffCenterFrustum is required in 2D."
  973. );
  974. }
  975. if (
  976. (mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) &&
  977. !(this.frustum instanceof PerspectiveFrustum) &&
  978. !(this.frustum instanceof OrthographicFrustum)
  979. ) {
  980. throw new DeveloperError(
  981. "A PerspectiveFrustum or OrthographicFrustum is required in 3D and Columbus view"
  982. );
  983. }
  984. //>>includeEnd('debug');
  985. let updateFrustum = false;
  986. if (mode !== this._mode) {
  987. this._mode = mode;
  988. this._modeChanged = mode !== SceneMode.MORPHING;
  989. updateFrustum = this._mode === SceneMode.SCENE2D;
  990. }
  991. if (updateFrustum) {
  992. const frustum = (this._max2Dfrustum = this.frustum.clone());
  993. //>>includeStart('debug', pragmas.debug);
  994. if (!(frustum instanceof OrthographicOffCenterFrustum)) {
  995. throw new DeveloperError(
  996. "The camera frustum is expected to be orthographic for 2D camera control."
  997. );
  998. }
  999. //>>includeEnd('debug');
  1000. const maxZoomOut = 2.0;
  1001. const ratio = frustum.top / frustum.right;
  1002. frustum.right = this._maxCoord.x * maxZoomOut;
  1003. frustum.left = -frustum.right;
  1004. frustum.top = ratio * frustum.right;
  1005. frustum.bottom = -frustum.top;
  1006. }
  1007. if (this._mode === SceneMode.SCENE2D) {
  1008. clampMove2D(this, this.position);
  1009. }
  1010. };
  1011. const setTransformPosition = new Cartesian3();
  1012. const setTransformUp = new Cartesian3();
  1013. const setTransformDirection = new Cartesian3();
  1014. Camera.prototype._setTransform = function (transform) {
  1015. const position = Cartesian3.clone(this.positionWC, setTransformPosition);
  1016. const up = Cartesian3.clone(this.upWC, setTransformUp);
  1017. const direction = Cartesian3.clone(this.directionWC, setTransformDirection);
  1018. Matrix4.clone(transform, this._transform);
  1019. this._transformChanged = true;
  1020. updateMembers(this);
  1021. const inverse = this._actualInvTransform;
  1022. Matrix4.multiplyByPoint(inverse, position, this.position);
  1023. Matrix4.multiplyByPointAsVector(inverse, direction, this.direction);
  1024. Matrix4.multiplyByPointAsVector(inverse, up, this.up);
  1025. Cartesian3.cross(this.direction, this.up, this.right);
  1026. updateMembers(this);
  1027. };
  1028. const scratchAdjustOrthographicFrustumMousePosition = new Cartesian2();
  1029. const scratchPickRay = new Ray();
  1030. const scratchRayIntersection = new Cartesian3();
  1031. const scratchDepthIntersection = new Cartesian3();
  1032. function calculateOrthographicFrustumWidth(camera) {
  1033. // Camera is fixed to an object, so keep frustum width constant.
  1034. if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) {
  1035. return Cartesian3.magnitude(camera.position);
  1036. }
  1037. const scene = camera._scene;
  1038. const globe = scene.globe;
  1039. const mousePosition = scratchAdjustOrthographicFrustumMousePosition;
  1040. mousePosition.x = scene.drawingBufferWidth / 2.0;
  1041. mousePosition.y = scene.drawingBufferHeight / 2.0;
  1042. let rayIntersection;
  1043. if (defined(globe)) {
  1044. const ray = camera.getPickRay(mousePosition, scratchPickRay);
  1045. rayIntersection = globe.pickWorldCoordinates(
  1046. ray,
  1047. scene,
  1048. true,
  1049. scratchRayIntersection
  1050. );
  1051. }
  1052. let depthIntersection;
  1053. if (scene.pickPositionSupported) {
  1054. depthIntersection = scene.pickPositionWorldCoordinates(
  1055. mousePosition,
  1056. scratchDepthIntersection
  1057. );
  1058. }
  1059. let distance;
  1060. if (defined(rayIntersection) || defined(depthIntersection)) {
  1061. const depthDistance = defined(depthIntersection)
  1062. ? Cartesian3.distance(depthIntersection, camera.positionWC)
  1063. : Number.POSITIVE_INFINITY;
  1064. const rayDistance = defined(rayIntersection)
  1065. ? Cartesian3.distance(rayIntersection, camera.positionWC)
  1066. : Number.POSITIVE_INFINITY;
  1067. distance = Math.min(depthDistance, rayDistance);
  1068. } else {
  1069. distance = Math.max(camera.positionCartographic.height, 0.0);
  1070. }
  1071. return distance;
  1072. }
  1073. Camera.prototype._adjustOrthographicFrustum = function (zooming) {
  1074. if (!(this.frustum instanceof OrthographicFrustum)) {
  1075. return;
  1076. }
  1077. if (!zooming && this._positionCartographic.height < 150000.0) {
  1078. return;
  1079. }
  1080. this.frustum.width = calculateOrthographicFrustumWidth(this);
  1081. };
  1082. const scratchSetViewCartesian = new Cartesian3();
  1083. const scratchSetViewTransform1 = new Matrix4();
  1084. const scratchSetViewTransform2 = new Matrix4();
  1085. const scratchSetViewQuaternion = new Quaternion();
  1086. const scratchSetViewMatrix3 = new Matrix3();
  1087. const scratchSetViewCartographic = new Cartographic();
  1088. function setView3D(camera, position, hpr) {
  1089. const currentTransform = Matrix4.clone(
  1090. camera.transform,
  1091. scratchSetViewTransform1
  1092. );
  1093. const localTransform = Transforms.eastNorthUpToFixedFrame(
  1094. position,
  1095. camera._projection.ellipsoid,
  1096. scratchSetViewTransform2
  1097. );
  1098. camera._setTransform(localTransform);
  1099. Cartesian3.clone(Cartesian3.ZERO, camera.position);
  1100. hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO;
  1101. const rotQuat = Quaternion.fromHeadingPitchRoll(
  1102. hpr,
  1103. scratchSetViewQuaternion
  1104. );
  1105. const rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);
  1106. Matrix3.getColumn(rotMat, 0, camera.direction);
  1107. Matrix3.getColumn(rotMat, 2, camera.up);
  1108. Cartesian3.cross(camera.direction, camera.up, camera.right);
  1109. camera._setTransform(currentTransform);
  1110. camera._adjustOrthographicFrustum(true);
  1111. }
  1112. function setViewCV(camera, position, hpr, convert) {
  1113. const currentTransform = Matrix4.clone(
  1114. camera.transform,
  1115. scratchSetViewTransform1
  1116. );
  1117. camera._setTransform(Matrix4.IDENTITY);
  1118. if (!Cartesian3.equals(position, camera.positionWC)) {
  1119. if (convert) {
  1120. const projection = camera._projection;
  1121. const cartographic = projection.ellipsoid.cartesianToCartographic(
  1122. position,
  1123. scratchSetViewCartographic
  1124. );
  1125. position = projection.project(cartographic, scratchSetViewCartesian);
  1126. }
  1127. Cartesian3.clone(position, camera.position);
  1128. }
  1129. hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO;
  1130. const rotQuat = Quaternion.fromHeadingPitchRoll(
  1131. hpr,
  1132. scratchSetViewQuaternion
  1133. );
  1134. const rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);
  1135. Matrix3.getColumn(rotMat, 0, camera.direction);
  1136. Matrix3.getColumn(rotMat, 2, camera.up);
  1137. Cartesian3.cross(camera.direction, camera.up, camera.right);
  1138. camera._setTransform(currentTransform);
  1139. camera._adjustOrthographicFrustum(true);
  1140. }
  1141. function setView2D(camera, position, hpr, convert) {
  1142. const currentTransform = Matrix4.clone(
  1143. camera.transform,
  1144. scratchSetViewTransform1
  1145. );
  1146. camera._setTransform(Matrix4.IDENTITY);
  1147. if (!Cartesian3.equals(position, camera.positionWC)) {
  1148. if (convert) {
  1149. const projection = camera._projection;
  1150. const cartographic = projection.ellipsoid.cartesianToCartographic(
  1151. position,
  1152. scratchSetViewCartographic
  1153. );
  1154. position = projection.project(cartographic, scratchSetViewCartesian);
  1155. }
  1156. Cartesian2.clone(position, camera.position);
  1157. const newLeft = -position.z * 0.5;
  1158. const newRight = -newLeft;
  1159. const frustum = camera.frustum;
  1160. if (newRight > newLeft) {
  1161. const ratio = frustum.top / frustum.right;
  1162. frustum.right = newRight;
  1163. frustum.left = newLeft;
  1164. frustum.top = frustum.right * ratio;
  1165. frustum.bottom = -frustum.top;
  1166. }
  1167. }
  1168. if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
  1169. hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO;
  1170. hpr.pitch = -CesiumMath.PI_OVER_TWO;
  1171. hpr.roll = 0.0;
  1172. const rotQuat = Quaternion.fromHeadingPitchRoll(
  1173. hpr,
  1174. scratchSetViewQuaternion
  1175. );
  1176. const rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);
  1177. Matrix3.getColumn(rotMat, 2, camera.up);
  1178. Cartesian3.cross(camera.direction, camera.up, camera.right);
  1179. }
  1180. camera._setTransform(currentTransform);
  1181. }
  1182. const scratchToHPRDirection = new Cartesian3();
  1183. const scratchToHPRUp = new Cartesian3();
  1184. const scratchToHPRRight = new Cartesian3();
  1185. function directionUpToHeadingPitchRoll(camera, position, orientation, result) {
  1186. const direction = Cartesian3.clone(
  1187. orientation.direction,
  1188. scratchToHPRDirection
  1189. );
  1190. const up = Cartesian3.clone(orientation.up, scratchToHPRUp);
  1191. if (camera._scene.mode === SceneMode.SCENE3D) {
  1192. const ellipsoid = camera._projection.ellipsoid;
  1193. const transform = Transforms.eastNorthUpToFixedFrame(
  1194. position,
  1195. ellipsoid,
  1196. scratchHPRMatrix1
  1197. );
  1198. const invTransform = Matrix4.inverseTransformation(
  1199. transform,
  1200. scratchHPRMatrix2
  1201. );
  1202. Matrix4.multiplyByPointAsVector(invTransform, direction, direction);
  1203. Matrix4.multiplyByPointAsVector(invTransform, up, up);
  1204. }
  1205. const right = Cartesian3.cross(direction, up, scratchToHPRRight);
  1206. result.heading = getHeading(direction, up);
  1207. result.pitch = getPitch(direction);
  1208. result.roll = getRoll(direction, up, right);
  1209. return result;
  1210. }
  1211. const scratchSetViewOptions = {
  1212. destination: undefined,
  1213. orientation: {
  1214. direction: undefined,
  1215. up: undefined,
  1216. heading: undefined,
  1217. pitch: undefined,
  1218. roll: undefined,
  1219. },
  1220. convert: undefined,
  1221. endTransform: undefined,
  1222. };
  1223. const scratchHpr = new HeadingPitchRoll();
  1224. /**
  1225. * Sets the camera position, orientation and transform.
  1226. *
  1227. * @param {object} options Object with the following properties:
  1228. * @param {Cartesian3|Rectangle} [options.destination] The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view.
  1229. * @param {HeadingPitchRollValues|DirectionUp} [options.orientation] An object that contains either direction and up properties or heading, pitch and roll properties. By default, the direction will point
  1230. * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive
  1231. * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode.
  1232. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame of the camera.
  1233. * @param {boolean} [options.convert] Whether to convert the destination from world coordinates to scene coordinates (only relevant when not using 3D). Defaults to <code>true</code>.
  1234. *
  1235. * @example
  1236. * // 1. Set position with a top-down view
  1237. * viewer.camera.setView({
  1238. * destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
  1239. * });
  1240. *
  1241. * // 2 Set view with heading, pitch and roll
  1242. * viewer.camera.setView({
  1243. * destination : cartesianPosition,
  1244. * orientation: {
  1245. * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
  1246. * pitch : Cesium.Math.toRadians(-90), // default value (looking down)
  1247. * roll : 0.0 // default value
  1248. * }
  1249. * });
  1250. *
  1251. * // 3. Change heading, pitch and roll with the camera position remaining the same.
  1252. * viewer.camera.setView({
  1253. * orientation: {
  1254. * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
  1255. * pitch : Cesium.Math.toRadians(-90), // default value (looking down)
  1256. * roll : 0.0 // default value
  1257. * }
  1258. * });
  1259. *
  1260. *
  1261. * // 4. View rectangle with a top-down view
  1262. * viewer.camera.setView({
  1263. * destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
  1264. * });
  1265. *
  1266. * // 5. Set position with an orientation using unit vectors.
  1267. * viewer.camera.setView({
  1268. * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
  1269. * orientation : {
  1270. * direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
  1271. * up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
  1272. * }
  1273. * });
  1274. */
  1275. Camera.prototype.setView = function (options) {
  1276. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  1277. let orientation = defaultValue(
  1278. options.orientation,
  1279. defaultValue.EMPTY_OBJECT
  1280. );
  1281. const mode = this._mode;
  1282. if (mode === SceneMode.MORPHING) {
  1283. return;
  1284. }
  1285. if (defined(options.endTransform)) {
  1286. this._setTransform(options.endTransform);
  1287. }
  1288. let convert = defaultValue(options.convert, true);
  1289. let destination = defaultValue(
  1290. options.destination,
  1291. Cartesian3.clone(this.positionWC, scratchSetViewCartesian)
  1292. );
  1293. if (defined(destination) && defined(destination.west)) {
  1294. destination = this.getRectangleCameraCoordinates(
  1295. destination,
  1296. scratchSetViewCartesian
  1297. );
  1298. convert = false;
  1299. }
  1300. if (defined(orientation.direction)) {
  1301. orientation = directionUpToHeadingPitchRoll(
  1302. this,
  1303. destination,
  1304. orientation,
  1305. scratchSetViewOptions.orientation
  1306. );
  1307. }
  1308. scratchHpr.heading = defaultValue(orientation.heading, 0.0);
  1309. scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO);
  1310. scratchHpr.roll = defaultValue(orientation.roll, 0.0);
  1311. if (mode === SceneMode.SCENE3D) {
  1312. setView3D(this, destination, scratchHpr);
  1313. } else if (mode === SceneMode.SCENE2D) {
  1314. setView2D(this, destination, scratchHpr, convert);
  1315. } else {
  1316. setViewCV(this, destination, scratchHpr, convert);
  1317. }
  1318. };
  1319. const pitchScratch = new Cartesian3();
  1320. /**
  1321. * Fly the camera to the home view. Use {@link Camera#.DEFAULT_VIEW_RECTANGLE} to set
  1322. * the default view for the 3D scene. The home view for 2D and columbus view shows the
  1323. * entire map.
  1324. *
  1325. * @param {number} [duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight. See {@link Camera#flyTo}
  1326. */
  1327. Camera.prototype.flyHome = function (duration) {
  1328. const mode = this._mode;
  1329. if (mode === SceneMode.MORPHING) {
  1330. this._scene.completeMorph();
  1331. }
  1332. if (mode === SceneMode.SCENE2D) {
  1333. this.flyTo({
  1334. destination: Camera.DEFAULT_VIEW_RECTANGLE,
  1335. duration: duration,
  1336. endTransform: Matrix4.IDENTITY,
  1337. });
  1338. } else if (mode === SceneMode.SCENE3D) {
  1339. const destination = this.getRectangleCameraCoordinates(
  1340. Camera.DEFAULT_VIEW_RECTANGLE
  1341. );
  1342. let mag = Cartesian3.magnitude(destination);
  1343. mag += mag * Camera.DEFAULT_VIEW_FACTOR;
  1344. Cartesian3.normalize(destination, destination);
  1345. Cartesian3.multiplyByScalar(destination, mag, destination);
  1346. this.flyTo({
  1347. destination: destination,
  1348. duration: duration,
  1349. endTransform: Matrix4.IDENTITY,
  1350. });
  1351. } else if (mode === SceneMode.COLUMBUS_VIEW) {
  1352. const maxRadii = this._projection.ellipsoid.maximumRadius;
  1353. let position = new Cartesian3(0.0, -1.0, 1.0);
  1354. position = Cartesian3.multiplyByScalar(
  1355. Cartesian3.normalize(position, position),
  1356. 5.0 * maxRadii,
  1357. position
  1358. );
  1359. this.flyTo({
  1360. destination: position,
  1361. duration: duration,
  1362. orientation: {
  1363. heading: 0.0,
  1364. pitch: -Math.acos(Cartesian3.normalize(position, pitchScratch).z),
  1365. roll: 0.0,
  1366. },
  1367. endTransform: Matrix4.IDENTITY,
  1368. convert: false,
  1369. });
  1370. }
  1371. };
  1372. /**
  1373. * Transform a vector or point from world coordinates to the camera's reference frame.
  1374. *
  1375. * @param {Cartesian4} cartesian The vector or point to transform.
  1376. * @param {Cartesian4} [result] The object onto which to store the result.
  1377. * @returns {Cartesian4} The transformed vector or point.
  1378. */
  1379. Camera.prototype.worldToCameraCoordinates = function (cartesian, result) {
  1380. //>>includeStart('debug', pragmas.debug);
  1381. if (!defined(cartesian)) {
  1382. throw new DeveloperError("cartesian is required.");
  1383. }
  1384. //>>includeEnd('debug');
  1385. if (!defined(result)) {
  1386. result = new Cartesian4();
  1387. }
  1388. updateMembers(this);
  1389. return Matrix4.multiplyByVector(this._actualInvTransform, cartesian, result);
  1390. };
  1391. /**
  1392. * Transform a point from world coordinates to the camera's reference frame.
  1393. *
  1394. * @param {Cartesian3} cartesian The point to transform.
  1395. * @param {Cartesian3} [result] The object onto which to store the result.
  1396. * @returns {Cartesian3} The transformed point.
  1397. */
  1398. Camera.prototype.worldToCameraCoordinatesPoint = function (cartesian, result) {
  1399. //>>includeStart('debug', pragmas.debug);
  1400. if (!defined(cartesian)) {
  1401. throw new DeveloperError("cartesian is required.");
  1402. }
  1403. //>>includeEnd('debug');
  1404. if (!defined(result)) {
  1405. result = new Cartesian3();
  1406. }
  1407. updateMembers(this);
  1408. return Matrix4.multiplyByPoint(this._actualInvTransform, cartesian, result);
  1409. };
  1410. /**
  1411. * Transform a vector from world coordinates to the camera's reference frame.
  1412. *
  1413. * @param {Cartesian3} cartesian The vector to transform.
  1414. * @param {Cartesian3} [result] The object onto which to store the result.
  1415. * @returns {Cartesian3} The transformed vector.
  1416. */
  1417. Camera.prototype.worldToCameraCoordinatesVector = function (cartesian, result) {
  1418. //>>includeStart('debug', pragmas.debug);
  1419. if (!defined(cartesian)) {
  1420. throw new DeveloperError("cartesian is required.");
  1421. }
  1422. //>>includeEnd('debug');
  1423. if (!defined(result)) {
  1424. result = new Cartesian3();
  1425. }
  1426. updateMembers(this);
  1427. return Matrix4.multiplyByPointAsVector(
  1428. this._actualInvTransform,
  1429. cartesian,
  1430. result
  1431. );
  1432. };
  1433. /**
  1434. * Transform a vector or point from the camera's reference frame to world coordinates.
  1435. *
  1436. * @param {Cartesian4} cartesian The vector or point to transform.
  1437. * @param {Cartesian4} [result] The object onto which to store the result.
  1438. * @returns {Cartesian4} The transformed vector or point.
  1439. */
  1440. Camera.prototype.cameraToWorldCoordinates = function (cartesian, result) {
  1441. //>>includeStart('debug', pragmas.debug);
  1442. if (!defined(cartesian)) {
  1443. throw new DeveloperError("cartesian is required.");
  1444. }
  1445. //>>includeEnd('debug');
  1446. if (!defined(result)) {
  1447. result = new Cartesian4();
  1448. }
  1449. updateMembers(this);
  1450. return Matrix4.multiplyByVector(this._actualTransform, cartesian, result);
  1451. };
  1452. /**
  1453. * Transform a point from the camera's reference frame to world coordinates.
  1454. *
  1455. * @param {Cartesian3} cartesian The point to transform.
  1456. * @param {Cartesian3} [result] The object onto which to store the result.
  1457. * @returns {Cartesian3} The transformed point.
  1458. */
  1459. Camera.prototype.cameraToWorldCoordinatesPoint = function (cartesian, result) {
  1460. //>>includeStart('debug', pragmas.debug);
  1461. if (!defined(cartesian)) {
  1462. throw new DeveloperError("cartesian is required.");
  1463. }
  1464. //>>includeEnd('debug');
  1465. if (!defined(result)) {
  1466. result = new Cartesian3();
  1467. }
  1468. updateMembers(this);
  1469. return Matrix4.multiplyByPoint(this._actualTransform, cartesian, result);
  1470. };
  1471. /**
  1472. * Transform a vector from the camera's reference frame to world coordinates.
  1473. *
  1474. * @param {Cartesian3} cartesian The vector to transform.
  1475. * @param {Cartesian3} [result] The object onto which to store the result.
  1476. * @returns {Cartesian3} The transformed vector.
  1477. */
  1478. Camera.prototype.cameraToWorldCoordinatesVector = function (cartesian, result) {
  1479. //>>includeStart('debug', pragmas.debug);
  1480. if (!defined(cartesian)) {
  1481. throw new DeveloperError("cartesian is required.");
  1482. }
  1483. //>>includeEnd('debug');
  1484. if (!defined(result)) {
  1485. result = new Cartesian3();
  1486. }
  1487. updateMembers(this);
  1488. return Matrix4.multiplyByPointAsVector(
  1489. this._actualTransform,
  1490. cartesian,
  1491. result
  1492. );
  1493. };
  1494. function clampMove2D(camera, position) {
  1495. const rotatable2D = camera._scene.mapMode2D === MapMode2D.ROTATE;
  1496. const maxProjectedX = camera._maxCoord.x;
  1497. const maxProjectedY = camera._maxCoord.y;
  1498. let minX;
  1499. let maxX;
  1500. if (rotatable2D) {
  1501. maxX = maxProjectedX;
  1502. minX = -maxX;
  1503. } else {
  1504. maxX = position.x - maxProjectedX * 2.0;
  1505. minX = position.x + maxProjectedX * 2.0;
  1506. }
  1507. if (position.x > maxProjectedX) {
  1508. position.x = maxX;
  1509. }
  1510. if (position.x < -maxProjectedX) {
  1511. position.x = minX;
  1512. }
  1513. if (position.y > maxProjectedY) {
  1514. position.y = maxProjectedY;
  1515. }
  1516. if (position.y < -maxProjectedY) {
  1517. position.y = -maxProjectedY;
  1518. }
  1519. }
  1520. const moveScratch = new Cartesian3();
  1521. /**
  1522. * Translates the camera's position by <code>amount</code> along <code>direction</code>.
  1523. *
  1524. * @param {Cartesian3} direction The direction to move.
  1525. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1526. *
  1527. * @see Camera#moveBackward
  1528. * @see Camera#moveForward
  1529. * @see Camera#moveLeft
  1530. * @see Camera#moveRight
  1531. * @see Camera#moveUp
  1532. * @see Camera#moveDown
  1533. */
  1534. Camera.prototype.move = function (direction, amount) {
  1535. //>>includeStart('debug', pragmas.debug);
  1536. if (!defined(direction)) {
  1537. throw new DeveloperError("direction is required.");
  1538. }
  1539. //>>includeEnd('debug');
  1540. const cameraPosition = this.position;
  1541. Cartesian3.multiplyByScalar(direction, amount, moveScratch);
  1542. Cartesian3.add(cameraPosition, moveScratch, cameraPosition);
  1543. if (this._mode === SceneMode.SCENE2D) {
  1544. clampMove2D(this, cameraPosition);
  1545. }
  1546. this._adjustOrthographicFrustum(true);
  1547. };
  1548. /**
  1549. * Translates the camera's position by <code>amount</code> along the camera's view vector.
  1550. * When in 2D mode, this will zoom in the camera instead of translating the camera's position.
  1551. *
  1552. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1553. *
  1554. * @see Camera#moveBackward
  1555. */
  1556. Camera.prototype.moveForward = function (amount) {
  1557. amount = defaultValue(amount, this.defaultMoveAmount);
  1558. if (this._mode === SceneMode.SCENE2D) {
  1559. // 2D mode
  1560. zoom2D(this, amount);
  1561. } else {
  1562. // 3D or Columbus view mode
  1563. this.move(this.direction, amount);
  1564. }
  1565. };
  1566. /**
  1567. * Translates the camera's position by <code>amount</code> along the opposite direction
  1568. * of the camera's view vector.
  1569. * When in 2D mode, this will zoom out the camera instead of translating the camera's position.
  1570. *
  1571. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1572. *
  1573. * @see Camera#moveForward
  1574. */
  1575. Camera.prototype.moveBackward = function (amount) {
  1576. amount = defaultValue(amount, this.defaultMoveAmount);
  1577. if (this._mode === SceneMode.SCENE2D) {
  1578. // 2D mode
  1579. zoom2D(this, -amount);
  1580. } else {
  1581. // 3D or Columbus view mode
  1582. this.move(this.direction, -amount);
  1583. }
  1584. };
  1585. /**
  1586. * Translates the camera's position by <code>amount</code> along the camera's up vector.
  1587. *
  1588. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1589. *
  1590. * @see Camera#moveDown
  1591. */
  1592. Camera.prototype.moveUp = function (amount) {
  1593. amount = defaultValue(amount, this.defaultMoveAmount);
  1594. this.move(this.up, amount);
  1595. };
  1596. /**
  1597. * Translates the camera's position by <code>amount</code> along the opposite direction
  1598. * of the camera's up vector.
  1599. *
  1600. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1601. *
  1602. * @see Camera#moveUp
  1603. */
  1604. Camera.prototype.moveDown = function (amount) {
  1605. amount = defaultValue(amount, this.defaultMoveAmount);
  1606. this.move(this.up, -amount);
  1607. };
  1608. /**
  1609. * Translates the camera's position by <code>amount</code> along the camera's right vector.
  1610. *
  1611. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1612. *
  1613. * @see Camera#moveLeft
  1614. */
  1615. Camera.prototype.moveRight = function (amount) {
  1616. amount = defaultValue(amount, this.defaultMoveAmount);
  1617. this.move(this.right, amount);
  1618. };
  1619. /**
  1620. * Translates the camera's position by <code>amount</code> along the opposite direction
  1621. * of the camera's right vector.
  1622. *
  1623. * @param {number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
  1624. *
  1625. * @see Camera#moveRight
  1626. */
  1627. Camera.prototype.moveLeft = function (amount) {
  1628. amount = defaultValue(amount, this.defaultMoveAmount);
  1629. this.move(this.right, -amount);
  1630. };
  1631. /**
  1632. * Rotates the camera around its up vector by amount, in radians, in the opposite direction
  1633. * of its right vector if not in 2D mode.
  1634. *
  1635. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1636. *
  1637. * @see Camera#lookRight
  1638. */
  1639. Camera.prototype.lookLeft = function (amount) {
  1640. amount = defaultValue(amount, this.defaultLookAmount);
  1641. // only want view of map to change in 3D mode, 2D visual is incorrect when look changes
  1642. if (this._mode !== SceneMode.SCENE2D) {
  1643. this.look(this.up, -amount);
  1644. }
  1645. };
  1646. /**
  1647. * Rotates the camera around its up vector by amount, in radians, in the direction
  1648. * of its right vector if not in 2D mode.
  1649. *
  1650. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1651. *
  1652. * @see Camera#lookLeft
  1653. */
  1654. Camera.prototype.lookRight = function (amount) {
  1655. amount = defaultValue(amount, this.defaultLookAmount);
  1656. // only want view of map to change in 3D mode, 2D visual is incorrect when look changes
  1657. if (this._mode !== SceneMode.SCENE2D) {
  1658. this.look(this.up, amount);
  1659. }
  1660. };
  1661. /**
  1662. * Rotates the camera around its right vector by amount, in radians, in the direction
  1663. * of its up vector if not in 2D mode.
  1664. *
  1665. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1666. *
  1667. * @see Camera#lookDown
  1668. */
  1669. Camera.prototype.lookUp = function (amount) {
  1670. amount = defaultValue(amount, this.defaultLookAmount);
  1671. // only want view of map to change in 3D mode, 2D visual is incorrect when look changes
  1672. if (this._mode !== SceneMode.SCENE2D) {
  1673. this.look(this.right, -amount);
  1674. }
  1675. };
  1676. /**
  1677. * Rotates the camera around its right vector by amount, in radians, in the opposite direction
  1678. * of its up vector if not in 2D mode.
  1679. *
  1680. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1681. *
  1682. * @see Camera#lookUp
  1683. */
  1684. Camera.prototype.lookDown = function (amount) {
  1685. amount = defaultValue(amount, this.defaultLookAmount);
  1686. // only want view of map to change in 3D mode, 2D visual is incorrect when look changes
  1687. if (this._mode !== SceneMode.SCENE2D) {
  1688. this.look(this.right, amount);
  1689. }
  1690. };
  1691. const lookScratchQuaternion = new Quaternion();
  1692. const lookScratchMatrix = new Matrix3();
  1693. /**
  1694. * Rotate each of the camera's orientation vectors around <code>axis</code> by <code>angle</code>
  1695. *
  1696. * @param {Cartesian3} axis The axis to rotate around.
  1697. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1698. *
  1699. * @see Camera#lookUp
  1700. * @see Camera#lookDown
  1701. * @see Camera#lookLeft
  1702. * @see Camera#lookRight
  1703. */
  1704. Camera.prototype.look = function (axis, angle) {
  1705. //>>includeStart('debug', pragmas.debug);
  1706. if (!defined(axis)) {
  1707. throw new DeveloperError("axis is required.");
  1708. }
  1709. //>>includeEnd('debug');
  1710. const turnAngle = defaultValue(angle, this.defaultLookAmount);
  1711. const quaternion = Quaternion.fromAxisAngle(
  1712. axis,
  1713. -turnAngle,
  1714. lookScratchQuaternion
  1715. );
  1716. const rotation = Matrix3.fromQuaternion(quaternion, lookScratchMatrix);
  1717. const direction = this.direction;
  1718. const up = this.up;
  1719. const right = this.right;
  1720. Matrix3.multiplyByVector(rotation, direction, direction);
  1721. Matrix3.multiplyByVector(rotation, up, up);
  1722. Matrix3.multiplyByVector(rotation, right, right);
  1723. };
  1724. /**
  1725. * Rotate the camera counter-clockwise around its direction vector by amount, in radians.
  1726. *
  1727. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1728. *
  1729. * @see Camera#twistRight
  1730. */
  1731. Camera.prototype.twistLeft = function (amount) {
  1732. amount = defaultValue(amount, this.defaultLookAmount);
  1733. this.look(this.direction, amount);
  1734. };
  1735. /**
  1736. * Rotate the camera clockwise around its direction vector by amount, in radians.
  1737. *
  1738. * @param {number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
  1739. *
  1740. * @see Camera#twistLeft
  1741. */
  1742. Camera.prototype.twistRight = function (amount) {
  1743. amount = defaultValue(amount, this.defaultLookAmount);
  1744. this.look(this.direction, -amount);
  1745. };
  1746. const rotateScratchQuaternion = new Quaternion();
  1747. const rotateScratchMatrix = new Matrix3();
  1748. /**
  1749. * Rotates the camera around <code>axis</code> by <code>angle</code>. The distance
  1750. * of the camera's position to the center of the camera's reference frame remains the same.
  1751. *
  1752. * @param {Cartesian3} axis The axis to rotate around given in world coordinates.
  1753. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
  1754. *
  1755. * @see Camera#rotateUp
  1756. * @see Camera#rotateDown
  1757. * @see Camera#rotateLeft
  1758. * @see Camera#rotateRight
  1759. */
  1760. Camera.prototype.rotate = function (axis, angle) {
  1761. //>>includeStart('debug', pragmas.debug);
  1762. if (!defined(axis)) {
  1763. throw new DeveloperError("axis is required.");
  1764. }
  1765. //>>includeEnd('debug');
  1766. const turnAngle = defaultValue(angle, this.defaultRotateAmount);
  1767. const quaternion = Quaternion.fromAxisAngle(
  1768. axis,
  1769. -turnAngle,
  1770. rotateScratchQuaternion
  1771. );
  1772. const rotation = Matrix3.fromQuaternion(quaternion, rotateScratchMatrix);
  1773. Matrix3.multiplyByVector(rotation, this.position, this.position);
  1774. Matrix3.multiplyByVector(rotation, this.direction, this.direction);
  1775. Matrix3.multiplyByVector(rotation, this.up, this.up);
  1776. Cartesian3.cross(this.direction, this.up, this.right);
  1777. Cartesian3.cross(this.right, this.direction, this.up);
  1778. this._adjustOrthographicFrustum(false);
  1779. };
  1780. /**
  1781. * Rotates the camera around the center of the camera's reference frame by angle downwards.
  1782. *
  1783. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
  1784. *
  1785. * @see Camera#rotateUp
  1786. * @see Camera#rotate
  1787. */
  1788. Camera.prototype.rotateDown = function (angle) {
  1789. angle = defaultValue(angle, this.defaultRotateAmount);
  1790. rotateVertical(this, angle);
  1791. };
  1792. /**
  1793. * Rotates the camera around the center of the camera's reference frame by angle upwards.
  1794. *
  1795. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
  1796. *
  1797. * @see Camera#rotateDown
  1798. * @see Camera#rotate
  1799. */
  1800. Camera.prototype.rotateUp = function (angle) {
  1801. angle = defaultValue(angle, this.defaultRotateAmount);
  1802. rotateVertical(this, -angle);
  1803. };
  1804. const rotateVertScratchP = new Cartesian3();
  1805. const rotateVertScratchA = new Cartesian3();
  1806. const rotateVertScratchTan = new Cartesian3();
  1807. const rotateVertScratchNegate = new Cartesian3();
  1808. function rotateVertical(camera, angle) {
  1809. const position = camera.position;
  1810. if (
  1811. defined(camera.constrainedAxis) &&
  1812. !Cartesian3.equalsEpsilon(
  1813. camera.position,
  1814. Cartesian3.ZERO,
  1815. CesiumMath.EPSILON2
  1816. )
  1817. ) {
  1818. const p = Cartesian3.normalize(position, rotateVertScratchP);
  1819. const northParallel = Cartesian3.equalsEpsilon(
  1820. p,
  1821. camera.constrainedAxis,
  1822. CesiumMath.EPSILON2
  1823. );
  1824. const southParallel = Cartesian3.equalsEpsilon(
  1825. p,
  1826. Cartesian3.negate(camera.constrainedAxis, rotateVertScratchNegate),
  1827. CesiumMath.EPSILON2
  1828. );
  1829. if (!northParallel && !southParallel) {
  1830. const constrainedAxis = Cartesian3.normalize(
  1831. camera.constrainedAxis,
  1832. rotateVertScratchA
  1833. );
  1834. let dot = Cartesian3.dot(p, constrainedAxis);
  1835. let angleToAxis = CesiumMath.acosClamped(dot);
  1836. if (angle > 0 && angle > angleToAxis) {
  1837. angle = angleToAxis - CesiumMath.EPSILON4;
  1838. }
  1839. dot = Cartesian3.dot(
  1840. p,
  1841. Cartesian3.negate(constrainedAxis, rotateVertScratchNegate)
  1842. );
  1843. angleToAxis = CesiumMath.acosClamped(dot);
  1844. if (angle < 0 && -angle > angleToAxis) {
  1845. angle = -angleToAxis + CesiumMath.EPSILON4;
  1846. }
  1847. const tangent = Cartesian3.cross(
  1848. constrainedAxis,
  1849. p,
  1850. rotateVertScratchTan
  1851. );
  1852. camera.rotate(tangent, angle);
  1853. } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
  1854. camera.rotate(camera.right, angle);
  1855. }
  1856. } else {
  1857. camera.rotate(camera.right, angle);
  1858. }
  1859. }
  1860. /**
  1861. * Rotates the camera around the center of the camera's reference frame by angle to the right.
  1862. *
  1863. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
  1864. *
  1865. * @see Camera#rotateLeft
  1866. * @see Camera#rotate
  1867. */
  1868. Camera.prototype.rotateRight = function (angle) {
  1869. angle = defaultValue(angle, this.defaultRotateAmount);
  1870. rotateHorizontal(this, -angle);
  1871. };
  1872. /**
  1873. * Rotates the camera around the center of the camera's reference frame by angle to the left.
  1874. *
  1875. * @param {number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
  1876. *
  1877. * @see Camera#rotateRight
  1878. * @see Camera#rotate
  1879. */
  1880. Camera.prototype.rotateLeft = function (angle) {
  1881. angle = defaultValue(angle, this.defaultRotateAmount);
  1882. rotateHorizontal(this, angle);
  1883. };
  1884. function rotateHorizontal(camera, angle) {
  1885. if (defined(camera.constrainedAxis)) {
  1886. camera.rotate(camera.constrainedAxis, angle);
  1887. } else {
  1888. camera.rotate(camera.up, angle);
  1889. }
  1890. }
  1891. function zoom2D(camera, amount) {
  1892. const frustum = camera.frustum;
  1893. //>>includeStart('debug', pragmas.debug);
  1894. if (
  1895. !(frustum instanceof OrthographicOffCenterFrustum) ||
  1896. !defined(frustum.left) ||
  1897. !defined(frustum.right) ||
  1898. !defined(frustum.bottom) ||
  1899. !defined(frustum.top)
  1900. ) {
  1901. throw new DeveloperError(
  1902. "The camera frustum is expected to be orthographic for 2D camera control."
  1903. );
  1904. }
  1905. //>>includeEnd('debug');
  1906. let ratio;
  1907. amount = amount * 0.5;
  1908. if (
  1909. Math.abs(frustum.top) + Math.abs(frustum.bottom) >
  1910. Math.abs(frustum.left) + Math.abs(frustum.right)
  1911. ) {
  1912. let newTop = frustum.top - amount;
  1913. let newBottom = frustum.bottom + amount;
  1914. let maxBottom = camera._maxCoord.y;
  1915. if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
  1916. maxBottom *= camera.maximumZoomFactor;
  1917. }
  1918. if (newBottom > maxBottom) {
  1919. newBottom = maxBottom;
  1920. newTop = -maxBottom;
  1921. }
  1922. if (newTop <= newBottom) {
  1923. newTop = 1.0;
  1924. newBottom = -1.0;
  1925. }
  1926. ratio = frustum.right / frustum.top;
  1927. frustum.top = newTop;
  1928. frustum.bottom = newBottom;
  1929. frustum.right = frustum.top * ratio;
  1930. frustum.left = -frustum.right;
  1931. } else {
  1932. let newRight = frustum.right - amount;
  1933. let newLeft = frustum.left + amount;
  1934. let maxRight = camera._maxCoord.x;
  1935. if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
  1936. maxRight *= camera.maximumZoomFactor;
  1937. }
  1938. if (newRight > maxRight) {
  1939. newRight = maxRight;
  1940. newLeft = -maxRight;
  1941. }
  1942. if (newRight <= newLeft) {
  1943. newRight = 1.0;
  1944. newLeft = -1.0;
  1945. }
  1946. ratio = frustum.top / frustum.right;
  1947. frustum.right = newRight;
  1948. frustum.left = newLeft;
  1949. frustum.top = frustum.right * ratio;
  1950. frustum.bottom = -frustum.top;
  1951. }
  1952. }
  1953. function zoom3D(camera, amount) {
  1954. camera.move(camera.direction, amount);
  1955. }
  1956. /**
  1957. * Zooms <code>amount</code> along the camera's view vector.
  1958. *
  1959. * @param {number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
  1960. *
  1961. * @see Camera#zoomOut
  1962. */
  1963. Camera.prototype.zoomIn = function (amount) {
  1964. amount = defaultValue(amount, this.defaultZoomAmount);
  1965. if (this._mode === SceneMode.SCENE2D) {
  1966. zoom2D(this, amount);
  1967. } else {
  1968. zoom3D(this, amount);
  1969. }
  1970. };
  1971. /**
  1972. * Zooms <code>amount</code> along the opposite direction of
  1973. * the camera's view vector.
  1974. *
  1975. * @param {number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
  1976. *
  1977. * @see Camera#zoomIn
  1978. */
  1979. Camera.prototype.zoomOut = function (amount) {
  1980. amount = defaultValue(amount, this.defaultZoomAmount);
  1981. if (this._mode === SceneMode.SCENE2D) {
  1982. zoom2D(this, -amount);
  1983. } else {
  1984. zoom3D(this, -amount);
  1985. }
  1986. };
  1987. /**
  1988. * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and
  1989. * Columbus view, this is the distance to the map.
  1990. *
  1991. * @returns {number} The magnitude of the position.
  1992. */
  1993. Camera.prototype.getMagnitude = function () {
  1994. if (this._mode === SceneMode.SCENE3D) {
  1995. return Cartesian3.magnitude(this.position);
  1996. } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
  1997. return Math.abs(this.position.z);
  1998. } else if (this._mode === SceneMode.SCENE2D) {
  1999. return Math.max(
  2000. this.frustum.right - this.frustum.left,
  2001. this.frustum.top - this.frustum.bottom
  2002. );
  2003. }
  2004. };
  2005. const scratchLookAtMatrix4 = new Matrix4();
  2006. /**
  2007. * Sets the camera position and orientation using a target and offset. The target must be given in
  2008. * world coordinates. The offset can be either a cartesian or heading/pitch/range in the local east-north-up reference frame centered at the target.
  2009. * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset
  2010. * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix.
  2011. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  2012. * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center.
  2013. *
  2014. * In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
  2015. * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be
  2016. * determined from the offset, the heading will be north.
  2017. *
  2018. * @param {Cartesian3} target The target position in world coordinates.
  2019. * @param {Cartesian3|HeadingPitchRange} offset The offset from the target in the local east-north-up reference frame centered at the target.
  2020. *
  2021. * @exception {DeveloperError} lookAt is not supported while morphing.
  2022. *
  2023. * @example
  2024. * // 1. Using a cartesian offset
  2025. * const center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0);
  2026. * viewer.camera.lookAt(center, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));
  2027. *
  2028. * // 2. Using a HeadingPitchRange offset
  2029. * const center = Cesium.Cartesian3.fromDegrees(-72.0, 40.0);
  2030. * const heading = Cesium.Math.toRadians(50.0);
  2031. * const pitch = Cesium.Math.toRadians(-20.0);
  2032. * const range = 5000.0;
  2033. * viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range));
  2034. */
  2035. Camera.prototype.lookAt = function (target, offset) {
  2036. //>>includeStart('debug', pragmas.debug);
  2037. if (!defined(target)) {
  2038. throw new DeveloperError("target is required");
  2039. }
  2040. if (!defined(offset)) {
  2041. throw new DeveloperError("offset is required");
  2042. }
  2043. if (this._mode === SceneMode.MORPHING) {
  2044. throw new DeveloperError("lookAt is not supported while morphing.");
  2045. }
  2046. //>>includeEnd('debug');
  2047. const transform = Transforms.eastNorthUpToFixedFrame(
  2048. target,
  2049. Ellipsoid.WGS84,
  2050. scratchLookAtMatrix4
  2051. );
  2052. this.lookAtTransform(transform, offset);
  2053. };
  2054. const scratchLookAtHeadingPitchRangeOffset = new Cartesian3();
  2055. const scratchLookAtHeadingPitchRangeQuaternion1 = new Quaternion();
  2056. const scratchLookAtHeadingPitchRangeQuaternion2 = new Quaternion();
  2057. const scratchHeadingPitchRangeMatrix3 = new Matrix3();
  2058. function offsetFromHeadingPitchRange(heading, pitch, range) {
  2059. pitch = CesiumMath.clamp(
  2060. pitch,
  2061. -CesiumMath.PI_OVER_TWO,
  2062. CesiumMath.PI_OVER_TWO
  2063. );
  2064. heading = CesiumMath.zeroToTwoPi(heading) - CesiumMath.PI_OVER_TWO;
  2065. const pitchQuat = Quaternion.fromAxisAngle(
  2066. Cartesian3.UNIT_Y,
  2067. -pitch,
  2068. scratchLookAtHeadingPitchRangeQuaternion1
  2069. );
  2070. const headingQuat = Quaternion.fromAxisAngle(
  2071. Cartesian3.UNIT_Z,
  2072. -heading,
  2073. scratchLookAtHeadingPitchRangeQuaternion2
  2074. );
  2075. const rotQuat = Quaternion.multiply(headingQuat, pitchQuat, headingQuat);
  2076. const rotMatrix = Matrix3.fromQuaternion(
  2077. rotQuat,
  2078. scratchHeadingPitchRangeMatrix3
  2079. );
  2080. const offset = Cartesian3.clone(
  2081. Cartesian3.UNIT_X,
  2082. scratchLookAtHeadingPitchRangeOffset
  2083. );
  2084. Matrix3.multiplyByVector(rotMatrix, offset, offset);
  2085. Cartesian3.negate(offset, offset);
  2086. Cartesian3.multiplyByScalar(offset, range, offset);
  2087. return offset;
  2088. }
  2089. /**
  2090. * Sets the camera position and orientation using a target and transformation matrix. The offset can be either a cartesian or heading/pitch/range.
  2091. * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset
  2092. * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix.
  2093. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  2094. * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center.
  2095. *
  2096. * In 2D, there must be a top down view. The camera will be placed above the center of the reference frame. The height above the
  2097. * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be
  2098. * determined from the offset, the heading will be north.
  2099. *
  2100. * @param {Matrix4} transform The transformation matrix defining the reference frame.
  2101. * @param {Cartesian3|HeadingPitchRange} [offset] The offset from the target in a reference frame centered at the target.
  2102. *
  2103. * @exception {DeveloperError} lookAtTransform is not supported while morphing.
  2104. *
  2105. * @example
  2106. * // 1. Using a cartesian offset
  2107. * const transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-98.0, 40.0));
  2108. * viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));
  2109. *
  2110. * // 2. Using a HeadingPitchRange offset
  2111. * const transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-72.0, 40.0));
  2112. * const heading = Cesium.Math.toRadians(50.0);
  2113. * const pitch = Cesium.Math.toRadians(-20.0);
  2114. * const range = 5000.0;
  2115. * viewer.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(heading, pitch, range));
  2116. */
  2117. Camera.prototype.lookAtTransform = function (transform, offset) {
  2118. //>>includeStart('debug', pragmas.debug);
  2119. if (!defined(transform)) {
  2120. throw new DeveloperError("transform is required");
  2121. }
  2122. if (this._mode === SceneMode.MORPHING) {
  2123. throw new DeveloperError(
  2124. "lookAtTransform is not supported while morphing."
  2125. );
  2126. }
  2127. //>>includeEnd('debug');
  2128. this._setTransform(transform);
  2129. if (!defined(offset)) {
  2130. return;
  2131. }
  2132. let cartesianOffset;
  2133. if (defined(offset.heading)) {
  2134. cartesianOffset = offsetFromHeadingPitchRange(
  2135. offset.heading,
  2136. offset.pitch,
  2137. offset.range
  2138. );
  2139. } else {
  2140. cartesianOffset = offset;
  2141. }
  2142. if (this._mode === SceneMode.SCENE2D) {
  2143. Cartesian2.clone(Cartesian2.ZERO, this.position);
  2144. Cartesian3.negate(cartesianOffset, this.up);
  2145. this.up.z = 0.0;
  2146. if (Cartesian3.magnitudeSquared(this.up) < CesiumMath.EPSILON10) {
  2147. Cartesian3.clone(Cartesian3.UNIT_Y, this.up);
  2148. }
  2149. Cartesian3.normalize(this.up, this.up);
  2150. this._setTransform(Matrix4.IDENTITY);
  2151. Cartesian3.negate(Cartesian3.UNIT_Z, this.direction);
  2152. Cartesian3.cross(this.direction, this.up, this.right);
  2153. Cartesian3.normalize(this.right, this.right);
  2154. const frustum = this.frustum;
  2155. const ratio = frustum.top / frustum.right;
  2156. frustum.right = Cartesian3.magnitude(cartesianOffset) * 0.5;
  2157. frustum.left = -frustum.right;
  2158. frustum.top = ratio * frustum.right;
  2159. frustum.bottom = -frustum.top;
  2160. this._setTransform(transform);
  2161. return;
  2162. }
  2163. Cartesian3.clone(cartesianOffset, this.position);
  2164. Cartesian3.negate(this.position, this.direction);
  2165. Cartesian3.normalize(this.direction, this.direction);
  2166. Cartesian3.cross(this.direction, Cartesian3.UNIT_Z, this.right);
  2167. if (Cartesian3.magnitudeSquared(this.right) < CesiumMath.EPSILON10) {
  2168. Cartesian3.clone(Cartesian3.UNIT_X, this.right);
  2169. }
  2170. Cartesian3.normalize(this.right, this.right);
  2171. Cartesian3.cross(this.right, this.direction, this.up);
  2172. Cartesian3.normalize(this.up, this.up);
  2173. this._adjustOrthographicFrustum(true);
  2174. };
  2175. const viewRectangle3DCartographic1 = new Cartographic();
  2176. const viewRectangle3DCartographic2 = new Cartographic();
  2177. const viewRectangle3DNorthEast = new Cartesian3();
  2178. const viewRectangle3DSouthWest = new Cartesian3();
  2179. const viewRectangle3DNorthWest = new Cartesian3();
  2180. const viewRectangle3DSouthEast = new Cartesian3();
  2181. const viewRectangle3DNorthCenter = new Cartesian3();
  2182. const viewRectangle3DSouthCenter = new Cartesian3();
  2183. const viewRectangle3DCenter = new Cartesian3();
  2184. const viewRectangle3DEquator = new Cartesian3();
  2185. const defaultRF = {
  2186. direction: new Cartesian3(),
  2187. right: new Cartesian3(),
  2188. up: new Cartesian3(),
  2189. };
  2190. let viewRectangle3DEllipsoidGeodesic;
  2191. function computeD(direction, upOrRight, corner, tanThetaOrPhi) {
  2192. const opposite = Math.abs(Cartesian3.dot(upOrRight, corner));
  2193. return opposite / tanThetaOrPhi - Cartesian3.dot(direction, corner);
  2194. }
  2195. function rectangleCameraPosition3D(camera, rectangle, result, updateCamera) {
  2196. const ellipsoid = camera._projection.ellipsoid;
  2197. const cameraRF = updateCamera ? camera : defaultRF;
  2198. const north = rectangle.north;
  2199. const south = rectangle.south;
  2200. let east = rectangle.east;
  2201. const west = rectangle.west;
  2202. // If we go across the International Date Line
  2203. if (west > east) {
  2204. east += CesiumMath.TWO_PI;
  2205. }
  2206. // Find the midpoint latitude.
  2207. //
  2208. // EllipsoidGeodesic will fail if the north and south edges are very close to being on opposite sides of the ellipsoid.
  2209. // Ideally we'd just call EllipsoidGeodesic.setEndPoints and let it throw when it detects this case, but sadly it doesn't
  2210. // even look for this case in optimized builds, so we have to test for it here instead.
  2211. //
  2212. // Fortunately, this case can only happen (here) when north is very close to the north pole and south is very close to the south pole,
  2213. // so handle it just by using 0 latitude as the center. It's certainliy possible to use a smaller tolerance
  2214. // than one degree here, but one degree is safe and putting the center at 0 latitude should be good enough for any
  2215. // rectangle that spans 178+ of the 180 degrees of latitude.
  2216. const longitude = (west + east) * 0.5;
  2217. let latitude;
  2218. if (
  2219. south < -CesiumMath.PI_OVER_TWO + CesiumMath.RADIANS_PER_DEGREE &&
  2220. north > CesiumMath.PI_OVER_TWO - CesiumMath.RADIANS_PER_DEGREE
  2221. ) {
  2222. latitude = 0.0;
  2223. } else {
  2224. const northCartographic = viewRectangle3DCartographic1;
  2225. northCartographic.longitude = longitude;
  2226. northCartographic.latitude = north;
  2227. northCartographic.height = 0.0;
  2228. const southCartographic = viewRectangle3DCartographic2;
  2229. southCartographic.longitude = longitude;
  2230. southCartographic.latitude = south;
  2231. southCartographic.height = 0.0;
  2232. let ellipsoidGeodesic = viewRectangle3DEllipsoidGeodesic;
  2233. if (
  2234. !defined(ellipsoidGeodesic) ||
  2235. ellipsoidGeodesic.ellipsoid !== ellipsoid
  2236. ) {
  2237. viewRectangle3DEllipsoidGeodesic = ellipsoidGeodesic = new EllipsoidGeodesic(
  2238. undefined,
  2239. undefined,
  2240. ellipsoid
  2241. );
  2242. }
  2243. ellipsoidGeodesic.setEndPoints(northCartographic, southCartographic);
  2244. latitude = ellipsoidGeodesic.interpolateUsingFraction(
  2245. 0.5,
  2246. viewRectangle3DCartographic1
  2247. ).latitude;
  2248. }
  2249. const centerCartographic = viewRectangle3DCartographic1;
  2250. centerCartographic.longitude = longitude;
  2251. centerCartographic.latitude = latitude;
  2252. centerCartographic.height = 0.0;
  2253. const center = ellipsoid.cartographicToCartesian(
  2254. centerCartographic,
  2255. viewRectangle3DCenter
  2256. );
  2257. const cart = viewRectangle3DCartographic1;
  2258. cart.longitude = east;
  2259. cart.latitude = north;
  2260. const northEast = ellipsoid.cartographicToCartesian(
  2261. cart,
  2262. viewRectangle3DNorthEast
  2263. );
  2264. cart.longitude = west;
  2265. const northWest = ellipsoid.cartographicToCartesian(
  2266. cart,
  2267. viewRectangle3DNorthWest
  2268. );
  2269. cart.longitude = longitude;
  2270. const northCenter = ellipsoid.cartographicToCartesian(
  2271. cart,
  2272. viewRectangle3DNorthCenter
  2273. );
  2274. cart.latitude = south;
  2275. const southCenter = ellipsoid.cartographicToCartesian(
  2276. cart,
  2277. viewRectangle3DSouthCenter
  2278. );
  2279. cart.longitude = east;
  2280. const southEast = ellipsoid.cartographicToCartesian(
  2281. cart,
  2282. viewRectangle3DSouthEast
  2283. );
  2284. cart.longitude = west;
  2285. const southWest = ellipsoid.cartographicToCartesian(
  2286. cart,
  2287. viewRectangle3DSouthWest
  2288. );
  2289. Cartesian3.subtract(northWest, center, northWest);
  2290. Cartesian3.subtract(southEast, center, southEast);
  2291. Cartesian3.subtract(northEast, center, northEast);
  2292. Cartesian3.subtract(southWest, center, southWest);
  2293. Cartesian3.subtract(northCenter, center, northCenter);
  2294. Cartesian3.subtract(southCenter, center, southCenter);
  2295. const direction = ellipsoid.geodeticSurfaceNormal(center, cameraRF.direction);
  2296. Cartesian3.negate(direction, direction);
  2297. const right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, cameraRF.right);
  2298. Cartesian3.normalize(right, right);
  2299. const up = Cartesian3.cross(right, direction, cameraRF.up);
  2300. let d;
  2301. if (camera.frustum instanceof OrthographicFrustum) {
  2302. const width = Math.max(
  2303. Cartesian3.distance(northEast, northWest),
  2304. Cartesian3.distance(southEast, southWest)
  2305. );
  2306. const height = Math.max(
  2307. Cartesian3.distance(northEast, southEast),
  2308. Cartesian3.distance(northWest, southWest)
  2309. );
  2310. let rightScalar;
  2311. let topScalar;
  2312. const offCenterFrustum = camera.frustum._offCenterFrustum;
  2313. const ratio = offCenterFrustum.right / offCenterFrustum.top;
  2314. const heightRatio = height * ratio;
  2315. if (width > heightRatio) {
  2316. rightScalar = width;
  2317. topScalar = rightScalar / ratio;
  2318. } else {
  2319. topScalar = height;
  2320. rightScalar = heightRatio;
  2321. }
  2322. d = Math.max(rightScalar, topScalar);
  2323. } else {
  2324. const tanPhi = Math.tan(camera.frustum.fovy * 0.5);
  2325. const tanTheta = camera.frustum.aspectRatio * tanPhi;
  2326. d = Math.max(
  2327. computeD(direction, up, northWest, tanPhi),
  2328. computeD(direction, up, southEast, tanPhi),
  2329. computeD(direction, up, northEast, tanPhi),
  2330. computeD(direction, up, southWest, tanPhi),
  2331. computeD(direction, up, northCenter, tanPhi),
  2332. computeD(direction, up, southCenter, tanPhi),
  2333. computeD(direction, right, northWest, tanTheta),
  2334. computeD(direction, right, southEast, tanTheta),
  2335. computeD(direction, right, northEast, tanTheta),
  2336. computeD(direction, right, southWest, tanTheta),
  2337. computeD(direction, right, northCenter, tanTheta),
  2338. computeD(direction, right, southCenter, tanTheta)
  2339. );
  2340. // If the rectangle crosses the equator, compute D at the equator, too, because that's the
  2341. // widest part of the rectangle when projected onto the globe.
  2342. if (south < 0 && north > 0) {
  2343. const equatorCartographic = viewRectangle3DCartographic1;
  2344. equatorCartographic.longitude = west;
  2345. equatorCartographic.latitude = 0.0;
  2346. equatorCartographic.height = 0.0;
  2347. let equatorPosition = ellipsoid.cartographicToCartesian(
  2348. equatorCartographic,
  2349. viewRectangle3DEquator
  2350. );
  2351. Cartesian3.subtract(equatorPosition, center, equatorPosition);
  2352. d = Math.max(
  2353. d,
  2354. computeD(direction, up, equatorPosition, tanPhi),
  2355. computeD(direction, right, equatorPosition, tanTheta)
  2356. );
  2357. equatorCartographic.longitude = east;
  2358. equatorPosition = ellipsoid.cartographicToCartesian(
  2359. equatorCartographic,
  2360. viewRectangle3DEquator
  2361. );
  2362. Cartesian3.subtract(equatorPosition, center, equatorPosition);
  2363. d = Math.max(
  2364. d,
  2365. computeD(direction, up, equatorPosition, tanPhi),
  2366. computeD(direction, right, equatorPosition, tanTheta)
  2367. );
  2368. }
  2369. }
  2370. return Cartesian3.add(
  2371. center,
  2372. Cartesian3.multiplyByScalar(direction, -d, viewRectangle3DEquator),
  2373. result
  2374. );
  2375. }
  2376. const viewRectangleCVCartographic = new Cartographic();
  2377. const viewRectangleCVNorthEast = new Cartesian3();
  2378. const viewRectangleCVSouthWest = new Cartesian3();
  2379. function rectangleCameraPositionColumbusView(camera, rectangle, result) {
  2380. const projection = camera._projection;
  2381. if (rectangle.west > rectangle.east) {
  2382. rectangle = Rectangle.MAX_VALUE;
  2383. }
  2384. const transform = camera._actualTransform;
  2385. const invTransform = camera._actualInvTransform;
  2386. const cart = viewRectangleCVCartographic;
  2387. cart.longitude = rectangle.east;
  2388. cart.latitude = rectangle.north;
  2389. const northEast = projection.project(cart, viewRectangleCVNorthEast);
  2390. Matrix4.multiplyByPoint(transform, northEast, northEast);
  2391. Matrix4.multiplyByPoint(invTransform, northEast, northEast);
  2392. cart.longitude = rectangle.west;
  2393. cart.latitude = rectangle.south;
  2394. const southWest = projection.project(cart, viewRectangleCVSouthWest);
  2395. Matrix4.multiplyByPoint(transform, southWest, southWest);
  2396. Matrix4.multiplyByPoint(invTransform, southWest, southWest);
  2397. result.x = (northEast.x - southWest.x) * 0.5 + southWest.x;
  2398. result.y = (northEast.y - southWest.y) * 0.5 + southWest.y;
  2399. if (defined(camera.frustum.fovy)) {
  2400. const tanPhi = Math.tan(camera.frustum.fovy * 0.5);
  2401. const tanTheta = camera.frustum.aspectRatio * tanPhi;
  2402. result.z =
  2403. Math.max(
  2404. (northEast.x - southWest.x) / tanTheta,
  2405. (northEast.y - southWest.y) / tanPhi
  2406. ) * 0.5;
  2407. } else {
  2408. const width = northEast.x - southWest.x;
  2409. const height = northEast.y - southWest.y;
  2410. result.z = Math.max(width, height);
  2411. }
  2412. return result;
  2413. }
  2414. const viewRectangle2DCartographic = new Cartographic();
  2415. const viewRectangle2DNorthEast = new Cartesian3();
  2416. const viewRectangle2DSouthWest = new Cartesian3();
  2417. function rectangleCameraPosition2D(camera, rectangle, result) {
  2418. const projection = camera._projection;
  2419. // Account for the rectangle crossing the International Date Line in 2D mode
  2420. let east = rectangle.east;
  2421. if (rectangle.west > rectangle.east) {
  2422. if (camera._scene.mapMode2D === MapMode2D.INFINITE_SCROLL) {
  2423. east += CesiumMath.TWO_PI;
  2424. } else {
  2425. rectangle = Rectangle.MAX_VALUE;
  2426. east = rectangle.east;
  2427. }
  2428. }
  2429. let cart = viewRectangle2DCartographic;
  2430. cart.longitude = east;
  2431. cart.latitude = rectangle.north;
  2432. const northEast = projection.project(cart, viewRectangle2DNorthEast);
  2433. cart.longitude = rectangle.west;
  2434. cart.latitude = rectangle.south;
  2435. const southWest = projection.project(cart, viewRectangle2DSouthWest);
  2436. const width = Math.abs(northEast.x - southWest.x) * 0.5;
  2437. let height = Math.abs(northEast.y - southWest.y) * 0.5;
  2438. let right, top;
  2439. const ratio = camera.frustum.right / camera.frustum.top;
  2440. const heightRatio = height * ratio;
  2441. if (width > heightRatio) {
  2442. right = width;
  2443. top = right / ratio;
  2444. } else {
  2445. top = height;
  2446. right = heightRatio;
  2447. }
  2448. height = Math.max(2.0 * right, 2.0 * top);
  2449. result.x = (northEast.x - southWest.x) * 0.5 + southWest.x;
  2450. result.y = (northEast.y - southWest.y) * 0.5 + southWest.y;
  2451. cart = projection.unproject(result, cart);
  2452. cart.height = height;
  2453. result = projection.project(cart, result);
  2454. return result;
  2455. }
  2456. /**
  2457. * Get the camera position needed to view a rectangle on an ellipsoid or map
  2458. *
  2459. * @param {Rectangle} rectangle The rectangle to view.
  2460. * @param {Cartesian3} [result] The camera position needed to view the rectangle
  2461. * @returns {Cartesian3} The camera position needed to view the rectangle
  2462. */
  2463. Camera.prototype.getRectangleCameraCoordinates = function (rectangle, result) {
  2464. //>>includeStart('debug', pragmas.debug);
  2465. if (!defined(rectangle)) {
  2466. throw new DeveloperError("rectangle is required");
  2467. }
  2468. //>>includeEnd('debug');
  2469. const mode = this._mode;
  2470. if (!defined(result)) {
  2471. result = new Cartesian3();
  2472. }
  2473. if (mode === SceneMode.SCENE3D) {
  2474. return rectangleCameraPosition3D(this, rectangle, result);
  2475. } else if (mode === SceneMode.COLUMBUS_VIEW) {
  2476. return rectangleCameraPositionColumbusView(this, rectangle, result);
  2477. } else if (mode === SceneMode.SCENE2D) {
  2478. return rectangleCameraPosition2D(this, rectangle, result);
  2479. }
  2480. return undefined;
  2481. };
  2482. const pickEllipsoid3DRay = new Ray();
  2483. function pickEllipsoid3D(camera, windowPosition, ellipsoid, result) {
  2484. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  2485. const ray = camera.getPickRay(windowPosition, pickEllipsoid3DRay);
  2486. const intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
  2487. if (!intersection) {
  2488. return undefined;
  2489. }
  2490. const t = intersection.start > 0.0 ? intersection.start : intersection.stop;
  2491. return Ray.getPoint(ray, t, result);
  2492. }
  2493. const pickEllipsoid2DRay = new Ray();
  2494. function pickMap2D(camera, windowPosition, projection, result) {
  2495. const ray = camera.getPickRay(windowPosition, pickEllipsoid2DRay);
  2496. let position = ray.origin;
  2497. position = Cartesian3.fromElements(position.y, position.z, 0.0, position);
  2498. const cart = projection.unproject(position);
  2499. if (
  2500. cart.latitude < -CesiumMath.PI_OVER_TWO ||
  2501. cart.latitude > CesiumMath.PI_OVER_TWO
  2502. ) {
  2503. return undefined;
  2504. }
  2505. return projection.ellipsoid.cartographicToCartesian(cart, result);
  2506. }
  2507. const pickEllipsoidCVRay = new Ray();
  2508. function pickMapColumbusView(camera, windowPosition, projection, result) {
  2509. const ray = camera.getPickRay(windowPosition, pickEllipsoidCVRay);
  2510. const scalar = -ray.origin.x / ray.direction.x;
  2511. Ray.getPoint(ray, scalar, result);
  2512. const cart = projection.unproject(new Cartesian3(result.y, result.z, 0.0));
  2513. if (
  2514. cart.latitude < -CesiumMath.PI_OVER_TWO ||
  2515. cart.latitude > CesiumMath.PI_OVER_TWO ||
  2516. cart.longitude < -Math.PI ||
  2517. cart.longitude > Math.PI
  2518. ) {
  2519. return undefined;
  2520. }
  2521. return projection.ellipsoid.cartographicToCartesian(cart, result);
  2522. }
  2523. /**
  2524. * Pick an ellipsoid or map.
  2525. *
  2526. * @param {Cartesian2} windowPosition The x and y coordinates of a pixel.
  2527. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to pick.
  2528. * @param {Cartesian3} [result] The object onto which to store the result.
  2529. * @returns {Cartesian3 | undefined} If the ellipsoid or map was picked,
  2530. * returns the point on the surface of the ellipsoid or map in world
  2531. * coordinates. If the ellipsoid or map was not picked, returns undefined.
  2532. *
  2533. * @example
  2534. * const canvas = viewer.scene.canvas;
  2535. * const center = new Cesium.Cartesian2(canvas.clientWidth / 2.0, canvas.clientHeight / 2.0);
  2536. * const ellipsoid = viewer.scene.globe.ellipsoid;
  2537. * const result = viewer.camera.pickEllipsoid(center, ellipsoid);
  2538. */
  2539. Camera.prototype.pickEllipsoid = function (windowPosition, ellipsoid, result) {
  2540. //>>includeStart('debug', pragmas.debug);
  2541. if (!defined(windowPosition)) {
  2542. throw new DeveloperError("windowPosition is required.");
  2543. }
  2544. //>>includeEnd('debug');
  2545. const canvas = this._scene.canvas;
  2546. if (canvas.clientWidth === 0 || canvas.clientHeight === 0) {
  2547. return undefined;
  2548. }
  2549. if (!defined(result)) {
  2550. result = new Cartesian3();
  2551. }
  2552. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  2553. if (this._mode === SceneMode.SCENE3D) {
  2554. result = pickEllipsoid3D(this, windowPosition, ellipsoid, result);
  2555. } else if (this._mode === SceneMode.SCENE2D) {
  2556. result = pickMap2D(this, windowPosition, this._projection, result);
  2557. } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
  2558. result = pickMapColumbusView(
  2559. this,
  2560. windowPosition,
  2561. this._projection,
  2562. result
  2563. );
  2564. } else {
  2565. return undefined;
  2566. }
  2567. return result;
  2568. };
  2569. const pickPerspCenter = new Cartesian3();
  2570. const pickPerspXDir = new Cartesian3();
  2571. const pickPerspYDir = new Cartesian3();
  2572. function getPickRayPerspective(camera, windowPosition, result) {
  2573. const canvas = camera._scene.canvas;
  2574. const width = canvas.clientWidth;
  2575. const height = canvas.clientHeight;
  2576. const tanPhi = Math.tan(camera.frustum.fovy * 0.5);
  2577. const tanTheta = camera.frustum.aspectRatio * tanPhi;
  2578. const near = camera.frustum.near;
  2579. const x = (2.0 / width) * windowPosition.x - 1.0;
  2580. const y = (2.0 / height) * (height - windowPosition.y) - 1.0;
  2581. const position = camera.positionWC;
  2582. Cartesian3.clone(position, result.origin);
  2583. const nearCenter = Cartesian3.multiplyByScalar(
  2584. camera.directionWC,
  2585. near,
  2586. pickPerspCenter
  2587. );
  2588. Cartesian3.add(position, nearCenter, nearCenter);
  2589. const xDir = Cartesian3.multiplyByScalar(
  2590. camera.rightWC,
  2591. x * near * tanTheta,
  2592. pickPerspXDir
  2593. );
  2594. const yDir = Cartesian3.multiplyByScalar(
  2595. camera.upWC,
  2596. y * near * tanPhi,
  2597. pickPerspYDir
  2598. );
  2599. const direction = Cartesian3.add(nearCenter, xDir, result.direction);
  2600. Cartesian3.add(direction, yDir, direction);
  2601. Cartesian3.subtract(direction, position, direction);
  2602. Cartesian3.normalize(direction, direction);
  2603. return result;
  2604. }
  2605. const scratchDirection = new Cartesian3();
  2606. function getPickRayOrthographic(camera, windowPosition, result) {
  2607. const canvas = camera._scene.canvas;
  2608. const width = canvas.clientWidth;
  2609. const height = canvas.clientHeight;
  2610. let frustum = camera.frustum;
  2611. const offCenterFrustum = frustum.offCenterFrustum;
  2612. if (defined(offCenterFrustum)) {
  2613. frustum = offCenterFrustum;
  2614. }
  2615. let x = (2.0 / width) * windowPosition.x - 1.0;
  2616. x *= (frustum.right - frustum.left) * 0.5;
  2617. let y = (2.0 / height) * (height - windowPosition.y) - 1.0;
  2618. y *= (frustum.top - frustum.bottom) * 0.5;
  2619. const origin = result.origin;
  2620. Cartesian3.clone(camera.position, origin);
  2621. Cartesian3.multiplyByScalar(camera.right, x, scratchDirection);
  2622. Cartesian3.add(scratchDirection, origin, origin);
  2623. Cartesian3.multiplyByScalar(camera.up, y, scratchDirection);
  2624. Cartesian3.add(scratchDirection, origin, origin);
  2625. Cartesian3.clone(camera.directionWC, result.direction);
  2626. if (
  2627. camera._mode === SceneMode.COLUMBUS_VIEW ||
  2628. camera._mode === SceneMode.SCENE2D
  2629. ) {
  2630. Cartesian3.fromElements(
  2631. result.origin.z,
  2632. result.origin.x,
  2633. result.origin.y,
  2634. result.origin
  2635. );
  2636. }
  2637. return result;
  2638. }
  2639. /**
  2640. * Create a ray from the camera position through the pixel at <code>windowPosition</code>
  2641. * in world coordinates.
  2642. *
  2643. * @param {Cartesian2} windowPosition The x and y coordinates of a pixel.
  2644. * @param {Ray} [result] The object onto which to store the result.
  2645. * @returns {Ray|undefined} Returns the {@link Cartesian3} position and direction of the ray, or undefined if the pick ray cannot be determined.
  2646. */
  2647. Camera.prototype.getPickRay = function (windowPosition, result) {
  2648. //>>includeStart('debug', pragmas.debug);
  2649. if (!defined(windowPosition)) {
  2650. throw new DeveloperError("windowPosition is required.");
  2651. }
  2652. //>>includeEnd('debug');
  2653. if (!defined(result)) {
  2654. result = new Ray();
  2655. }
  2656. const canvas = this._scene.canvas;
  2657. if (canvas.clientWidth <= 0 || canvas.clientHeight <= 0) {
  2658. return undefined;
  2659. }
  2660. const frustum = this.frustum;
  2661. if (
  2662. defined(frustum.aspectRatio) &&
  2663. defined(frustum.fov) &&
  2664. defined(frustum.near)
  2665. ) {
  2666. return getPickRayPerspective(this, windowPosition, result);
  2667. }
  2668. return getPickRayOrthographic(this, windowPosition, result);
  2669. };
  2670. const scratchToCenter = new Cartesian3();
  2671. const scratchProj = new Cartesian3();
  2672. /**
  2673. * Return the distance from the camera to the front of the bounding sphere.
  2674. *
  2675. * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates.
  2676. * @returns {number} The distance to the bounding sphere.
  2677. */
  2678. Camera.prototype.distanceToBoundingSphere = function (boundingSphere) {
  2679. //>>includeStart('debug', pragmas.debug);
  2680. if (!defined(boundingSphere)) {
  2681. throw new DeveloperError("boundingSphere is required.");
  2682. }
  2683. //>>includeEnd('debug');
  2684. const toCenter = Cartesian3.subtract(
  2685. this.positionWC,
  2686. boundingSphere.center,
  2687. scratchToCenter
  2688. );
  2689. const proj = Cartesian3.multiplyByScalar(
  2690. this.directionWC,
  2691. Cartesian3.dot(toCenter, this.directionWC),
  2692. scratchProj
  2693. );
  2694. return Math.max(0.0, Cartesian3.magnitude(proj) - boundingSphere.radius);
  2695. };
  2696. const scratchPixelSize = new Cartesian2();
  2697. /**
  2698. * Return the pixel size in meters.
  2699. *
  2700. * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates.
  2701. * @param {number} drawingBufferWidth The drawing buffer width.
  2702. * @param {number} drawingBufferHeight The drawing buffer height.
  2703. * @returns {number} The pixel size in meters.
  2704. */
  2705. Camera.prototype.getPixelSize = function (
  2706. boundingSphere,
  2707. drawingBufferWidth,
  2708. drawingBufferHeight
  2709. ) {
  2710. //>>includeStart('debug', pragmas.debug);
  2711. if (!defined(boundingSphere)) {
  2712. throw new DeveloperError("boundingSphere is required.");
  2713. }
  2714. if (!defined(drawingBufferWidth)) {
  2715. throw new DeveloperError("drawingBufferWidth is required.");
  2716. }
  2717. if (!defined(drawingBufferHeight)) {
  2718. throw new DeveloperError("drawingBufferHeight is required.");
  2719. }
  2720. //>>includeEnd('debug');
  2721. const distance = this.distanceToBoundingSphere(boundingSphere);
  2722. const pixelSize = this.frustum.getPixelDimensions(
  2723. drawingBufferWidth,
  2724. drawingBufferHeight,
  2725. distance,
  2726. this._scene.pixelRatio,
  2727. scratchPixelSize
  2728. );
  2729. return Math.max(pixelSize.x, pixelSize.y);
  2730. };
  2731. function createAnimationTemplateCV(
  2732. camera,
  2733. position,
  2734. center,
  2735. maxX,
  2736. maxY,
  2737. duration
  2738. ) {
  2739. const newPosition = Cartesian3.clone(position);
  2740. if (center.y > maxX) {
  2741. newPosition.y -= center.y - maxX;
  2742. } else if (center.y < -maxX) {
  2743. newPosition.y += -maxX - center.y;
  2744. }
  2745. if (center.z > maxY) {
  2746. newPosition.z -= center.z - maxY;
  2747. } else if (center.z < -maxY) {
  2748. newPosition.z += -maxY - center.z;
  2749. }
  2750. function updateCV(value) {
  2751. const interp = Cartesian3.lerp(
  2752. position,
  2753. newPosition,
  2754. value.time,
  2755. new Cartesian3()
  2756. );
  2757. camera.worldToCameraCoordinatesPoint(interp, camera.position);
  2758. }
  2759. return {
  2760. easingFunction: EasingFunction.EXPONENTIAL_OUT,
  2761. startObject: {
  2762. time: 0.0,
  2763. },
  2764. stopObject: {
  2765. time: 1.0,
  2766. },
  2767. duration: duration,
  2768. update: updateCV,
  2769. };
  2770. }
  2771. const normalScratch = new Cartesian3();
  2772. const centerScratch = new Cartesian3();
  2773. const posScratch = new Cartesian3();
  2774. const scratchCartesian3Subtract = new Cartesian3();
  2775. function createAnimationCV(camera, duration) {
  2776. let position = camera.position;
  2777. const direction = camera.direction;
  2778. const normal = camera.worldToCameraCoordinatesVector(
  2779. Cartesian3.UNIT_X,
  2780. normalScratch
  2781. );
  2782. const scalar =
  2783. -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);
  2784. const center = Cartesian3.add(
  2785. position,
  2786. Cartesian3.multiplyByScalar(direction, scalar, centerScratch),
  2787. centerScratch
  2788. );
  2789. camera.cameraToWorldCoordinatesPoint(center, center);
  2790. position = camera.cameraToWorldCoordinatesPoint(camera.position, posScratch);
  2791. const tanPhi = Math.tan(camera.frustum.fovy * 0.5);
  2792. const tanTheta = camera.frustum.aspectRatio * tanPhi;
  2793. const distToC = Cartesian3.magnitude(
  2794. Cartesian3.subtract(position, center, scratchCartesian3Subtract)
  2795. );
  2796. const dWidth = tanTheta * distToC;
  2797. const dHeight = tanPhi * distToC;
  2798. const mapWidth = camera._maxCoord.x;
  2799. const mapHeight = camera._maxCoord.y;
  2800. const maxX = Math.max(dWidth - mapWidth, mapWidth);
  2801. const maxY = Math.max(dHeight - mapHeight, mapHeight);
  2802. if (
  2803. position.z < -maxX ||
  2804. position.z > maxX ||
  2805. position.y < -maxY ||
  2806. position.y > maxY
  2807. ) {
  2808. const translateX = center.y < -maxX || center.y > maxX;
  2809. const translateY = center.z < -maxY || center.z > maxY;
  2810. if (translateX || translateY) {
  2811. return createAnimationTemplateCV(
  2812. camera,
  2813. position,
  2814. center,
  2815. maxX,
  2816. maxY,
  2817. duration
  2818. );
  2819. }
  2820. }
  2821. return undefined;
  2822. }
  2823. /**
  2824. * Create an animation to move the map into view. This method is only valid for 2D and Columbus modes.
  2825. *
  2826. * @param {number} duration The duration, in seconds, of the animation.
  2827. * @returns {object} The animation or undefined if the scene mode is 3D or the map is already ion view.
  2828. *
  2829. * @private
  2830. */
  2831. Camera.prototype.createCorrectPositionTween = function (duration) {
  2832. //>>includeStart('debug', pragmas.debug);
  2833. if (!defined(duration)) {
  2834. throw new DeveloperError("duration is required.");
  2835. }
  2836. //>>includeEnd('debug');
  2837. if (this._mode === SceneMode.COLUMBUS_VIEW) {
  2838. return createAnimationCV(this, duration);
  2839. }
  2840. return undefined;
  2841. };
  2842. const scratchFlyToDestination = new Cartesian3();
  2843. const newOptions = {
  2844. destination: undefined,
  2845. heading: undefined,
  2846. pitch: undefined,
  2847. roll: undefined,
  2848. duration: undefined,
  2849. complete: undefined,
  2850. cancel: undefined,
  2851. endTransform: undefined,
  2852. maximumHeight: undefined,
  2853. easingFunction: undefined,
  2854. };
  2855. /**
  2856. * Cancels the current camera flight and leaves the camera at its current location.
  2857. * If no flight is in progress, this this function does nothing.
  2858. */
  2859. Camera.prototype.cancelFlight = function () {
  2860. if (defined(this._currentFlight)) {
  2861. this._currentFlight.cancelTween();
  2862. this._currentFlight = undefined;
  2863. }
  2864. };
  2865. /**
  2866. * Completes the current camera flight and moves the camera immediately to its final destination.
  2867. * If no flight is in progress, this this function does nothing.
  2868. */
  2869. Camera.prototype.completeFlight = function () {
  2870. if (defined(this._currentFlight)) {
  2871. this._currentFlight.cancelTween();
  2872. const options = {
  2873. destination: undefined,
  2874. orientation: {
  2875. heading: undefined,
  2876. pitch: undefined,
  2877. roll: undefined,
  2878. },
  2879. };
  2880. options.destination = newOptions.destination;
  2881. options.orientation.heading = newOptions.heading;
  2882. options.orientation.pitch = newOptions.pitch;
  2883. options.orientation.roll = newOptions.roll;
  2884. this.setView(options);
  2885. if (defined(this._currentFlight.complete)) {
  2886. this._currentFlight.complete();
  2887. }
  2888. this._currentFlight = undefined;
  2889. }
  2890. };
  2891. /**
  2892. * Flies the camera from its current position to a new position.
  2893. *
  2894. * @param {object} options Object with the following properties:
  2895. * @param {Cartesian3|Rectangle} options.destination The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view.
  2896. * @param {object} [options.orientation] An object that contains either direction and up properties or heading, pitch and roll properties. By default, the direction will point
  2897. * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive
  2898. * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode.
  2899. * @param {number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight.
  2900. * @param {Camera.FlightCompleteCallback} [options.complete] The function to execute when the flight is complete.
  2901. * @param {Camera.FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled.
  2902. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed.
  2903. * @param {number} [options.maximumHeight] The maximum height at the peak of the flight.
  2904. * @param {number} [options.pitchAdjustHeight] If camera flyes higher than that value, adjust pitch duiring the flight to look down, and keep Earth in viewport.
  2905. * @param {number} [options.flyOverLongitude] There are always two ways between 2 points on globe. This option force camera to choose fight direction to fly over that longitude.
  2906. * @param {number} [options.flyOverLongitudeWeight] Fly over the lon specifyed via flyOverLongitude only if that way is not longer than short way times flyOverLongitudeWeight.
  2907. * @param {boolean} [options.convert] Whether to convert the destination from world coordinates to scene coordinates (only relevant when not using 3D). Defaults to <code>true</code>.
  2908. * @param {EasingFunction.Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight.
  2909. *
  2910. * @exception {DeveloperError} If either direction or up is given, then both are required.
  2911. *
  2912. * @example
  2913. * // 1. Fly to a position with a top-down view
  2914. * viewer.camera.flyTo({
  2915. * destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
  2916. * });
  2917. *
  2918. * // 2. Fly to a Rectangle with a top-down view
  2919. * viewer.camera.flyTo({
  2920. * destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
  2921. * });
  2922. *
  2923. * // 3. Fly to a position with an orientation using unit vectors.
  2924. * viewer.camera.flyTo({
  2925. * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
  2926. * orientation : {
  2927. * direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
  2928. * up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
  2929. * }
  2930. * });
  2931. *
  2932. * // 4. Fly to a position with an orientation using heading, pitch and roll.
  2933. * viewer.camera.flyTo({
  2934. * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
  2935. * orientation : {
  2936. * heading : Cesium.Math.toRadians(175.0),
  2937. * pitch : Cesium.Math.toRadians(-35.0),
  2938. * roll : 0.0
  2939. * }
  2940. * });
  2941. */
  2942. Camera.prototype.flyTo = function (options) {
  2943. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2944. let destination = options.destination;
  2945. //>>includeStart('debug', pragmas.debug);
  2946. if (!defined(destination)) {
  2947. throw new DeveloperError("destination is required.");
  2948. }
  2949. //>>includeEnd('debug');
  2950. const mode = this._mode;
  2951. if (mode === SceneMode.MORPHING) {
  2952. return;
  2953. }
  2954. this.cancelFlight();
  2955. const isRectangle = destination instanceof Rectangle;
  2956. if (isRectangle) {
  2957. destination = this.getRectangleCameraCoordinates(
  2958. destination,
  2959. scratchFlyToDestination
  2960. );
  2961. }
  2962. let orientation = defaultValue(
  2963. options.orientation,
  2964. defaultValue.EMPTY_OBJECT
  2965. );
  2966. if (defined(orientation.direction)) {
  2967. orientation = directionUpToHeadingPitchRoll(
  2968. this,
  2969. destination,
  2970. orientation,
  2971. scratchSetViewOptions.orientation
  2972. );
  2973. }
  2974. if (defined(options.duration) && options.duration <= 0.0) {
  2975. const setViewOptions = scratchSetViewOptions;
  2976. setViewOptions.destination = options.destination;
  2977. setViewOptions.orientation.heading = orientation.heading;
  2978. setViewOptions.orientation.pitch = orientation.pitch;
  2979. setViewOptions.orientation.roll = orientation.roll;
  2980. setViewOptions.convert = options.convert;
  2981. setViewOptions.endTransform = options.endTransform;
  2982. this.setView(setViewOptions);
  2983. if (typeof options.complete === "function") {
  2984. options.complete();
  2985. }
  2986. return;
  2987. }
  2988. const that = this;
  2989. /* eslint-disable-next-line prefer-const */
  2990. let flightTween;
  2991. newOptions.destination = destination;
  2992. newOptions.heading = orientation.heading;
  2993. newOptions.pitch = orientation.pitch;
  2994. newOptions.roll = orientation.roll;
  2995. newOptions.duration = options.duration;
  2996. newOptions.complete = function () {
  2997. if (flightTween === that._currentFlight) {
  2998. that._currentFlight = undefined;
  2999. }
  3000. if (defined(options.complete)) {
  3001. options.complete();
  3002. }
  3003. };
  3004. newOptions.cancel = options.cancel;
  3005. newOptions.endTransform = options.endTransform;
  3006. newOptions.convert = isRectangle ? false : options.convert;
  3007. newOptions.maximumHeight = options.maximumHeight;
  3008. newOptions.pitchAdjustHeight = options.pitchAdjustHeight;
  3009. newOptions.flyOverLongitude = options.flyOverLongitude;
  3010. newOptions.flyOverLongitudeWeight = options.flyOverLongitudeWeight;
  3011. newOptions.easingFunction = options.easingFunction;
  3012. const scene = this._scene;
  3013. const tweenOptions = CameraFlightPath.createTween(scene, newOptions);
  3014. // If the camera doesn't actually need to go anywhere, duration
  3015. // will be 0 and we can just complete the current flight.
  3016. if (tweenOptions.duration === 0) {
  3017. if (typeof tweenOptions.complete === "function") {
  3018. tweenOptions.complete();
  3019. }
  3020. return;
  3021. }
  3022. flightTween = scene.tweens.add(tweenOptions);
  3023. this._currentFlight = flightTween;
  3024. // Save the final destination view information for the PRELOAD_FLIGHT pass.
  3025. let preloadFlightCamera = this._scene.preloadFlightCamera;
  3026. if (this._mode !== SceneMode.SCENE2D) {
  3027. if (!defined(preloadFlightCamera)) {
  3028. preloadFlightCamera = Camera.clone(this);
  3029. }
  3030. preloadFlightCamera.setView({
  3031. destination: destination,
  3032. orientation: orientation,
  3033. });
  3034. this._scene.preloadFlightCullingVolume = preloadFlightCamera.frustum.computeCullingVolume(
  3035. preloadFlightCamera.positionWC,
  3036. preloadFlightCamera.directionWC,
  3037. preloadFlightCamera.upWC
  3038. );
  3039. }
  3040. };
  3041. function distanceToBoundingSphere3D(camera, radius) {
  3042. const frustum = camera.frustum;
  3043. const tanPhi = Math.tan(frustum.fovy * 0.5);
  3044. const tanTheta = frustum.aspectRatio * tanPhi;
  3045. return Math.max(radius / tanTheta, radius / tanPhi);
  3046. }
  3047. function distanceToBoundingSphere2D(camera, radius) {
  3048. let frustum = camera.frustum;
  3049. const offCenterFrustum = frustum.offCenterFrustum;
  3050. if (defined(offCenterFrustum)) {
  3051. frustum = offCenterFrustum;
  3052. }
  3053. let right, top;
  3054. const ratio = frustum.right / frustum.top;
  3055. const heightRatio = radius * ratio;
  3056. if (radius > heightRatio) {
  3057. right = radius;
  3058. top = right / ratio;
  3059. } else {
  3060. top = radius;
  3061. right = heightRatio;
  3062. }
  3063. return Math.max(right, top) * 1.5;
  3064. }
  3065. const MINIMUM_ZOOM = 100.0;
  3066. function adjustBoundingSphereOffset(camera, boundingSphere, offset) {
  3067. offset = HeadingPitchRange.clone(
  3068. defined(offset) ? offset : Camera.DEFAULT_OFFSET
  3069. );
  3070. const minimumZoom =
  3071. camera._scene.screenSpaceCameraController.minimumZoomDistance;
  3072. const maximumZoom =
  3073. camera._scene.screenSpaceCameraController.maximumZoomDistance;
  3074. const range = offset.range;
  3075. if (!defined(range) || range === 0.0) {
  3076. const radius = boundingSphere.radius;
  3077. if (radius === 0.0) {
  3078. offset.range = MINIMUM_ZOOM;
  3079. } else if (
  3080. camera.frustum instanceof OrthographicFrustum ||
  3081. camera._mode === SceneMode.SCENE2D
  3082. ) {
  3083. offset.range = distanceToBoundingSphere2D(camera, radius);
  3084. } else {
  3085. offset.range = distanceToBoundingSphere3D(camera, radius);
  3086. }
  3087. offset.range = CesiumMath.clamp(offset.range, minimumZoom, maximumZoom);
  3088. }
  3089. return offset;
  3090. }
  3091. /**
  3092. * Sets the camera so that the current view contains the provided bounding sphere.
  3093. *
  3094. * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
  3095. * The heading and the pitch angles are defined in the local east-north-up reference frame.
  3096. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  3097. * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is
  3098. * zero, a range will be computed such that the whole bounding sphere is visible.</p>
  3099. *
  3100. * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
  3101. * target will be the range. The heading will be determined from the offset. If the heading cannot be
  3102. * determined from the offset, the heading will be north.</p>
  3103. *
  3104. * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates.
  3105. * @param {HeadingPitchRange} [offset] The offset from the target in the local east-north-up reference frame centered at the target.
  3106. *
  3107. * @exception {DeveloperError} viewBoundingSphere is not supported while morphing.
  3108. */
  3109. Camera.prototype.viewBoundingSphere = function (boundingSphere, offset) {
  3110. //>>includeStart('debug', pragmas.debug);
  3111. if (!defined(boundingSphere)) {
  3112. throw new DeveloperError("boundingSphere is required.");
  3113. }
  3114. if (this._mode === SceneMode.MORPHING) {
  3115. throw new DeveloperError(
  3116. "viewBoundingSphere is not supported while morphing."
  3117. );
  3118. }
  3119. //>>includeEnd('debug');
  3120. offset = adjustBoundingSphereOffset(this, boundingSphere, offset);
  3121. this.lookAt(boundingSphere.center, offset);
  3122. };
  3123. const scratchflyToBoundingSphereTransform = new Matrix4();
  3124. const scratchflyToBoundingSphereDestination = new Cartesian3();
  3125. const scratchflyToBoundingSphereDirection = new Cartesian3();
  3126. const scratchflyToBoundingSphereUp = new Cartesian3();
  3127. const scratchflyToBoundingSphereRight = new Cartesian3();
  3128. const scratchFlyToBoundingSphereCart4 = new Cartesian4();
  3129. const scratchFlyToBoundingSphereQuaternion = new Quaternion();
  3130. const scratchFlyToBoundingSphereMatrix3 = new Matrix3();
  3131. /**
  3132. * Flies the camera to a location where the current view contains the provided bounding sphere.
  3133. *
  3134. * <p> The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
  3135. * The heading and the pitch angles are defined in the local east-north-up reference frame.
  3136. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  3137. * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is
  3138. * zero, a range will be computed such that the whole bounding sphere is visible.</p>
  3139. *
  3140. * <p>In 2D and Columbus View, there must be a top down view. The camera will be placed above the target looking down. The height above the
  3141. * target will be the range. The heading will be aligned to local north.</p>
  3142. *
  3143. * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates.
  3144. * @param {object} [options] Object with the following properties:
  3145. * @param {number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight.
  3146. * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
  3147. * @param {Camera.FlightCompleteCallback} [options.complete] The function to execute when the flight is complete.
  3148. * @param {Camera.FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled.
  3149. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed.
  3150. * @param {number} [options.maximumHeight] The maximum height at the peak of the flight.
  3151. * @param {number} [options.pitchAdjustHeight] If camera flyes higher than that value, adjust pitch duiring the flight to look down, and keep Earth in viewport.
  3152. * @param {number} [options.flyOverLongitude] There are always two ways between 2 points on globe. This option force camera to choose fight direction to fly over that longitude.
  3153. * @param {number} [options.flyOverLongitudeWeight] Fly over the lon specifyed via flyOverLongitude only if that way is not longer than short way times flyOverLongitudeWeight.
  3154. * @param {EasingFunction.Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight.
  3155. */
  3156. Camera.prototype.flyToBoundingSphere = function (boundingSphere, options) {
  3157. //>>includeStart('debug', pragmas.debug);
  3158. if (!defined(boundingSphere)) {
  3159. throw new DeveloperError("boundingSphere is required.");
  3160. }
  3161. //>>includeEnd('debug');
  3162. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  3163. const scene2D =
  3164. this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW;
  3165. this._setTransform(Matrix4.IDENTITY);
  3166. const offset = adjustBoundingSphereOffset(
  3167. this,
  3168. boundingSphere,
  3169. options.offset
  3170. );
  3171. let position;
  3172. if (scene2D) {
  3173. position = Cartesian3.multiplyByScalar(
  3174. Cartesian3.UNIT_Z,
  3175. offset.range,
  3176. scratchflyToBoundingSphereDestination
  3177. );
  3178. } else {
  3179. position = offsetFromHeadingPitchRange(
  3180. offset.heading,
  3181. offset.pitch,
  3182. offset.range
  3183. );
  3184. }
  3185. const transform = Transforms.eastNorthUpToFixedFrame(
  3186. boundingSphere.center,
  3187. Ellipsoid.WGS84,
  3188. scratchflyToBoundingSphereTransform
  3189. );
  3190. Matrix4.multiplyByPoint(transform, position, position);
  3191. let direction;
  3192. let up;
  3193. if (!scene2D) {
  3194. direction = Cartesian3.subtract(
  3195. boundingSphere.center,
  3196. position,
  3197. scratchflyToBoundingSphereDirection
  3198. );
  3199. Cartesian3.normalize(direction, direction);
  3200. up = Matrix4.multiplyByPointAsVector(
  3201. transform,
  3202. Cartesian3.UNIT_Z,
  3203. scratchflyToBoundingSphereUp
  3204. );
  3205. if (1.0 - Math.abs(Cartesian3.dot(direction, up)) < CesiumMath.EPSILON6) {
  3206. const rotateQuat = Quaternion.fromAxisAngle(
  3207. direction,
  3208. offset.heading,
  3209. scratchFlyToBoundingSphereQuaternion
  3210. );
  3211. const rotation = Matrix3.fromQuaternion(
  3212. rotateQuat,
  3213. scratchFlyToBoundingSphereMatrix3
  3214. );
  3215. Cartesian3.fromCartesian4(
  3216. Matrix4.getColumn(transform, 1, scratchFlyToBoundingSphereCart4),
  3217. up
  3218. );
  3219. Matrix3.multiplyByVector(rotation, up, up);
  3220. }
  3221. const right = Cartesian3.cross(
  3222. direction,
  3223. up,
  3224. scratchflyToBoundingSphereRight
  3225. );
  3226. Cartesian3.cross(right, direction, up);
  3227. Cartesian3.normalize(up, up);
  3228. }
  3229. this.flyTo({
  3230. destination: position,
  3231. orientation: {
  3232. direction: direction,
  3233. up: up,
  3234. },
  3235. duration: options.duration,
  3236. complete: options.complete,
  3237. cancel: options.cancel,
  3238. endTransform: options.endTransform,
  3239. maximumHeight: options.maximumHeight,
  3240. easingFunction: options.easingFunction,
  3241. flyOverLongitude: options.flyOverLongitude,
  3242. flyOverLongitudeWeight: options.flyOverLongitudeWeight,
  3243. pitchAdjustHeight: options.pitchAdjustHeight,
  3244. });
  3245. };
  3246. const scratchCartesian3_1 = new Cartesian3();
  3247. const scratchCartesian3_2 = new Cartesian3();
  3248. const scratchCartesian3_3 = new Cartesian3();
  3249. const scratchCartesian3_4 = new Cartesian3();
  3250. const horizonPoints = [
  3251. new Cartesian3(),
  3252. new Cartesian3(),
  3253. new Cartesian3(),
  3254. new Cartesian3(),
  3255. ];
  3256. function computeHorizonQuad(camera, ellipsoid) {
  3257. const radii = ellipsoid.radii;
  3258. const p = camera.positionWC;
  3259. // Find the corresponding position in the scaled space of the ellipsoid.
  3260. const q = Cartesian3.multiplyComponents(
  3261. ellipsoid.oneOverRadii,
  3262. p,
  3263. scratchCartesian3_1
  3264. );
  3265. const qMagnitude = Cartesian3.magnitude(q);
  3266. const qUnit = Cartesian3.normalize(q, scratchCartesian3_2);
  3267. // Determine the east and north directions at q.
  3268. let eUnit;
  3269. let nUnit;
  3270. if (
  3271. Cartesian3.equalsEpsilon(qUnit, Cartesian3.UNIT_Z, CesiumMath.EPSILON10)
  3272. ) {
  3273. eUnit = new Cartesian3(0, 1, 0);
  3274. nUnit = new Cartesian3(0, 0, 1);
  3275. } else {
  3276. eUnit = Cartesian3.normalize(
  3277. Cartesian3.cross(Cartesian3.UNIT_Z, qUnit, scratchCartesian3_3),
  3278. scratchCartesian3_3
  3279. );
  3280. nUnit = Cartesian3.normalize(
  3281. Cartesian3.cross(qUnit, eUnit, scratchCartesian3_4),
  3282. scratchCartesian3_4
  3283. );
  3284. }
  3285. // Determine the radius of the 'limb' of the ellipsoid.
  3286. const wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0);
  3287. // Compute the center and offsets.
  3288. const center = Cartesian3.multiplyByScalar(
  3289. qUnit,
  3290. 1.0 / qMagnitude,
  3291. scratchCartesian3_1
  3292. );
  3293. const scalar = wMagnitude / qMagnitude;
  3294. const eastOffset = Cartesian3.multiplyByScalar(
  3295. eUnit,
  3296. scalar,
  3297. scratchCartesian3_2
  3298. );
  3299. const northOffset = Cartesian3.multiplyByScalar(
  3300. nUnit,
  3301. scalar,
  3302. scratchCartesian3_3
  3303. );
  3304. // A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
  3305. const upperLeft = Cartesian3.add(center, northOffset, horizonPoints[0]);
  3306. Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
  3307. Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);
  3308. const lowerLeft = Cartesian3.subtract(center, northOffset, horizonPoints[1]);
  3309. Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
  3310. Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);
  3311. const lowerRight = Cartesian3.subtract(center, northOffset, horizonPoints[2]);
  3312. Cartesian3.add(lowerRight, eastOffset, lowerRight);
  3313. Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);
  3314. const upperRight = Cartesian3.add(center, northOffset, horizonPoints[3]);
  3315. Cartesian3.add(upperRight, eastOffset, upperRight);
  3316. Cartesian3.multiplyComponents(radii, upperRight, upperRight);
  3317. return horizonPoints;
  3318. }
  3319. const scratchPickCartesian2 = new Cartesian2();
  3320. const scratchRectCartesian = new Cartesian3();
  3321. const cartoArray = [
  3322. new Cartographic(),
  3323. new Cartographic(),
  3324. new Cartographic(),
  3325. new Cartographic(),
  3326. ];
  3327. function addToResult(x, y, index, camera, ellipsoid, computedHorizonQuad) {
  3328. scratchPickCartesian2.x = x;
  3329. scratchPickCartesian2.y = y;
  3330. const r = camera.pickEllipsoid(
  3331. scratchPickCartesian2,
  3332. ellipsoid,
  3333. scratchRectCartesian
  3334. );
  3335. if (defined(r)) {
  3336. cartoArray[index] = ellipsoid.cartesianToCartographic(r, cartoArray[index]);
  3337. return 1;
  3338. }
  3339. cartoArray[index] = ellipsoid.cartesianToCartographic(
  3340. computedHorizonQuad[index],
  3341. cartoArray[index]
  3342. );
  3343. return 0;
  3344. }
  3345. /**
  3346. * Computes the approximate visible rectangle on the ellipsoid.
  3347. *
  3348. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that you want to know the visible region.
  3349. * @param {Rectangle} [result] The rectangle in which to store the result
  3350. *
  3351. * @returns {Rectangle|undefined} The visible rectangle or undefined if the ellipsoid isn't visible at all.
  3352. */
  3353. Camera.prototype.computeViewRectangle = function (ellipsoid, result) {
  3354. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  3355. const cullingVolume = this.frustum.computeCullingVolume(
  3356. this.positionWC,
  3357. this.directionWC,
  3358. this.upWC
  3359. );
  3360. const boundingSphere = new BoundingSphere(
  3361. Cartesian3.ZERO,
  3362. ellipsoid.maximumRadius
  3363. );
  3364. const visibility = cullingVolume.computeVisibility(boundingSphere);
  3365. if (visibility === Intersect.OUTSIDE) {
  3366. return undefined;
  3367. }
  3368. const canvas = this._scene.canvas;
  3369. const width = canvas.clientWidth;
  3370. const height = canvas.clientHeight;
  3371. let successfulPickCount = 0;
  3372. const computedHorizonQuad = computeHorizonQuad(this, ellipsoid);
  3373. successfulPickCount += addToResult(
  3374. 0,
  3375. 0,
  3376. 0,
  3377. this,
  3378. ellipsoid,
  3379. computedHorizonQuad
  3380. );
  3381. successfulPickCount += addToResult(
  3382. 0,
  3383. height,
  3384. 1,
  3385. this,
  3386. ellipsoid,
  3387. computedHorizonQuad
  3388. );
  3389. successfulPickCount += addToResult(
  3390. width,
  3391. height,
  3392. 2,
  3393. this,
  3394. ellipsoid,
  3395. computedHorizonQuad
  3396. );
  3397. successfulPickCount += addToResult(
  3398. width,
  3399. 0,
  3400. 3,
  3401. this,
  3402. ellipsoid,
  3403. computedHorizonQuad
  3404. );
  3405. if (successfulPickCount < 2) {
  3406. // If we have space non-globe in 3 or 4 corners then return the whole globe
  3407. return Rectangle.MAX_VALUE;
  3408. }
  3409. result = Rectangle.fromCartographicArray(cartoArray, result);
  3410. // Detect if we go over the poles
  3411. let distance = 0;
  3412. let lastLon = cartoArray[3].longitude;
  3413. for (let i = 0; i < 4; ++i) {
  3414. const lon = cartoArray[i].longitude;
  3415. const diff = Math.abs(lon - lastLon);
  3416. if (diff > CesiumMath.PI) {
  3417. // Crossed the dateline
  3418. distance += CesiumMath.TWO_PI - diff;
  3419. } else {
  3420. distance += diff;
  3421. }
  3422. lastLon = lon;
  3423. }
  3424. // We are over one of the poles so adjust the rectangle accordingly
  3425. if (
  3426. CesiumMath.equalsEpsilon(
  3427. Math.abs(distance),
  3428. CesiumMath.TWO_PI,
  3429. CesiumMath.EPSILON9
  3430. )
  3431. ) {
  3432. result.west = -CesiumMath.PI;
  3433. result.east = CesiumMath.PI;
  3434. if (cartoArray[0].latitude >= 0.0) {
  3435. result.north = CesiumMath.PI_OVER_TWO;
  3436. } else {
  3437. result.south = -CesiumMath.PI_OVER_TWO;
  3438. }
  3439. }
  3440. return result;
  3441. };
  3442. /**
  3443. * Switches the frustum/projection to perspective.
  3444. *
  3445. * This function is a no-op in 2D which must always be orthographic.
  3446. */
  3447. Camera.prototype.switchToPerspectiveFrustum = function () {
  3448. if (
  3449. this._mode === SceneMode.SCENE2D ||
  3450. this.frustum instanceof PerspectiveFrustum
  3451. ) {
  3452. return;
  3453. }
  3454. const scene = this._scene;
  3455. this.frustum = new PerspectiveFrustum();
  3456. this.frustum.aspectRatio =
  3457. scene.drawingBufferWidth / scene.drawingBufferHeight;
  3458. this.frustum.fov = CesiumMath.toRadians(60.0);
  3459. };
  3460. /**
  3461. * Switches the frustum/projection to orthographic.
  3462. *
  3463. * This function is a no-op in 2D which will always be orthographic.
  3464. */
  3465. Camera.prototype.switchToOrthographicFrustum = function () {
  3466. if (
  3467. this._mode === SceneMode.SCENE2D ||
  3468. this.frustum instanceof OrthographicFrustum
  3469. ) {
  3470. return;
  3471. }
  3472. // This must be called before changing the frustum because it uses the previous
  3473. // frustum to reconstruct the world space position from the depth buffer.
  3474. const frustumWidth = calculateOrthographicFrustumWidth(this);
  3475. const scene = this._scene;
  3476. this.frustum = new OrthographicFrustum();
  3477. this.frustum.aspectRatio =
  3478. scene.drawingBufferWidth / scene.drawingBufferHeight;
  3479. this.frustum.width = frustumWidth;
  3480. };
  3481. /**
  3482. * @private
  3483. */
  3484. Camera.clone = function (camera, result) {
  3485. if (!defined(result)) {
  3486. result = new Camera(camera._scene);
  3487. }
  3488. Cartesian3.clone(camera.position, result.position);
  3489. Cartesian3.clone(camera.direction, result.direction);
  3490. Cartesian3.clone(camera.up, result.up);
  3491. Cartesian3.clone(camera.right, result.right);
  3492. Matrix4.clone(camera._transform, result.transform);
  3493. result._transformChanged = true;
  3494. result.frustum = camera.frustum.clone();
  3495. return result;
  3496. };
  3497. /**
  3498. * A function that will execute when a flight completes.
  3499. * @callback Camera.FlightCompleteCallback
  3500. */
  3501. /**
  3502. * A function that will execute when a flight is cancelled.
  3503. * @callback Camera.FlightCancelledCallback
  3504. */
  3505. export default Camera;