| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933 | import Cartesian2 from "../Core/Cartesian2.js";import Cartesian3 from "../Core/Cartesian3.js";import Cartesian4 from "../Core/Cartesian4.js";import Cartographic from "../Core/Cartographic.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import Ellipsoid from "../Core/Ellipsoid.js";import HeadingPitchRoll from "../Core/HeadingPitchRoll.js";import IntersectionTests from "../Core/IntersectionTests.js";import KeyboardEventModifier from "../Core/KeyboardEventModifier.js";import CesiumMath from "../Core/Math.js";import Matrix3 from "../Core/Matrix3.js";import Matrix4 from "../Core/Matrix4.js";import OrthographicFrustum from "../Core/OrthographicFrustum.js";import Plane from "../Core/Plane.js";import Quaternion from "../Core/Quaternion.js";import Ray from "../Core/Ray.js";import TerrainExaggeration from "../Core/TerrainExaggeration.js";import Transforms from "../Core/Transforms.js";import CameraEventAggregator from "./CameraEventAggregator.js";import CameraEventType from "./CameraEventType.js";import MapMode2D from "./MapMode2D.js";import SceneMode from "./SceneMode.js";import SceneTransforms from "./SceneTransforms.js";import TweenCollection from "./TweenCollection.js";/** * Modifies the camera position and orientation based on mouse input to a canvas. * @alias ScreenSpaceCameraController * @constructor * * @param {Scene} scene The scene. */function ScreenSpaceCameraController(scene) {  //>>includeStart('debug', pragmas.debug);  if (!defined(scene)) {    throw new DeveloperError("scene is required.");  }  //>>includeEnd('debug');  /**   * If true, inputs are allowed conditionally with the flags enableTranslate, enableZoom,   * enableRotate, enableTilt, and enableLook.  If false, all inputs are disabled.   *   * NOTE: This setting is for temporary use cases, such as camera flights and   * drag-selection of regions (see Picking demo).  It is typically set to false at the   * start of such events, and set true on completion.  To keep inputs disabled   * past the end of camera flights, you must use the other booleans (enableTranslate,   * enableZoom, enableRotate, enableTilt, and enableLook).   * @type {Boolean}   * @default true   */  this.enableInputs = true;  /**   * If true, allows the user to pan around the map.  If false, the camera stays locked at the current position.   * This flag only applies in 2D and Columbus view modes.   * @type {Boolean}   * @default true   */  this.enableTranslate = true;  /**   * If true, allows the user to zoom in and out.  If false, the camera is locked to the current distance from the ellipsoid.   * @type {Boolean}   * @default true   */  this.enableZoom = true;  /**   * If true, allows the user to rotate the world which translates the user's position.   * This flag only applies in 2D and 3D.   * @type {Boolean}   * @default true   */  this.enableRotate = true;  /**   * If true, allows the user to tilt the camera.  If false, the camera is locked to the current heading.   * This flag only applies in 3D and Columbus view.   * @type {Boolean}   * @default true   */  this.enableTilt = true;  /**   * If true, allows the user to use free-look. If false, the camera view direction can only be changed through translating   * or rotating. This flag only applies in 3D and Columbus view modes.   * @type {Boolean}   * @default true   */  this.enableLook = true;  /**   * A parameter in the range <code>[0, 1)</code> used to determine how long   * the camera will continue to spin because of inertia.   * With value of zero, the camera will have no inertia.   * @type {Number}   * @default 0.9   */  this.inertiaSpin = 0.9;  /**   * A parameter in the range <code>[0, 1)</code> used to determine how long   * the camera will continue to translate because of inertia.   * With value of zero, the camera will have no inertia.   * @type {Number}   * @default 0.9   */  this.inertiaTranslate = 0.9;  /**   * A parameter in the range <code>[0, 1)</code> used to determine how long   * the camera will continue to zoom because of inertia.   * With value of zero, the camera will have no inertia.   * @type {Number}   * @default 0.8   */  this.inertiaZoom = 0.8;  /**   * A parameter in the range <code>[0, 1)</code> used to limit the range   * of various user inputs to a percentage of the window width/height per animation frame.   * This helps keep the camera under control in low-frame-rate situations.   * @type {Number}   * @default 0.1   */  this.maximumMovementRatio = 0.1;  /**   * Sets the duration, in seconds, of the bounce back animations in 2D and Columbus view.   * @type {Number}   * @default 3.0   */  this.bounceAnimationTime = 3.0;  /**   * The minimum magnitude, in meters, of the camera position when zooming. Defaults to 1.0.   * @type {Number}   * @default 1.0   */  this.minimumZoomDistance = 1.0;  /**   * The maximum magnitude, in meters, of the camera position when zooming. Defaults to positive infinity.   * @type {Number}   * @default {@link Number.POSITIVE_INFINITY}   */  this.maximumZoomDistance = Number.POSITIVE_INFINITY;  /**   * The input that allows the user to pan around the map. This only applies in 2D and Columbus view modes.   * <p>   * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>   * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},   * or an array of any of the preceding.   * </p>   * @type {CameraEventType|Array|undefined}   * @default {@link CameraEventType.LEFT_DRAG}   */  this.translateEventTypes = CameraEventType.LEFT_DRAG;  /**   * The input that allows the user to zoom in/out.   * <p>   * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>   * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},   * or an array of any of the preceding.   * </p>   * @type {CameraEventType|Array|undefined}   * @default [{@link CameraEventType.RIGHT_DRAG}, {@link CameraEventType.WHEEL}, {@link CameraEventType.PINCH}]   */  this.zoomEventTypes = [    CameraEventType.RIGHT_DRAG,    CameraEventType.WHEEL,    CameraEventType.PINCH,  ];  /**   * The input that allows the user to rotate around the globe or another object. This only applies in 3D and Columbus view modes.   * <p>   * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>   * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},   * or an array of any of the preceding.   * </p>   * @type {CameraEventType|Array|undefined}   * @default {@link CameraEventType.LEFT_DRAG}   */  this.rotateEventTypes = CameraEventType.LEFT_DRAG;  /**   * The input that allows the user to tilt in 3D and Columbus view or twist in 2D.   * <p>   * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>   * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},   * or an array of any of the preceding.   * </p>   * @type {CameraEventType|Array|undefined}   * @default [{@link CameraEventType.MIDDLE_DRAG}, {@link CameraEventType.PINCH}, {   *     eventType : {@link CameraEventType.LEFT_DRAG},   *     modifier : {@link KeyboardEventModifier.CTRL}   * }, {   *     eventType : {@link CameraEventType.RIGHT_DRAG},   *     modifier : {@link KeyboardEventModifier.CTRL}   * }]   */  this.tiltEventTypes = [    CameraEventType.MIDDLE_DRAG,    CameraEventType.PINCH,    {      eventType: CameraEventType.LEFT_DRAG,      modifier: KeyboardEventModifier.CTRL,    },    {      eventType: CameraEventType.RIGHT_DRAG,      modifier: KeyboardEventModifier.CTRL,    },  ];  /**   * The input that allows the user to change the direction the camera is viewing. This only applies in 3D and Columbus view modes.   * <p>   * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>   * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},   * or an array of any of the preceding.   * </p>   * @type {CameraEventType|Array|undefined}   * @default { eventType : {@link CameraEventType.LEFT_DRAG}, modifier : {@link KeyboardEventModifier.SHIFT} }   */  this.lookEventTypes = {    eventType: CameraEventType.LEFT_DRAG,    modifier: KeyboardEventModifier.SHIFT,  };  /**   * The minimum height the camera must be before picking the terrain instead of the ellipsoid.   * @type {Number}   * @default 150000.0   */  this.minimumPickingTerrainHeight = 150000.0;  this._minimumPickingTerrainHeight = this.minimumPickingTerrainHeight;  /**   * The minimum height the camera must be before testing for collision with terrain.   * @type {Number}   * @default 15000.0   */  this.minimumCollisionTerrainHeight = 15000.0;  this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight;  /**   * The minimum height the camera must be before switching from rotating a track ball to   * free look when clicks originate on the sky or in space.   * @type {Number}   * @default 7500000.0   */  this.minimumTrackBallHeight = 7500000.0;  this._minimumTrackBallHeight = this.minimumTrackBallHeight;  /**   * Enables or disables camera collision detection with terrain.   * @type {Boolean}   * @default true   */  this.enableCollisionDetection = true;  this._scene = scene;  this._globe = undefined;  this._ellipsoid = undefined;  this._aggregator = new CameraEventAggregator(scene.canvas);  this._lastInertiaSpinMovement = undefined;  this._lastInertiaZoomMovement = undefined;  this._lastInertiaTranslateMovement = undefined;  this._lastInertiaTiltMovement = undefined;  // Zoom disables tilt, spin, and translate inertia  // Tilt disables spin and translate inertia  this._inertiaDisablers = {    _lastInertiaZoomMovement: [      "_lastInertiaSpinMovement",      "_lastInertiaTranslateMovement",      "_lastInertiaTiltMovement",    ],    _lastInertiaTiltMovement: [      "_lastInertiaSpinMovement",      "_lastInertiaTranslateMovement",    ],  };  this._tweens = new TweenCollection();  this._tween = undefined;  this._horizontalRotationAxis = undefined;  this._tiltCenterMousePosition = new Cartesian2(-1.0, -1.0);  this._tiltCenter = new Cartesian3();  this._rotateMousePosition = new Cartesian2(-1.0, -1.0);  this._rotateStartPosition = new Cartesian3();  this._strafeStartPosition = new Cartesian3();  this._strafeMousePosition = new Cartesian2();  this._strafeEndMousePosition = new Cartesian2();  this._zoomMouseStart = new Cartesian2(-1.0, -1.0);  this._zoomWorldPosition = new Cartesian3();  this._useZoomWorldPosition = false;  this._tiltCVOffMap = false;  this._looking = false;  this._rotating = false;  this._strafing = false;  this._zoomingOnVector = false;  this._zoomingUnderground = false;  this._rotatingZoom = false;  this._adjustedHeightForTerrain = false;  this._cameraUnderground = false;  const projection = scene.mapProjection;  this._maxCoord = projection.project(    new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)  );  // Constants, Make any of these public?  this._zoomFactor = 5.0;  this._rotateFactor = undefined;  this._rotateRateRangeAdjustment = undefined;  this._maximumRotateRate = 1.77;  this._minimumRotateRate = 1.0 / 5000.0;  this._minimumZoomRate = 20.0;  this._maximumZoomRate = 5906376272000.0; // distance from the Sun to Pluto in meters.  this._minimumUndergroundPickDistance = 2000.0;  this._maximumUndergroundPickDistance = 10000.0;}function decay(time, coefficient) {  if (time < 0) {    return 0.0;  }  const tau = (1.0 - coefficient) * 25.0;  return Math.exp(-tau * time);}function sameMousePosition(movement) {  return Cartesian2.equalsEpsilon(    movement.startPosition,    movement.endPosition,    CesiumMath.EPSILON14  );}// If the time between mouse down and mouse up is not between// these thresholds, the camera will not move with inertia.// This value is probably dependent on the browser and/or the// hardware. Should be investigated further.const inertiaMaxClickTimeThreshold = 0.4;function maintainInertia(  aggregator,  type,  modifier,  decayCoef,  action,  object,  lastMovementName) {  let movementState = object[lastMovementName];  if (!defined(movementState)) {    movementState = object[lastMovementName] = {      startPosition: new Cartesian2(),      endPosition: new Cartesian2(),      motion: new Cartesian2(),      inertiaEnabled: true,    };  }  const ts = aggregator.getButtonPressTime(type, modifier);  const tr = aggregator.getButtonReleaseTime(type, modifier);  const threshold = ts && tr && (tr.getTime() - ts.getTime()) / 1000.0;  const now = new Date();  const fromNow = tr && (now.getTime() - tr.getTime()) / 1000.0;  if (ts && tr && threshold < inertiaMaxClickTimeThreshold) {    const d = decay(fromNow, decayCoef);    const lastMovement = aggregator.getLastMovement(type, modifier);    if (      !defined(lastMovement) ||      sameMousePosition(lastMovement) ||      !movementState.inertiaEnabled    ) {      return;    }    movementState.motion.x =      (lastMovement.endPosition.x - lastMovement.startPosition.x) * 0.5;    movementState.motion.y =      (lastMovement.endPosition.y - lastMovement.startPosition.y) * 0.5;    movementState.startPosition = Cartesian2.clone(      lastMovement.startPosition,      movementState.startPosition    );    movementState.endPosition = Cartesian2.multiplyByScalar(      movementState.motion,      d,      movementState.endPosition    );    movementState.endPosition = Cartesian2.add(      movementState.startPosition,      movementState.endPosition,      movementState.endPosition    );    // If value from the decreasing exponential function is close to zero,    // the end coordinates may be NaN.    if (      isNaN(movementState.endPosition.x) ||      isNaN(movementState.endPosition.y) ||      Cartesian2.distance(        movementState.startPosition,        movementState.endPosition      ) < 0.5    ) {      return;    }    if (!aggregator.isButtonDown(type, modifier)) {      const startPosition = aggregator.getStartMousePosition(type, modifier);      action(object, startPosition, movementState);    }  }}function activateInertia(controller, inertiaStateName) {  if (defined(inertiaStateName)) {    // Re-enable inertia if it was disabled    let movementState = controller[inertiaStateName];    if (defined(movementState)) {      movementState.inertiaEnabled = true;    }    // Disable inertia on other movements    const inertiasToDisable = controller._inertiaDisablers[inertiaStateName];    if (defined(inertiasToDisable)) {      const length = inertiasToDisable.length;      for (let i = 0; i < length; ++i) {        movementState = controller[inertiasToDisable[i]];        if (defined(movementState)) {          movementState.inertiaEnabled = false;        }      }    }  }}const scratchEventTypeArray = [];function reactToInput(  controller,  enabled,  eventTypes,  action,  inertiaConstant,  inertiaStateName) {  if (!defined(eventTypes)) {    return;  }  const aggregator = controller._aggregator;  if (!Array.isArray(eventTypes)) {    scratchEventTypeArray[0] = eventTypes;    eventTypes = scratchEventTypeArray;  }  const length = eventTypes.length;  for (let i = 0; i < length; ++i) {    const eventType = eventTypes[i];    const type = defined(eventType.eventType) ? eventType.eventType : eventType;    const modifier = eventType.modifier;    const movement =      aggregator.isMoving(type, modifier) &&      aggregator.getMovement(type, modifier);    const startPosition = aggregator.getStartMousePosition(type, modifier);    if (controller.enableInputs && enabled) {      if (movement) {        action(controller, startPosition, movement);        activateInertia(controller, inertiaStateName);      } else if (inertiaConstant < 1.0) {        maintainInertia(          aggregator,          type,          modifier,          inertiaConstant,          action,          controller,          inertiaStateName        );      }    }  }}const scratchZoomPickRay = new Ray();const scratchPickCartesian = new Cartesian3();const scratchZoomOffset = new Cartesian2();const scratchZoomDirection = new Cartesian3();const scratchCenterPixel = new Cartesian2();const scratchCenterPosition = new Cartesian3();const scratchPositionNormal = new Cartesian3();const scratchPickNormal = new Cartesian3();const scratchZoomAxis = new Cartesian3();const scratchCameraPositionNormal = new Cartesian3();// Scratch variables used in zooming algorithmconst scratchTargetNormal = new Cartesian3();const scratchCameraPosition = new Cartesian3();const scratchCameraUpNormal = new Cartesian3();const scratchCameraRightNormal = new Cartesian3();const scratchForwardNormal = new Cartesian3();const scratchPositionToTarget = new Cartesian3();const scratchPositionToTargetNormal = new Cartesian3();const scratchPan = new Cartesian3();const scratchCenterMovement = new Cartesian3();const scratchCenter = new Cartesian3();const scratchCartesian = new Cartesian3();const scratchCartesianTwo = new Cartesian3();const scratchCartesianThree = new Cartesian3();const scratchZoomViewOptions = {  orientation: new HeadingPitchRoll(),};function handleZoom(  object,  startPosition,  movement,  zoomFactor,  distanceMeasure,  unitPositionDotDirection) {  let percentage = 1.0;  if (defined(unitPositionDotDirection)) {    percentage = CesiumMath.clamp(      Math.abs(unitPositionDotDirection),      0.25,      1.0    );  }  const diff = movement.endPosition.y - movement.startPosition.y;  // distanceMeasure should be the height above the ellipsoid.  // When approaching the surface, the zoomRate slows and stops minimumZoomDistance above it.  const approachingSurface = diff > 0;  const minHeight = approachingSurface    ? object.minimumZoomDistance * percentage    : 0;  const maxHeight = object.maximumZoomDistance;  const minDistance = distanceMeasure - minHeight;  let zoomRate = zoomFactor * minDistance;  zoomRate = CesiumMath.clamp(    zoomRate,    object._minimumZoomRate,    object._maximumZoomRate  );  let rangeWindowRatio = diff / object._scene.canvas.clientHeight;  rangeWindowRatio = Math.min(rangeWindowRatio, object.maximumMovementRatio);  let distance = zoomRate * rangeWindowRatio;  if (    object.enableCollisionDetection ||    object.minimumZoomDistance === 0.0 ||    !defined(object._globe) // look-at mode  ) {    if (distance > 0.0 && Math.abs(distanceMeasure - minHeight) < 1.0) {      return;    }    if (distance < 0.0 && Math.abs(distanceMeasure - maxHeight) < 1.0) {      return;    }    if (distanceMeasure - distance < minHeight) {      distance = distanceMeasure - minHeight - 1.0;    } else if (distanceMeasure - distance > maxHeight) {      distance = distanceMeasure - maxHeight;    }  }  const scene = object._scene;  const camera = scene.camera;  const mode = scene.mode;  const orientation = scratchZoomViewOptions.orientation;  orientation.heading = camera.heading;  orientation.pitch = camera.pitch;  orientation.roll = camera.roll;  if (camera.frustum instanceof OrthographicFrustum) {    if (Math.abs(distance) > 0.0) {      camera.zoomIn(distance);      camera._adjustOrthographicFrustum();    }    return;  }  const sameStartPosition = Cartesian2.equals(    startPosition,    object._zoomMouseStart  );  let zoomingOnVector = object._zoomingOnVector;  let rotatingZoom = object._rotatingZoom;  let pickedPosition;  if (!sameStartPosition) {    object._zoomMouseStart = Cartesian2.clone(      startPosition,      object._zoomMouseStart    );    if (defined(object._globe)) {      if (mode === SceneMode.SCENE2D) {        pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay)          .origin;        pickedPosition = Cartesian3.fromElements(          pickedPosition.y,          pickedPosition.z,          pickedPosition.x        );      } else {        pickedPosition = pickGlobe(object, startPosition, scratchPickCartesian);      }    }    if (defined(pickedPosition)) {      object._useZoomWorldPosition = true;      object._zoomWorldPosition = Cartesian3.clone(        pickedPosition,        object._zoomWorldPosition      );    } else {      object._useZoomWorldPosition = false;    }    zoomingOnVector = object._zoomingOnVector = false;    rotatingZoom = object._rotatingZoom = false;    object._zoomingUnderground = object._cameraUnderground;  }  if (!object._useZoomWorldPosition) {    camera.zoomIn(distance);    return;  }  let zoomOnVector = mode === SceneMode.COLUMBUS_VIEW;  if (camera.positionCartographic.height < 2000000) {    rotatingZoom = true;  }  if (!sameStartPosition || rotatingZoom) {    if (mode === SceneMode.SCENE2D) {      const worldPosition = object._zoomWorldPosition;      const endPosition = camera.position;      if (        !Cartesian3.equals(worldPosition, endPosition) &&        camera.positionCartographic.height < object._maxCoord.x * 2.0      ) {        const savedX = camera.position.x;        const direction = Cartesian3.subtract(          worldPosition,          endPosition,          scratchZoomDirection        );        Cartesian3.normalize(direction, direction);        const d =          (Cartesian3.distance(worldPosition, endPosition) * distance) /          (camera.getMagnitude() * 0.5);        camera.move(direction, d * 0.5);        if (          (camera.position.x < 0.0 && savedX > 0.0) ||          (camera.position.x > 0.0 && savedX < 0.0)        ) {          pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay)            .origin;          pickedPosition = Cartesian3.fromElements(            pickedPosition.y,            pickedPosition.z,            pickedPosition.x          );          object._zoomWorldPosition = Cartesian3.clone(            pickedPosition,            object._zoomWorldPosition          );        }      }    } else if (mode === SceneMode.SCENE3D) {      const cameraPositionNormal = Cartesian3.normalize(        camera.position,        scratchCameraPositionNormal      );      if (        object._cameraUnderground ||        object._zoomingUnderground ||        (camera.positionCartographic.height < 3000.0 &&          Math.abs(Cartesian3.dot(camera.direction, cameraPositionNormal)) <            0.6)      ) {        zoomOnVector = true;      } else {        const canvas = scene.canvas;        const centerPixel = scratchCenterPixel;        centerPixel.x = canvas.clientWidth / 2;        centerPixel.y = canvas.clientHeight / 2;        const centerPosition = pickGlobe(          object,          centerPixel,          scratchCenterPosition        );        // If centerPosition is not defined, it means the globe does not cover the center position of screen        if (!defined(centerPosition)) {          zoomOnVector = true;        } else if (camera.positionCartographic.height < 1000000) {          // The math in the else block assumes the camera          // points toward the earth surface, so we check it here.          // Theoretically, we should check for 90 degree, but it doesn't behave well when parallel          // to the earth surface          if (Cartesian3.dot(camera.direction, cameraPositionNormal) >= -0.5) {            zoomOnVector = true;          } else {            const cameraPosition = scratchCameraPosition;            Cartesian3.clone(camera.position, cameraPosition);            const target = object._zoomWorldPosition;            let targetNormal = scratchTargetNormal;            targetNormal = Cartesian3.normalize(target, targetNormal);            if (Cartesian3.dot(targetNormal, cameraPositionNormal) < 0.0) {              return;            }            const center = scratchCenter;            const forward = scratchForwardNormal;            Cartesian3.clone(camera.direction, forward);            Cartesian3.add(              cameraPosition,              Cartesian3.multiplyByScalar(forward, 1000, scratchCartesian),              center            );            const positionToTarget = scratchPositionToTarget;            const positionToTargetNormal = scratchPositionToTargetNormal;            Cartesian3.subtract(target, cameraPosition, positionToTarget);            Cartesian3.normalize(positionToTarget, positionToTargetNormal);            const alphaDot = Cartesian3.dot(              cameraPositionNormal,              positionToTargetNormal            );            if (alphaDot >= 0.0) {              // We zoomed past the target, and this zoom is not valid anymore.              // This line causes the next zoom movement to pick a new starting point.              object._zoomMouseStart.x = -1;              return;            }            const alpha = Math.acos(-alphaDot);            const cameraDistance = Cartesian3.magnitude(cameraPosition);            const targetDistance = Cartesian3.magnitude(target);            const remainingDistance = cameraDistance - distance;            const positionToTargetDistance = Cartesian3.magnitude(              positionToTarget            );            const gamma = Math.asin(              CesiumMath.clamp(                (positionToTargetDistance / targetDistance) * Math.sin(alpha),                -1.0,                1.0              )            );            const delta = Math.asin(              CesiumMath.clamp(                (remainingDistance / targetDistance) * Math.sin(alpha),                -1.0,                1.0              )            );            const beta = gamma - delta + alpha;            const up = scratchCameraUpNormal;            Cartesian3.normalize(cameraPosition, up);            let right = scratchCameraRightNormal;            right = Cartesian3.cross(positionToTargetNormal, up, right);            right = Cartesian3.normalize(right, right);            Cartesian3.normalize(              Cartesian3.cross(up, right, scratchCartesian),              forward            );            // Calculate new position to move to            Cartesian3.multiplyByScalar(              Cartesian3.normalize(center, scratchCartesian),              Cartesian3.magnitude(center) - distance,              center            );            Cartesian3.normalize(cameraPosition, cameraPosition);            Cartesian3.multiplyByScalar(              cameraPosition,              remainingDistance,              cameraPosition            );            // Pan            const pMid = scratchPan;            Cartesian3.multiplyByScalar(              Cartesian3.add(                Cartesian3.multiplyByScalar(                  up,                  Math.cos(beta) - 1,                  scratchCartesianTwo                ),                Cartesian3.multiplyByScalar(                  forward,                  Math.sin(beta),                  scratchCartesianThree                ),                scratchCartesian              ),              remainingDistance,              pMid            );            Cartesian3.add(cameraPosition, pMid, cameraPosition);            Cartesian3.normalize(center, up);            Cartesian3.normalize(              Cartesian3.cross(up, right, scratchCartesian),              forward            );            const cMid = scratchCenterMovement;            Cartesian3.multiplyByScalar(              Cartesian3.add(                Cartesian3.multiplyByScalar(                  up,                  Math.cos(beta) - 1,                  scratchCartesianTwo                ),                Cartesian3.multiplyByScalar(                  forward,                  Math.sin(beta),                  scratchCartesianThree                ),                scratchCartesian              ),              Cartesian3.magnitude(center),              cMid            );            Cartesian3.add(center, cMid, center);            // Update camera            // Set new position            Cartesian3.clone(cameraPosition, camera.position);            // Set new direction            Cartesian3.normalize(              Cartesian3.subtract(center, cameraPosition, scratchCartesian),              camera.direction            );            Cartesian3.clone(camera.direction, camera.direction);            // Set new right & up vectors            Cartesian3.cross(camera.direction, camera.up, camera.right);            Cartesian3.cross(camera.right, camera.direction, camera.up);            camera.setView(scratchZoomViewOptions);            return;          }        } else {          const positionNormal = Cartesian3.normalize(            centerPosition,            scratchPositionNormal          );          const pickedNormal = Cartesian3.normalize(            object._zoomWorldPosition,            scratchPickNormal          );          const dotProduct = Cartesian3.dot(pickedNormal, positionNormal);          if (dotProduct > 0.0 && dotProduct < 1.0) {            const angle = CesiumMath.acosClamped(dotProduct);            const axis = Cartesian3.cross(              pickedNormal,              positionNormal,              scratchZoomAxis            );            const denom =              Math.abs(angle) > CesiumMath.toRadians(20.0)                ? camera.positionCartographic.height * 0.75                : camera.positionCartographic.height - distance;            const scalar = distance / denom;            camera.rotate(axis, angle * scalar);          }        }      }    }    object._rotatingZoom = !zoomOnVector;  }  if ((!sameStartPosition && zoomOnVector) || zoomingOnVector) {    let ray;    const zoomMouseStart = SceneTransforms.wgs84ToWindowCoordinates(      scene,      object._zoomWorldPosition,      scratchZoomOffset    );    if (      mode !== SceneMode.COLUMBUS_VIEW &&      Cartesian2.equals(startPosition, object._zoomMouseStart) &&      defined(zoomMouseStart)    ) {      ray = camera.getPickRay(zoomMouseStart, scratchZoomPickRay);    } else {      ray = camera.getPickRay(startPosition, scratchZoomPickRay);    }    const rayDirection = ray.direction;    if (mode === SceneMode.COLUMBUS_VIEW || mode === SceneMode.SCENE2D) {      Cartesian3.fromElements(        rayDirection.y,        rayDirection.z,        rayDirection.x,        rayDirection      );    }    camera.move(rayDirection, distance);    object._zoomingOnVector = true;  } else {    camera.zoomIn(distance);  }  if (!object._cameraUnderground) {    camera.setView(scratchZoomViewOptions);  }}const translate2DStart = new Ray();const translate2DEnd = new Ray();const scratchTranslateP0 = new Cartesian3();function translate2D(controller, startPosition, movement) {  const scene = controller._scene;  const camera = scene.camera;  let start = camera.getPickRay(movement.startPosition, translate2DStart)    .origin;  let end = camera.getPickRay(movement.endPosition, translate2DEnd).origin;  start = Cartesian3.fromElements(start.y, start.z, start.x, start);  end = Cartesian3.fromElements(end.y, end.z, end.x, end);  const direction = Cartesian3.subtract(start, end, scratchTranslateP0);  const distance = Cartesian3.magnitude(direction);  if (distance > 0.0) {    Cartesian3.normalize(direction, direction);    camera.move(direction, distance);  }}function zoom2D(controller, startPosition, movement) {  if (defined(movement.distance)) {    movement = movement.distance;  }  const scene = controller._scene;  const camera = scene.camera;  handleZoom(    controller,    startPosition,    movement,    controller._zoomFactor,    camera.getMagnitude()  );}const twist2DStart = new Cartesian2();const twist2DEnd = new Cartesian2();function twist2D(controller, startPosition, movement) {  if (defined(movement.angleAndHeight)) {    singleAxisTwist2D(controller, startPosition, movement.angleAndHeight);    return;  }  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  const width = canvas.clientWidth;  const height = canvas.clientHeight;  let start = twist2DStart;  start.x = (2.0 / width) * movement.startPosition.x - 1.0;  start.y = (2.0 / height) * (height - movement.startPosition.y) - 1.0;  start = Cartesian2.normalize(start, start);  let end = twist2DEnd;  end.x = (2.0 / width) * movement.endPosition.x - 1.0;  end.y = (2.0 / height) * (height - movement.endPosition.y) - 1.0;  end = Cartesian2.normalize(end, end);  let startTheta = CesiumMath.acosClamped(start.x);  if (start.y < 0) {    startTheta = CesiumMath.TWO_PI - startTheta;  }  let endTheta = CesiumMath.acosClamped(end.x);  if (end.y < 0) {    endTheta = CesiumMath.TWO_PI - endTheta;  }  const theta = endTheta - startTheta;  camera.twistRight(theta);}function singleAxisTwist2D(controller, startPosition, movement) {  let rotateRate =    controller._rotateFactor * controller._rotateRateRangeAdjustment;  if (rotateRate > controller._maximumRotateRate) {    rotateRate = controller._maximumRotateRate;  }  if (rotateRate < controller._minimumRotateRate) {    rotateRate = controller._minimumRotateRate;  }  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  let phiWindowRatio =    (movement.endPosition.x - movement.startPosition.x) / canvas.clientWidth;  phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);  const deltaPhi = rotateRate * phiWindowRatio * Math.PI * 4.0;  camera.twistRight(deltaPhi);}function update2D(controller) {  const rotatable2D = controller._scene.mapMode2D === MapMode2D.ROTATE;  if (!Matrix4.equals(Matrix4.IDENTITY, controller._scene.camera.transform)) {    reactToInput(      controller,      controller.enableZoom,      controller.zoomEventTypes,      zoom2D,      controller.inertiaZoom,      "_lastInertiaZoomMovement"    );    if (rotatable2D) {      reactToInput(        controller,        controller.enableRotate,        controller.translateEventTypes,        twist2D,        controller.inertiaSpin,        "_lastInertiaSpinMovement"      );    }  } else {    reactToInput(      controller,      controller.enableTranslate,      controller.translateEventTypes,      translate2D,      controller.inertiaTranslate,      "_lastInertiaTranslateMovement"    );    reactToInput(      controller,      controller.enableZoom,      controller.zoomEventTypes,      zoom2D,      controller.inertiaZoom,      "_lastInertiaZoomMovement"    );    if (rotatable2D) {      reactToInput(        controller,        controller.enableRotate,        controller.tiltEventTypes,        twist2D,        controller.inertiaSpin,        "_lastInertiaTiltMovement"      );    }  }}const pickGlobeScratchRay = new Ray();const scratchDepthIntersection = new Cartesian3();const scratchRayIntersection = new Cartesian3();function pickGlobe(controller, mousePosition, result) {  const scene = controller._scene;  const globe = controller._globe;  const camera = scene.camera;  if (!defined(globe)) {    return undefined;  }  const cullBackFaces = !controller._cameraUnderground;  let depthIntersection;  if (scene.pickPositionSupported) {    depthIntersection = scene.pickPositionWorldCoordinates(      mousePosition,      scratchDepthIntersection    );  }  const ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);  const rayIntersection = globe.pickWorldCoordinates(    ray,    scene,    cullBackFaces,    scratchRayIntersection  );  const pickDistance = defined(depthIntersection)    ? Cartesian3.distance(depthIntersection, camera.positionWC)    : Number.POSITIVE_INFINITY;  const rayDistance = defined(rayIntersection)    ? Cartesian3.distance(rayIntersection, camera.positionWC)    : Number.POSITIVE_INFINITY;  if (pickDistance < rayDistance) {    return Cartesian3.clone(depthIntersection, result);  }  return Cartesian3.clone(rayIntersection, result);}const scratchDistanceCartographic = new Cartographic();function getDistanceFromSurface(controller) {  const ellipsoid = controller._ellipsoid;  const scene = controller._scene;  const camera = scene.camera;  const mode = scene.mode;  let height = 0.0;  if (mode === SceneMode.SCENE3D) {    const cartographic = ellipsoid.cartesianToCartographic(      camera.position,      scratchDistanceCartographic    );    if (defined(cartographic)) {      height = cartographic.height;    }  } else {    height = camera.position.z;  }  const globeHeight = defaultValue(controller._scene.globeHeight, 0.0);  const distanceFromSurface = Math.abs(globeHeight - height);  return distanceFromSurface;}const scratchSurfaceNormal = new Cartesian3();function getZoomDistanceUnderground(controller, ray) {  const origin = ray.origin;  const direction = ray.direction;  const distanceFromSurface = getDistanceFromSurface(controller);  // Weight zoom distance based on how strongly the pick ray is pointing inward.  // Geocentric normal is accurate enough for these purposes  const surfaceNormal = Cartesian3.normalize(origin, scratchSurfaceNormal);  let strength = Math.abs(Cartesian3.dot(surfaceNormal, direction));  strength = Math.max(strength, 0.5) * 2.0;  return distanceFromSurface * strength;}function getTiltCenterUnderground(controller, ray, pickedPosition, result) {  let distance = Cartesian3.distance(ray.origin, pickedPosition);  const distanceFromSurface = getDistanceFromSurface(controller);  const maximumDistance = CesiumMath.clamp(    distanceFromSurface * 5.0,    controller._minimumUndergroundPickDistance,    controller._maximumUndergroundPickDistance  );  if (distance > maximumDistance) {    // Simulate look-at behavior by tilting around a small invisible sphere    distance = Math.min(distance, distanceFromSurface / 5.0);    distance = Math.max(distance, 100.0);  }  return Ray.getPoint(ray, distance, result);}function getStrafeStartPositionUnderground(  controller,  ray,  pickedPosition,  result) {  let distance;  if (!defined(pickedPosition)) {    distance = getDistanceFromSurface(controller);  } else {    distance = Cartesian3.distance(ray.origin, pickedPosition);    if (distance > controller._maximumUndergroundPickDistance) {      // If the picked position is too far away set the strafe speed based on the      // camera's height from the globe surface      distance = getDistanceFromSurface(controller);    }  }  return Ray.getPoint(ray, distance, result);}const scratchInertialDelta = new Cartesian2();function continueStrafing(controller, movement) {  // Update the end position continually based on the inertial delta  const originalEndPosition = movement.endPosition;  const inertialDelta = Cartesian2.subtract(    movement.endPosition,    movement.startPosition,    scratchInertialDelta  );  const endPosition = controller._strafeEndMousePosition;  Cartesian2.add(endPosition, inertialDelta, endPosition);  movement.endPosition = endPosition;  strafe(controller, movement, controller._strafeStartPosition);  movement.endPosition = originalEndPosition;}const translateCVStartRay = new Ray();const translateCVEndRay = new Ray();const translateCVStartPos = new Cartesian3();const translateCVEndPos = new Cartesian3();const translateCVDifference = new Cartesian3();const translateCVOrigin = new Cartesian3();const translateCVPlane = new Plane(Cartesian3.UNIT_X, 0.0);const translateCVStartMouse = new Cartesian2();const translateCVEndMouse = new Cartesian2();function translateCV(controller, startPosition, movement) {  if (!Cartesian3.equals(startPosition, controller._translateMousePosition)) {    controller._looking = false;  }  if (!Cartesian3.equals(startPosition, controller._strafeMousePosition)) {    controller._strafing = false;  }  if (controller._looking) {    look3D(controller, startPosition, movement);    return;  }  if (controller._strafing) {    continueStrafing(controller, movement);    return;  }  const scene = controller._scene;  const camera = scene.camera;  const cameraUnderground = controller._cameraUnderground;  const startMouse = Cartesian2.clone(    movement.startPosition,    translateCVStartMouse  );  const endMouse = Cartesian2.clone(movement.endPosition, translateCVEndMouse);  let startRay = camera.getPickRay(startMouse, translateCVStartRay);  const origin = Cartesian3.clone(Cartesian3.ZERO, translateCVOrigin);  const normal = Cartesian3.UNIT_X;  let globePos;  if (camera.position.z < controller._minimumPickingTerrainHeight) {    globePos = pickGlobe(controller, startMouse, translateCVStartPos);    if (defined(globePos)) {      origin.x = globePos.x;    }  }  if (    cameraUnderground ||    (origin.x > camera.position.z && defined(globePos))  ) {    let pickPosition = globePos;    if (cameraUnderground) {      pickPosition = getStrafeStartPositionUnderground(        controller,        startRay,        globePos,        translateCVStartPos      );    }    Cartesian2.clone(startPosition, controller._strafeMousePosition);    Cartesian2.clone(startPosition, controller._strafeEndMousePosition);    Cartesian3.clone(pickPosition, controller._strafeStartPosition);    controller._strafing = true;    strafe(controller, movement, controller._strafeStartPosition);    return;  }  const plane = Plane.fromPointNormal(origin, normal, translateCVPlane);  startRay = camera.getPickRay(startMouse, translateCVStartRay);  const startPlanePos = IntersectionTests.rayPlane(    startRay,    plane,    translateCVStartPos  );  const endRay = camera.getPickRay(endMouse, translateCVEndRay);  const endPlanePos = IntersectionTests.rayPlane(    endRay,    plane,    translateCVEndPos  );  if (!defined(startPlanePos) || !defined(endPlanePos)) {    controller._looking = true;    look3D(controller, startPosition, movement);    Cartesian2.clone(startPosition, controller._translateMousePosition);    return;  }  const diff = Cartesian3.subtract(    startPlanePos,    endPlanePos,    translateCVDifference  );  const temp = diff.x;  diff.x = diff.y;  diff.y = diff.z;  diff.z = temp;  const mag = Cartesian3.magnitude(diff);  if (mag > CesiumMath.EPSILON6) {    Cartesian3.normalize(diff, diff);    camera.move(diff, mag);  }}const rotateCVWindowPos = new Cartesian2();const rotateCVWindowRay = new Ray();const rotateCVCenter = new Cartesian3();const rotateCVVerticalCenter = new Cartesian3();const rotateCVTransform = new Matrix4();const rotateCVVerticalTransform = new Matrix4();const rotateCVOrigin = new Cartesian3();const rotateCVPlane = new Plane(Cartesian3.UNIT_X, 0.0);const rotateCVCartesian3 = new Cartesian3();const rotateCVCart = new Cartographic();const rotateCVOldTransform = new Matrix4();const rotateCVQuaternion = new Quaternion();const rotateCVMatrix = new Matrix3();const tilt3DCartesian3 = new Cartesian3();function rotateCV(controller, startPosition, movement) {  if (defined(movement.angleAndHeight)) {    movement = movement.angleAndHeight;  }  if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {    controller._tiltCVOffMap = false;    controller._looking = false;  }  if (controller._looking) {    look3D(controller, startPosition, movement);    return;  }  const scene = controller._scene;  const camera = scene.camera;  if (    controller._tiltCVOffMap ||    !controller.onMap() ||    Math.abs(camera.position.z) > controller._minimumPickingTerrainHeight  ) {    controller._tiltCVOffMap = true;    rotateCVOnPlane(controller, startPosition, movement);  } else {    rotateCVOnTerrain(controller, startPosition, movement);  }}function rotateCVOnPlane(controller, startPosition, movement) {  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  const windowPosition = rotateCVWindowPos;  windowPosition.x = canvas.clientWidth / 2;  windowPosition.y = canvas.clientHeight / 2;  const ray = camera.getPickRay(windowPosition, rotateCVWindowRay);  const normal = Cartesian3.UNIT_X;  const position = ray.origin;  const direction = ray.direction;  let scalar;  const normalDotDirection = Cartesian3.dot(normal, direction);  if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {    scalar = -Cartesian3.dot(normal, position) / normalDotDirection;  }  if (!defined(scalar) || scalar <= 0.0) {    controller._looking = true;    look3D(controller, startPosition, movement);    Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);    return;  }  const center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);  Cartesian3.add(position, center, center);  const projection = scene.mapProjection;  const ellipsoid = projection.ellipsoid;  Cartesian3.fromElements(center.y, center.z, center.x, center);  const cart = projection.unproject(center, rotateCVCart);  ellipsoid.cartographicToCartesian(cart, center);  const transform = Transforms.eastNorthUpToFixedFrame(    center,    ellipsoid,    rotateCVTransform  );  const oldGlobe = controller._globe;  const oldEllipsoid = controller._ellipsoid;  controller._globe = undefined;  controller._ellipsoid = Ellipsoid.UNIT_SPHERE;  controller._rotateFactor = 1.0;  controller._rotateRateRangeAdjustment = 1.0;  const oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);  camera._setTransform(transform);  rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);  camera._setTransform(oldTransform);  controller._globe = oldGlobe;  controller._ellipsoid = oldEllipsoid;  const radius = oldEllipsoid.maximumRadius;  controller._rotateFactor = 1.0 / radius;  controller._rotateRateRangeAdjustment = radius;}function rotateCVOnTerrain(controller, startPosition, movement) {  const scene = controller._scene;  const camera = scene.camera;  const cameraUnderground = controller._cameraUnderground;  let center;  let ray;  const normal = Cartesian3.UNIT_X;  if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {    center = Cartesian3.clone(controller._tiltCenter, rotateCVCenter);  } else {    if (camera.position.z < controller._minimumPickingTerrainHeight) {      center = pickGlobe(controller, startPosition, rotateCVCenter);    }    if (!defined(center)) {      ray = camera.getPickRay(startPosition, rotateCVWindowRay);      const position = ray.origin;      const direction = ray.direction;      let scalar;      const normalDotDirection = Cartesian3.dot(normal, direction);      if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {        scalar = -Cartesian3.dot(normal, position) / normalDotDirection;      }      if (!defined(scalar) || scalar <= 0.0) {        controller._looking = true;        look3D(controller, startPosition, movement);        Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);        return;      }      center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);      Cartesian3.add(position, center, center);    }    if (cameraUnderground) {      if (!defined(ray)) {        ray = camera.getPickRay(startPosition, rotateCVWindowRay);      }      getTiltCenterUnderground(controller, ray, center, center);    }    Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);    Cartesian3.clone(center, controller._tiltCenter);  }  const canvas = scene.canvas;  const windowPosition = rotateCVWindowPos;  windowPosition.x = canvas.clientWidth / 2;  windowPosition.y = controller._tiltCenterMousePosition.y;  ray = camera.getPickRay(windowPosition, rotateCVWindowRay);  const origin = Cartesian3.clone(Cartesian3.ZERO, rotateCVOrigin);  origin.x = center.x;  const plane = Plane.fromPointNormal(origin, normal, rotateCVPlane);  const verticalCenter = IntersectionTests.rayPlane(    ray,    plane,    rotateCVVerticalCenter  );  const projection = camera._projection;  const ellipsoid = projection.ellipsoid;  Cartesian3.fromElements(center.y, center.z, center.x, center);  let cart = projection.unproject(center, rotateCVCart);  ellipsoid.cartographicToCartesian(cart, center);  const transform = Transforms.eastNorthUpToFixedFrame(    center,    ellipsoid,    rotateCVTransform  );  let verticalTransform;  if (defined(verticalCenter)) {    Cartesian3.fromElements(      verticalCenter.y,      verticalCenter.z,      verticalCenter.x,      verticalCenter    );    cart = projection.unproject(verticalCenter, rotateCVCart);    ellipsoid.cartographicToCartesian(cart, verticalCenter);    verticalTransform = Transforms.eastNorthUpToFixedFrame(      verticalCenter,      ellipsoid,      rotateCVVerticalTransform    );  } else {    verticalTransform = transform;  }  const oldGlobe = controller._globe;  const oldEllipsoid = controller._ellipsoid;  controller._globe = undefined;  controller._ellipsoid = Ellipsoid.UNIT_SPHERE;  controller._rotateFactor = 1.0;  controller._rotateRateRangeAdjustment = 1.0;  let constrainedAxis = Cartesian3.UNIT_Z;  const oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);  camera._setTransform(transform);  const tangent = Cartesian3.cross(    Cartesian3.UNIT_Z,    Cartesian3.normalize(camera.position, rotateCVCartesian3),    rotateCVCartesian3  );  const dot = Cartesian3.dot(camera.right, tangent);  rotate3D(controller, startPosition, movement, constrainedAxis, false, true);  camera._setTransform(verticalTransform);  if (dot < 0.0) {    const movementDelta = movement.startPosition.y - movement.endPosition.y;    if (      (cameraUnderground && movementDelta < 0.0) ||      (!cameraUnderground && movementDelta > 0.0)    ) {      // Prevent camera from flipping past the up axis      constrainedAxis = undefined;    }    const oldConstrainedAxis = camera.constrainedAxis;    camera.constrainedAxis = undefined;    rotate3D(controller, startPosition, movement, constrainedAxis, true, false);    camera.constrainedAxis = oldConstrainedAxis;  } else {    rotate3D(controller, startPosition, movement, constrainedAxis, true, false);  }  if (defined(camera.constrainedAxis)) {    const right = Cartesian3.cross(      camera.direction,      camera.constrainedAxis,      tilt3DCartesian3    );    if (      !Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)    ) {      if (Cartesian3.dot(right, camera.right) < 0.0) {        Cartesian3.negate(right, right);      }      Cartesian3.cross(right, camera.direction, camera.up);      Cartesian3.cross(camera.direction, camera.up, camera.right);      Cartesian3.normalize(camera.up, camera.up);      Cartesian3.normalize(camera.right, camera.right);    }  }  camera._setTransform(oldTransform);  controller._globe = oldGlobe;  controller._ellipsoid = oldEllipsoid;  const radius = oldEllipsoid.maximumRadius;  controller._rotateFactor = 1.0 / radius;  controller._rotateRateRangeAdjustment = radius;  const originalPosition = Cartesian3.clone(    camera.positionWC,    rotateCVCartesian3  );  if (controller.enableCollisionDetection) {    adjustHeightForTerrain(controller);  }  if (!Cartesian3.equals(camera.positionWC, originalPosition)) {    camera._setTransform(verticalTransform);    camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);    const magSqrd = Cartesian3.magnitudeSquared(originalPosition);    if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {      Cartesian3.normalize(camera.position, camera.position);      Cartesian3.multiplyByScalar(        camera.position,        Math.sqrt(magSqrd),        camera.position      );    }    const angle = Cartesian3.angleBetween(originalPosition, camera.position);    const axis = Cartesian3.cross(      originalPosition,      camera.position,      originalPosition    );    Cartesian3.normalize(axis, axis);    const quaternion = Quaternion.fromAxisAngle(      axis,      angle,      rotateCVQuaternion    );    const rotation = Matrix3.fromQuaternion(quaternion, rotateCVMatrix);    Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);    Matrix3.multiplyByVector(rotation, camera.up, camera.up);    Cartesian3.cross(camera.direction, camera.up, camera.right);    Cartesian3.cross(camera.right, camera.direction, camera.up);    camera._setTransform(oldTransform);  }}const zoomCVWindowPos = new Cartesian2();const zoomCVWindowRay = new Ray();const zoomCVIntersection = new Cartesian3();function zoomCV(controller, startPosition, movement) {  if (defined(movement.distance)) {    movement = movement.distance;  }  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  const cameraUnderground = controller._cameraUnderground;  let windowPosition;  if (cameraUnderground) {    windowPosition = startPosition;  } else {    windowPosition = zoomCVWindowPos;    windowPosition.x = canvas.clientWidth / 2;    windowPosition.y = canvas.clientHeight / 2;  }  const ray = camera.getPickRay(windowPosition, zoomCVWindowRay);  const position = ray.origin;  const direction = ray.direction;  const height = camera.position.z;  let intersection;  if (height < controller._minimumPickingTerrainHeight) {    intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);  }  let distance;  if (defined(intersection)) {    distance = Cartesian3.distance(position, intersection);  }  if (cameraUnderground) {    const distanceUnderground = getZoomDistanceUnderground(      controller,      ray,      height    );    if (defined(distance)) {      distance = Math.min(distance, distanceUnderground);    } else {      distance = distanceUnderground;    }  }  if (!defined(distance)) {    const normal = Cartesian3.UNIT_X;    distance =      -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);  }  handleZoom(    controller,    startPosition,    movement,    controller._zoomFactor,    distance  );}function updateCV(controller) {  const scene = controller._scene;  const camera = scene.camera;  if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) {    reactToInput(      controller,      controller.enableRotate,      controller.rotateEventTypes,      rotate3D,      controller.inertiaSpin,      "_lastInertiaSpinMovement"    );    reactToInput(      controller,      controller.enableZoom,      controller.zoomEventTypes,      zoom3D,      controller.inertiaZoom,      "_lastInertiaZoomMovement"    );  } else {    const tweens = controller._tweens;    if (controller._aggregator.anyButtonDown) {      tweens.removeAll();    }    reactToInput(      controller,      controller.enableTilt,      controller.tiltEventTypes,      rotateCV,      controller.inertiaSpin,      "_lastInertiaTiltMovement"    );    reactToInput(      controller,      controller.enableTranslate,      controller.translateEventTypes,      translateCV,      controller.inertiaTranslate,      "_lastInertiaTranslateMovement"    );    reactToInput(      controller,      controller.enableZoom,      controller.zoomEventTypes,      zoomCV,      controller.inertiaZoom,      "_lastInertiaZoomMovement"    );    reactToInput(      controller,      controller.enableLook,      controller.lookEventTypes,      look3D    );    if (      !controller._aggregator.anyButtonDown &&      !tweens.contains(controller._tween)    ) {      const tween = camera.createCorrectPositionTween(        controller.bounceAnimationTime      );      if (defined(tween)) {        controller._tween = tweens.add(tween);      }    }    tweens.update();  }}const scratchStrafeRay = new Ray();const scratchStrafePlane = new Plane(Cartesian3.UNIT_X, 0.0);const scratchStrafeIntersection = new Cartesian3();const scratchStrafeDirection = new Cartesian3();const scratchMousePos = new Cartesian3();function strafe(controller, movement, strafeStartPosition) {  const scene = controller._scene;  const camera = scene.camera;  const ray = camera.getPickRay(movement.endPosition, scratchStrafeRay);  let direction = Cartesian3.clone(camera.direction, scratchStrafeDirection);  if (scene.mode === SceneMode.COLUMBUS_VIEW) {    Cartesian3.fromElements(direction.z, direction.x, direction.y, direction);  }  const plane = Plane.fromPointNormal(    strafeStartPosition,    direction,    scratchStrafePlane  );  const intersection = IntersectionTests.rayPlane(    ray,    plane,    scratchStrafeIntersection  );  if (!defined(intersection)) {    return;  }  direction = Cartesian3.subtract(strafeStartPosition, intersection, direction);  if (scene.mode === SceneMode.COLUMBUS_VIEW) {    Cartesian3.fromElements(direction.y, direction.z, direction.x, direction);  }  Cartesian3.add(camera.position, direction, camera.position);}const spin3DPick = new Cartesian3();const scratchCartographic = new Cartographic();const scratchRadii = new Cartesian3();const scratchEllipsoid = new Ellipsoid();const scratchLookUp = new Cartesian3();const scratchNormal = new Cartesian3();function spin3D(controller, startPosition, movement) {  const scene = controller._scene;  const camera = scene.camera;  const cameraUnderground = controller._cameraUnderground;  let ellipsoid = controller._ellipsoid;  if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {    rotate3D(controller, startPosition, movement);    return;  }  let magnitude;  let radii;  const up = ellipsoid.geodeticSurfaceNormal(camera.position, scratchLookUp);  if (Cartesian2.equals(startPosition, controller._rotateMousePosition)) {    if (controller._looking) {      look3D(controller, startPosition, movement, up);    } else if (controller._rotating) {      rotate3D(controller, startPosition, movement);    } else if (controller._strafing) {      continueStrafing(controller, movement);    } else {      if (        Cartesian3.magnitude(camera.position) <        Cartesian3.magnitude(controller._rotateStartPosition)      ) {        // Pan action is no longer valid if camera moves below the pan ellipsoid        return;      }      magnitude = Cartesian3.magnitude(controller._rotateStartPosition);      radii = scratchRadii;      radii.x = radii.y = radii.z = magnitude;      ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);      pan3D(controller, startPosition, movement, ellipsoid);    }    return;  }  controller._looking = false;  controller._rotating = false;  controller._strafing = false;  const height = ellipsoid.cartesianToCartographic(    camera.positionWC,    scratchCartographic  ).height;  const globe = controller._globe;  if (defined(globe) && height < controller._minimumPickingTerrainHeight) {    const mousePos = pickGlobe(      controller,      movement.startPosition,      scratchMousePos    );    if (defined(mousePos)) {      let strafing = false;      const ray = camera.getPickRay(        movement.startPosition,        pickGlobeScratchRay      );      if (cameraUnderground) {        strafing = true;        getStrafeStartPositionUnderground(controller, ray, mousePos, mousePos);      } else {        const normal = ellipsoid.geodeticSurfaceNormal(mousePos, scratchNormal);        const tangentPick =          Math.abs(Cartesian3.dot(ray.direction, normal)) < 0.05;        if (tangentPick) {          strafing = true;        } else {          strafing =            Cartesian3.magnitude(camera.position) <            Cartesian3.magnitude(mousePos);        }      }      if (strafing) {        Cartesian2.clone(startPosition, controller._strafeEndMousePosition);        Cartesian3.clone(mousePos, controller._strafeStartPosition);        controller._strafing = true;        strafe(controller, movement, controller._strafeStartPosition);      } else {        magnitude = Cartesian3.magnitude(mousePos);        radii = scratchRadii;        radii.x = radii.y = radii.z = magnitude;        ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);        pan3D(controller, startPosition, movement, ellipsoid);        Cartesian3.clone(mousePos, controller._rotateStartPosition);      }    } else {      controller._looking = true;      look3D(controller, startPosition, movement, up);    }  } else if (    defined(      camera.pickEllipsoid(        movement.startPosition,        controller._ellipsoid,        spin3DPick      )    )  ) {    pan3D(controller, startPosition, movement, controller._ellipsoid);    Cartesian3.clone(spin3DPick, controller._rotateStartPosition);  } else if (height > controller._minimumTrackBallHeight) {    controller._rotating = true;    rotate3D(controller, startPosition, movement);  } else {    controller._looking = true;    look3D(controller, startPosition, movement, up);  }  Cartesian2.clone(startPosition, controller._rotateMousePosition);}function rotate3D(  controller,  startPosition,  movement,  constrainedAxis,  rotateOnlyVertical,  rotateOnlyHorizontal) {  rotateOnlyVertical = defaultValue(rotateOnlyVertical, false);  rotateOnlyHorizontal = defaultValue(rotateOnlyHorizontal, false);  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  const oldAxis = camera.constrainedAxis;  if (defined(constrainedAxis)) {    camera.constrainedAxis = constrainedAxis;  }  const rho = Cartesian3.magnitude(camera.position);  let rotateRate =    controller._rotateFactor * (rho - controller._rotateRateRangeAdjustment);  if (rotateRate > controller._maximumRotateRate) {    rotateRate = controller._maximumRotateRate;  }  if (rotateRate < controller._minimumRotateRate) {    rotateRate = controller._minimumRotateRate;  }  let phiWindowRatio =    (movement.startPosition.x - movement.endPosition.x) / canvas.clientWidth;  let thetaWindowRatio =    (movement.startPosition.y - movement.endPosition.y) / canvas.clientHeight;  phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);  thetaWindowRatio = Math.min(    thetaWindowRatio,    controller.maximumMovementRatio  );  const deltaPhi = rotateRate * phiWindowRatio * Math.PI * 2.0;  const deltaTheta = rotateRate * thetaWindowRatio * Math.PI;  if (!rotateOnlyVertical) {    camera.rotateRight(deltaPhi);  }  if (!rotateOnlyHorizontal) {    camera.rotateUp(deltaTheta);  }  camera.constrainedAxis = oldAxis;}const pan3DP0 = Cartesian4.clone(Cartesian4.UNIT_W);const pan3DP1 = Cartesian4.clone(Cartesian4.UNIT_W);const pan3DTemp0 = new Cartesian3();const pan3DTemp1 = new Cartesian3();const pan3DTemp2 = new Cartesian3();const pan3DTemp3 = new Cartesian3();const pan3DStartMousePosition = new Cartesian2();const pan3DEndMousePosition = new Cartesian2();function pan3D(controller, startPosition, movement, ellipsoid) {  const scene = controller._scene;  const camera = scene.camera;  const startMousePosition = Cartesian2.clone(    movement.startPosition,    pan3DStartMousePosition  );  const endMousePosition = Cartesian2.clone(    movement.endPosition,    pan3DEndMousePosition  );  let p0 = camera.pickEllipsoid(startMousePosition, ellipsoid, pan3DP0);  let p1 = camera.pickEllipsoid(endMousePosition, ellipsoid, pan3DP1);  if (!defined(p0) || !defined(p1)) {    controller._rotating = true;    rotate3D(controller, startPosition, movement);    return;  }  p0 = camera.worldToCameraCoordinates(p0, p0);  p1 = camera.worldToCameraCoordinates(p1, p1);  if (!defined(camera.constrainedAxis)) {    Cartesian3.normalize(p0, p0);    Cartesian3.normalize(p1, p1);    const dot = Cartesian3.dot(p0, p1);    const axis = Cartesian3.cross(p0, p1, pan3DTemp0);    if (      dot < 1.0 &&      !Cartesian3.equalsEpsilon(axis, Cartesian3.ZERO, CesiumMath.EPSILON14)    ) {      // dot is in [0, 1]      const angle = Math.acos(dot);      camera.rotate(axis, angle);    }  } else {    const basis0 = camera.constrainedAxis;    const basis1 = Cartesian3.mostOrthogonalAxis(basis0, pan3DTemp0);    Cartesian3.cross(basis1, basis0, basis1);    Cartesian3.normalize(basis1, basis1);    const basis2 = Cartesian3.cross(basis0, basis1, pan3DTemp1);    const startRho = Cartesian3.magnitude(p0);    const startDot = Cartesian3.dot(basis0, p0);    const startTheta = Math.acos(startDot / startRho);    const startRej = Cartesian3.multiplyByScalar(basis0, startDot, pan3DTemp2);    Cartesian3.subtract(p0, startRej, startRej);    Cartesian3.normalize(startRej, startRej);    const endRho = Cartesian3.magnitude(p1);    const endDot = Cartesian3.dot(basis0, p1);    const endTheta = Math.acos(endDot / endRho);    const endRej = Cartesian3.multiplyByScalar(basis0, endDot, pan3DTemp3);    Cartesian3.subtract(p1, endRej, endRej);    Cartesian3.normalize(endRej, endRej);    let startPhi = Math.acos(Cartesian3.dot(startRej, basis1));    if (Cartesian3.dot(startRej, basis2) < 0) {      startPhi = CesiumMath.TWO_PI - startPhi;    }    let endPhi = Math.acos(Cartesian3.dot(endRej, basis1));    if (Cartesian3.dot(endRej, basis2) < 0) {      endPhi = CesiumMath.TWO_PI - endPhi;    }    const deltaPhi = startPhi - endPhi;    let east;    if (      Cartesian3.equalsEpsilon(basis0, camera.position, CesiumMath.EPSILON2)    ) {      east = camera.right;    } else {      east = Cartesian3.cross(basis0, camera.position, pan3DTemp0);    }    const planeNormal = Cartesian3.cross(basis0, east, pan3DTemp0);    const side0 = Cartesian3.dot(      planeNormal,      Cartesian3.subtract(p0, basis0, pan3DTemp1)    );    const side1 = Cartesian3.dot(      planeNormal,      Cartesian3.subtract(p1, basis0, pan3DTemp1)    );    let deltaTheta;    if (side0 > 0 && side1 > 0) {      deltaTheta = endTheta - startTheta;    } else if (side0 > 0 && side1 <= 0) {      if (Cartesian3.dot(camera.position, basis0) > 0) {        deltaTheta = -startTheta - endTheta;      } else {        deltaTheta = startTheta + endTheta;      }    } else {      deltaTheta = startTheta - endTheta;    }    camera.rotateRight(deltaPhi);    camera.rotateUp(deltaTheta);  }}const zoom3DUnitPosition = new Cartesian3();const zoom3DCartographic = new Cartographic();function zoom3D(controller, startPosition, movement) {  if (defined(movement.distance)) {    movement = movement.distance;  }  const ellipsoid = controller._ellipsoid;  const scene = controller._scene;  const camera = scene.camera;  const canvas = scene.canvas;  const cameraUnderground = controller._cameraUnderground;  let windowPosition;  if (cameraUnderground) {    windowPosition = startPosition;  } else {    windowPosition = zoomCVWindowPos;    windowPosition.x = canvas.clientWidth / 2;    windowPosition.y = canvas.clientHeight / 2;  }  const ray = camera.getPickRay(windowPosition, zoomCVWindowRay);  let intersection;  const height = ellipsoid.cartesianToCartographic(    camera.position,    zoom3DCartographic  ).height;  if (height < controller._minimumPickingTerrainHeight) {    intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);  }  let distance;  if (defined(intersection)) {    distance = Cartesian3.distance(ray.origin, intersection);  }  if (cameraUnderground) {    const distanceUnderground = getZoomDistanceUnderground(      controller,      ray,      height    );    if (defined(distance)) {      distance = Math.min(distance, distanceUnderground);    } else {      distance = distanceUnderground;    }  }  if (!defined(distance)) {    distance = height;  }  const unitPosition = Cartesian3.normalize(    camera.position,    zoom3DUnitPosition  );  handleZoom(    controller,    startPosition,    movement,    controller._zoomFactor,    distance,    Cartesian3.dot(unitPosition, camera.direction)  );}const tilt3DWindowPos = new Cartesian2();const tilt3DRay = new Ray();const tilt3DCenter = new Cartesian3();const tilt3DVerticalCenter = new Cartesian3();const tilt3DTransform = new Matrix4();const tilt3DVerticalTransform = new Matrix4();const tilt3DOldTransform = new Matrix4();const tilt3DQuaternion = new Quaternion();const tilt3DMatrix = new Matrix3();const tilt3DCart = new Cartographic();const tilt3DLookUp = new Cartesian3();function tilt3D(controller, startPosition, movement) {  const scene = controller._scene;  const camera = scene.camera;  if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {    return;  }  if (defined(movement.angleAndHeight)) {    movement = movement.angleAndHeight;  }  if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {    controller._tiltOnEllipsoid = false;    controller._looking = false;  }  if (controller._looking) {    const up = controller._ellipsoid.geodeticSurfaceNormal(      camera.position,      tilt3DLookUp    );    look3D(controller, startPosition, movement, up);    return;  }  const ellipsoid = controller._ellipsoid;  const cartographic = ellipsoid.cartesianToCartographic(    camera.position,    tilt3DCart  );  if (    controller._tiltOnEllipsoid ||    cartographic.height > controller._minimumCollisionTerrainHeight  ) {    controller._tiltOnEllipsoid = true;    tilt3DOnEllipsoid(controller, startPosition, movement);  } else {    tilt3DOnTerrain(controller, startPosition, movement);  }}const tilt3DOnEllipsoidCartographic = new Cartographic();function tilt3DOnEllipsoid(controller, startPosition, movement) {  const ellipsoid = controller._ellipsoid;  const scene = controller._scene;  const camera = scene.camera;  const minHeight = controller.minimumZoomDistance * 0.25;  const height = ellipsoid.cartesianToCartographic(    camera.positionWC,    tilt3DOnEllipsoidCartographic  ).height;  if (    height - minHeight - 1.0 < CesiumMath.EPSILON3 &&    movement.endPosition.y - movement.startPosition.y < 0  ) {    return;  }  const canvas = scene.canvas;  const windowPosition = tilt3DWindowPos;  windowPosition.x = canvas.clientWidth / 2;  windowPosition.y = canvas.clientHeight / 2;  const ray = camera.getPickRay(windowPosition, tilt3DRay);  let center;  const intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);  if (defined(intersection)) {    center = Ray.getPoint(ray, intersection.start, tilt3DCenter);  } else if (height > controller._minimumTrackBallHeight) {    const grazingAltitudeLocation = IntersectionTests.grazingAltitudeLocation(      ray,      ellipsoid    );    if (!defined(grazingAltitudeLocation)) {      return;    }    const grazingAltitudeCart = ellipsoid.cartesianToCartographic(      grazingAltitudeLocation,      tilt3DCart    );    grazingAltitudeCart.height = 0.0;    center = ellipsoid.cartographicToCartesian(      grazingAltitudeCart,      tilt3DCenter    );  } else {    controller._looking = true;    const up = controller._ellipsoid.geodeticSurfaceNormal(      camera.position,      tilt3DLookUp    );    look3D(controller, startPosition, movement, up);    Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);    return;  }  const transform = Transforms.eastNorthUpToFixedFrame(    center,    ellipsoid,    tilt3DTransform  );  const oldGlobe = controller._globe;  const oldEllipsoid = controller._ellipsoid;  controller._globe = undefined;  controller._ellipsoid = Ellipsoid.UNIT_SPHERE;  controller._rotateFactor = 1.0;  controller._rotateRateRangeAdjustment = 1.0;  const oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);  camera._setTransform(transform);  rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);  camera._setTransform(oldTransform);  controller._globe = oldGlobe;  controller._ellipsoid = oldEllipsoid;  const radius = oldEllipsoid.maximumRadius;  controller._rotateFactor = 1.0 / radius;  controller._rotateRateRangeAdjustment = radius;}function tilt3DOnTerrain(controller, startPosition, movement) {  const ellipsoid = controller._ellipsoid;  const scene = controller._scene;  const camera = scene.camera;  const cameraUnderground = controller._cameraUnderground;  let center;  let ray;  let intersection;  if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {    center = Cartesian3.clone(controller._tiltCenter, tilt3DCenter);  } else {    center = pickGlobe(controller, startPosition, tilt3DCenter);    if (!defined(center)) {      ray = camera.getPickRay(startPosition, tilt3DRay);      intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);      if (!defined(intersection)) {        const cartographic = ellipsoid.cartesianToCartographic(          camera.position,          tilt3DCart        );        if (cartographic.height <= controller._minimumTrackBallHeight) {          controller._looking = true;          const up = controller._ellipsoid.geodeticSurfaceNormal(            camera.position,            tilt3DLookUp          );          look3D(controller, startPosition, movement, up);          Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);        }        return;      }      center = Ray.getPoint(ray, intersection.start, tilt3DCenter);    }    if (cameraUnderground) {      if (!defined(ray)) {        ray = camera.getPickRay(startPosition, tilt3DRay);      }      getTiltCenterUnderground(controller, ray, center, center);    }    Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);    Cartesian3.clone(center, controller._tiltCenter);  }  const canvas = scene.canvas;  const windowPosition = tilt3DWindowPos;  windowPosition.x = canvas.clientWidth / 2;  windowPosition.y = controller._tiltCenterMousePosition.y;  ray = camera.getPickRay(windowPosition, tilt3DRay);  const mag = Cartesian3.magnitude(center);  const radii = Cartesian3.fromElements(mag, mag, mag, scratchRadii);  const newEllipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);  intersection = IntersectionTests.rayEllipsoid(ray, newEllipsoid);  if (!defined(intersection)) {    return;  }  const t =    Cartesian3.magnitude(ray.origin) > mag      ? intersection.start      : intersection.stop;  const verticalCenter = Ray.getPoint(ray, t, tilt3DVerticalCenter);  const transform = Transforms.eastNorthUpToFixedFrame(    center,    ellipsoid,    tilt3DTransform  );  const verticalTransform = Transforms.eastNorthUpToFixedFrame(    verticalCenter,    newEllipsoid,    tilt3DVerticalTransform  );  const oldGlobe = controller._globe;  const oldEllipsoid = controller._ellipsoid;  controller._globe = undefined;  controller._ellipsoid = Ellipsoid.UNIT_SPHERE;  controller._rotateFactor = 1.0;  controller._rotateRateRangeAdjustment = 1.0;  let constrainedAxis = Cartesian3.UNIT_Z;  const oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);  camera._setTransform(verticalTransform);  const tangent = Cartesian3.cross(    verticalCenter,    camera.positionWC,    tilt3DCartesian3  );  const dot = Cartesian3.dot(camera.rightWC, tangent);  if (dot < 0.0) {    const movementDelta = movement.startPosition.y - movement.endPosition.y;    if (      (cameraUnderground && movementDelta < 0.0) ||      (!cameraUnderground && movementDelta > 0.0)    ) {      // Prevent camera from flipping past the up axis      constrainedAxis = undefined;    }    const oldConstrainedAxis = camera.constrainedAxis;    camera.constrainedAxis = undefined;    rotate3D(controller, startPosition, movement, constrainedAxis, true, false);    camera.constrainedAxis = oldConstrainedAxis;  } else {    rotate3D(controller, startPosition, movement, constrainedAxis, true, false);  }  camera._setTransform(transform);  rotate3D(controller, startPosition, movement, constrainedAxis, false, true);  if (defined(camera.constrainedAxis)) {    const right = Cartesian3.cross(      camera.direction,      camera.constrainedAxis,      tilt3DCartesian3    );    if (      !Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)    ) {      if (Cartesian3.dot(right, camera.right) < 0.0) {        Cartesian3.negate(right, right);      }      Cartesian3.cross(right, camera.direction, camera.up);      Cartesian3.cross(camera.direction, camera.up, camera.right);      Cartesian3.normalize(camera.up, camera.up);      Cartesian3.normalize(camera.right, camera.right);    }  }  camera._setTransform(oldTransform);  controller._globe = oldGlobe;  controller._ellipsoid = oldEllipsoid;  const radius = oldEllipsoid.maximumRadius;  controller._rotateFactor = 1.0 / radius;  controller._rotateRateRangeAdjustment = radius;  const originalPosition = Cartesian3.clone(    camera.positionWC,    tilt3DCartesian3  );  if (controller.enableCollisionDetection) {    adjustHeightForTerrain(controller);  }  if (!Cartesian3.equals(camera.positionWC, originalPosition)) {    camera._setTransform(verticalTransform);    camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);    const magSqrd = Cartesian3.magnitudeSquared(originalPosition);    if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {      Cartesian3.normalize(camera.position, camera.position);      Cartesian3.multiplyByScalar(        camera.position,        Math.sqrt(magSqrd),        camera.position      );    }    const angle = Cartesian3.angleBetween(originalPosition, camera.position);    const axis = Cartesian3.cross(      originalPosition,      camera.position,      originalPosition    );    Cartesian3.normalize(axis, axis);    const quaternion = Quaternion.fromAxisAngle(axis, angle, tilt3DQuaternion);    const rotation = Matrix3.fromQuaternion(quaternion, tilt3DMatrix);    Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);    Matrix3.multiplyByVector(rotation, camera.up, camera.up);    Cartesian3.cross(camera.direction, camera.up, camera.right);    Cartesian3.cross(camera.right, camera.direction, camera.up);    camera._setTransform(oldTransform);  }}const look3DStartPos = new Cartesian2();const look3DEndPos = new Cartesian2();const look3DStartRay = new Ray();const look3DEndRay = new Ray();const look3DNegativeRot = new Cartesian3();const look3DTan = new Cartesian3();function look3D(controller, startPosition, movement, rotationAxis) {  const scene = controller._scene;  const camera = scene.camera;  const startPos = look3DStartPos;  startPos.x = movement.startPosition.x;  startPos.y = 0.0;  const endPos = look3DEndPos;  endPos.x = movement.endPosition.x;  endPos.y = 0.0;  let startRay = camera.getPickRay(startPos, look3DStartRay);  let endRay = camera.getPickRay(endPos, look3DEndRay);  let angle = 0.0;  let start;  let end;  if (camera.frustum instanceof OrthographicFrustum) {    start = startRay.origin;    end = endRay.origin;    Cartesian3.add(camera.direction, start, start);    Cartesian3.add(camera.direction, end, end);    Cartesian3.subtract(start, camera.position, start);    Cartesian3.subtract(end, camera.position, end);    Cartesian3.normalize(start, start);    Cartesian3.normalize(end, end);  } else {    start = startRay.direction;    end = endRay.direction;  }  let dot = Cartesian3.dot(start, end);  if (dot < 1.0) {    // dot is in [0, 1]    angle = Math.acos(dot);  }  angle = movement.startPosition.x > movement.endPosition.x ? -angle : angle;  const horizontalRotationAxis = controller._horizontalRotationAxis;  if (defined(rotationAxis)) {    camera.look(rotationAxis, -angle);  } else if (defined(horizontalRotationAxis)) {    camera.look(horizontalRotationAxis, -angle);  } else {    camera.lookLeft(angle);  }  startPos.x = 0.0;  startPos.y = movement.startPosition.y;  endPos.x = 0.0;  endPos.y = movement.endPosition.y;  startRay = camera.getPickRay(startPos, look3DStartRay);  endRay = camera.getPickRay(endPos, look3DEndRay);  angle = 0.0;  if (camera.frustum instanceof OrthographicFrustum) {    start = startRay.origin;    end = endRay.origin;    Cartesian3.add(camera.direction, start, start);    Cartesian3.add(camera.direction, end, end);    Cartesian3.subtract(start, camera.position, start);    Cartesian3.subtract(end, camera.position, end);    Cartesian3.normalize(start, start);    Cartesian3.normalize(end, end);  } else {    start = startRay.direction;    end = endRay.direction;  }  dot = Cartesian3.dot(start, end);  if (dot < 1.0) {    // dot is in [0, 1]    angle = Math.acos(dot);  }  angle = movement.startPosition.y > movement.endPosition.y ? -angle : angle;  rotationAxis = defaultValue(rotationAxis, horizontalRotationAxis);  if (defined(rotationAxis)) {    const direction = camera.direction;    const negativeRotationAxis = Cartesian3.negate(      rotationAxis,      look3DNegativeRot    );    const northParallel = Cartesian3.equalsEpsilon(      direction,      rotationAxis,      CesiumMath.EPSILON2    );    const southParallel = Cartesian3.equalsEpsilon(      direction,      negativeRotationAxis,      CesiumMath.EPSILON2    );    if (!northParallel && !southParallel) {      dot = Cartesian3.dot(direction, rotationAxis);      let angleToAxis = CesiumMath.acosClamped(dot);      if (angle > 0 && angle > angleToAxis) {        angle = angleToAxis - CesiumMath.EPSILON4;      }      dot = Cartesian3.dot(direction, negativeRotationAxis);      angleToAxis = CesiumMath.acosClamped(dot);      if (angle < 0 && -angle > angleToAxis) {        angle = -angleToAxis + CesiumMath.EPSILON4;      }      const tangent = Cartesian3.cross(rotationAxis, direction, look3DTan);      camera.look(tangent, angle);    } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {      camera.look(camera.right, -angle);    }  } else {    camera.lookUp(angle);  }}function update3D(controller) {  reactToInput(    controller,    controller.enableRotate,    controller.rotateEventTypes,    spin3D,    controller.inertiaSpin,    "_lastInertiaSpinMovement"  );  reactToInput(    controller,    controller.enableZoom,    controller.zoomEventTypes,    zoom3D,    controller.inertiaZoom,    "_lastInertiaZoomMovement"  );  reactToInput(    controller,    controller.enableTilt,    controller.tiltEventTypes,    tilt3D,    controller.inertiaSpin,    "_lastInertiaTiltMovement"  );  reactToInput(    controller,    controller.enableLook,    controller.lookEventTypes,    look3D  );}const scratchAdjustHeightTransform = new Matrix4();const scratchAdjustHeightCartographic = new Cartographic();function adjustHeightForTerrain(controller) {  controller._adjustedHeightForTerrain = true;  const scene = controller._scene;  const mode = scene.mode;  const globe = scene.globe;  if (    !defined(globe) ||    mode === SceneMode.SCENE2D ||    mode === SceneMode.MORPHING  ) {    return;  }  const camera = scene.camera;  const ellipsoid = globe.ellipsoid;  const projection = scene.mapProjection;  let transform;  let mag;  if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {    transform = Matrix4.clone(camera.transform, scratchAdjustHeightTransform);    mag = Cartesian3.magnitude(camera.position);    camera._setTransform(Matrix4.IDENTITY);  }  const cartographic = scratchAdjustHeightCartographic;  if (mode === SceneMode.SCENE3D) {    ellipsoid.cartesianToCartographic(camera.position, cartographic);  } else {    projection.unproject(camera.position, cartographic);  }  let heightUpdated = false;  if (cartographic.height < controller._minimumCollisionTerrainHeight) {    const globeHeight = controller._scene.globeHeight;    if (defined(globeHeight)) {      const height = globeHeight + controller.minimumZoomDistance;      if (cartographic.height < height) {        cartographic.height = height;        if (mode === SceneMode.SCENE3D) {          ellipsoid.cartographicToCartesian(cartographic, camera.position);        } else {          projection.project(cartographic, camera.position);        }        heightUpdated = true;      }    }  }  if (defined(transform)) {    camera._setTransform(transform);    if (heightUpdated) {      Cartesian3.normalize(camera.position, camera.position);      Cartesian3.negate(camera.position, camera.direction);      Cartesian3.multiplyByScalar(        camera.position,        Math.max(mag, controller.minimumZoomDistance),        camera.position      );      Cartesian3.normalize(camera.direction, camera.direction);      Cartesian3.cross(camera.direction, camera.up, camera.right);      Cartesian3.cross(camera.right, camera.direction, camera.up);    }  }}/** * @private */ScreenSpaceCameraController.prototype.onMap = function () {  const scene = this._scene;  const mode = scene.mode;  const camera = scene.camera;  if (mode === SceneMode.COLUMBUS_VIEW) {    return (      Math.abs(camera.position.x) - this._maxCoord.x < 0 &&      Math.abs(camera.position.y) - this._maxCoord.y < 0    );  }  return true;};const scratchPreviousPosition = new Cartesian3();const scratchPreviousDirection = new Cartesian3();/** * @private */ScreenSpaceCameraController.prototype.update = function () {  const scene = this._scene;  const camera = scene.camera;  const globe = scene.globe;  const mode = scene.mode;  if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {    this._globe = undefined;    this._ellipsoid = Ellipsoid.UNIT_SPHERE;  } else {    this._globe = globe;    this._ellipsoid = defined(this._globe)      ? this._globe.ellipsoid      : scene.mapProjection.ellipsoid;  }  const exaggeration = defined(this._globe)    ? this._globe.terrainExaggeration    : 1.0;  const exaggerationRelativeHeight = defined(this._globe)    ? this._globe.terrainExaggerationRelativeHeight    : 0.0;  this._minimumCollisionTerrainHeight = TerrainExaggeration.getHeight(    this.minimumCollisionTerrainHeight,    exaggeration,    exaggerationRelativeHeight  );  this._minimumPickingTerrainHeight = TerrainExaggeration.getHeight(    this.minimumPickingTerrainHeight,    exaggeration,    exaggerationRelativeHeight  );  this._minimumTrackBallHeight = TerrainExaggeration.getHeight(    this.minimumTrackBallHeight,    exaggeration,    exaggerationRelativeHeight  );  this._cameraUnderground = scene.cameraUnderground && defined(this._globe);  const radius = this._ellipsoid.maximumRadius;  this._rotateFactor = 1.0 / radius;  this._rotateRateRangeAdjustment = radius;  this._adjustedHeightForTerrain = false;  const previousPosition = Cartesian3.clone(    camera.positionWC,    scratchPreviousPosition  );  const previousDirection = Cartesian3.clone(    camera.directionWC,    scratchPreviousDirection  );  if (mode === SceneMode.SCENE2D) {    update2D(this);  } else if (mode === SceneMode.COLUMBUS_VIEW) {    this._horizontalRotationAxis = Cartesian3.UNIT_Z;    updateCV(this);  } else if (mode === SceneMode.SCENE3D) {    this._horizontalRotationAxis = undefined;    update3D(this);  }  if (this.enableCollisionDetection && !this._adjustedHeightForTerrain) {    // Adjust the camera height if the camera moved at all (user input or inertia) and an action didn't already adjust the camera height    const cameraChanged =      !Cartesian3.equals(previousPosition, camera.positionWC) ||      !Cartesian3.equals(previousDirection, camera.directionWC);    if (cameraChanged) {      adjustHeightForTerrain(this);    }  }  this._aggregator.reset();};/** * Returns true if this object was destroyed; otherwise, false. * <br /><br /> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>. * * @see ScreenSpaceCameraController#destroy */ScreenSpaceCameraController.prototype.isDestroyed = function () {  return false;};/** * Removes mouse listeners held by this object. * <br /><br /> * Once an object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * controller = controller && controller.destroy(); * * @see ScreenSpaceCameraController#isDestroyed */ScreenSpaceCameraController.prototype.destroy = function () {  this._tweens.removeAll();  this._aggregator = this._aggregator && this._aggregator.destroy();  return destroyObject(this);};export default ScreenSpaceCameraController;
 |