智慧水务管理系统 - 精河县供水工程综合管理平台

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