UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

561 lines 29.1 kB
import { __decorate } from "../tslib.es6.js"; import { GeospatialCameraInputsManager } from "./geospatialCameraInputsManager.js"; import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector.js"; import { Epsilon } from "../Maths/math.constants.js"; import { Camera } from "./camera.js"; import { serialize, serializeAsVector3 } from "../Misc/decorators.js"; import { GeospatialLimits } from "./Limits/geospatialLimits.js"; import { ClampCenterFromPolesInPlace, ComputeLocalBasisToRefs, GeospatialCameraMovement } from "./geospatialCameraMovement.js"; import { Vector3CopyToRef, Vector3Distance, Vector3Dot, Vector3SubtractToRef } from "../Maths/math.vector.functions.js"; import { Clamp, NormalizeRadians } from "../Maths/math.scalar.functions.js"; import { InterpolatingBehavior } from "../Behaviors/Cameras/interpolatingBehavior.js"; import { RegisterClass } from "../Misc/typeStore.js"; /** * Camera equipped to orbit a spherical planet centered at world origin */ export class GeospatialCamera extends Camera { constructor(name, scene, options) { super(name, new Vector3(), scene); // Temp vars this._tempPosition = new Vector3(); this._tempCenter = new Vector3(); this._viewMatrix = new Matrix(); this._lookAtVector = new Vector3(); this._flyToTargets = new Map(); this._collisionVelocity = new Vector3(); /** Public option to customize the collision offset applied each frame - vs the one calculated using internal CollisionCoordinator */ this.perFrameCollisionOffset = new Vector3(); /** Enable or disable collision checking for this camera. Default is false. */ this.checkCollisions = false; this._center = new Vector3(); this._yaw = 0; this._pitch = 0; this._radius = 0; this._tempVect = new Vector3(); this._tempEast = new Vector3(); this._tempNorth = new Vector3(); this._tempUp = new Vector3(); this._wasCenterMovingLastFrame = false; this._limits = new GeospatialLimits(options.planetRadius); this._flyingBehavior = new InterpolatingBehavior(); this.addBehavior(this._flyingBehavior); this.movement = new GeospatialCameraMovement(scene, this._limits, this.position, this.center, this._lookAtVector, options.pickPredicate, this._flyingBehavior); this._resetToDefault(this._limits); this.inputs = new GeospatialCameraInputsManager(this); this.inputs.addMouse().addMouseWheel().addKeyboard(); } /** The point on the globe that we are anchoring around. If no alternate rotation point is supplied, this will represent the center of screen*/ get center() { return this._center; } /** * Sets the camera position to orbit around a new center point * @param center The world position (ECEF) to orbit around */ set center(center) { this._center.copyFromFloats(center.x, center.y, center.z); this._setOrientation(this._yaw, this._pitch, this._radius, this._center); } /** * Gets the camera's yaw (rotation around the geocentric normal) in radians */ get yaw() { return this._yaw; } /** * Sets the camera's yaw (rotation around the geocentric normal). Will wrap value to [-π, π) * @param yaw The desired yaw angle in radians (0 = north, π/2 = east) */ set yaw(yaw) { yaw !== this._yaw && this._setOrientation(yaw, this.pitch, this.radius, this.center); } /** * Gets the camera's pitch (angle from looking straight at globe) * Pitch is measured from looking straight down at planet center: * - zero pitch = looking straight at planet center (down) * - positive pitch = tilting up away from planet * - π/2 pitch = looking at horizon (perpendicular to geocentric normal) */ get pitch() { return this._pitch; } /** * Sets the camera's pitch (angle from looking straight at globe). Will wrap value to [-π, π) * @param pitch The desired pitch angle in radians (0 = looking at planet center, π/2 = looking at horizon) */ set pitch(pitch) { pitch !== this._pitch && this._setOrientation(this.yaw, pitch, this.radius, this.center); } /** * Gets the camera's distance from the current center point. This is distinct from planetRadius supplied at construction. */ get radius() { return this._radius; } /** * Sets the camera's distance from the current center point * @param radius The desired radius */ set radius(radius) { radius !== this._radius && this._setOrientation(this.yaw, this.pitch, radius, this.center); } _checkLimits() { const limits = this.limits; this._yaw = Clamp(this._yaw, limits.yawMin, limits.yawMax); const effectivePitchMax = limits.getEffectivePitchMax(this._radius); this._pitch = Clamp(this._pitch, limits.pitchMin, effectivePitchMax); this._radius = Clamp(this._radius, limits.radiusMin, limits.radiusMax); ClampCenterFromPolesInPlace(this._center); } _setOrientation(yaw, pitch, radius, center) { // Wrap yaw and pitch to [-π, π) this._yaw = NormalizeRadians(yaw); this._pitch = NormalizeRadians(pitch); this._radius = radius; Vector3CopyToRef(center, this._center); // Clamp to limits this._checkLimits(); // Refresh local basis at center (treat these as read-only for the whole call) ComputeLocalBasisToRefs(this._center, this._tempEast, this._tempNorth, this._tempUp, this._scene.useRightHandedSystem, this.movement.calculateUpVectorFromPointToRef); // Compute lookAt from yaw/pitch ComputeLookAtFromYawPitchToRef(this._yaw, this._pitch, this._center, this._scene.useRightHandedSystem, this._lookAtVector, this.movement.calculateUpVectorFromPointToRef); // Build an orthonormal up aligned with geocentric Up // When looking straight down (pitch ≈ 0), lookAt is parallel to Up, so use the horizontal direction as the camera's up. const right = TmpVectors.Vector3[10]; Vector3.CrossToRef(this._tempUp, this._lookAtVector, right); if (right.lengthSquared() < Epsilon) { // horiz = north * cos(yaw) + east * sin(yaw) // Using tempEast directly ensures handedness is taken into account const horiz = TmpVectors.Vector3[11]; const t1 = TmpVectors.Vector3[12]; horiz .copyFrom(this._tempNorth) .scaleInPlace(Math.cos(this._yaw)) .addInPlace(t1.copyFrom(this._tempEast).scaleInPlace(Math.sin(this._yaw))); // right = cross(horiz, lookAt) Vector3.CrossToRef(horiz, this._lookAtVector, right); } right.normalize(); // up = normalize(cross(look, right)) Vector3.CrossToRef(this._lookAtVector, right, this.upVector); this.upVector.normalize(); // Position = center - look * radius (preserve unit look) this._tempVect.copyFrom(this._lookAtVector).scaleInPlace(-this._radius); this._tempPosition.copyFrom(this._center).addInPlace(this._tempVect); // Recalculate collisionOffset to be applied later when viewMatrix is calculated (allowing camera users to modify the value in afterCheckInputsObservable) if (this.checkCollisions) { this.perFrameCollisionOffset = this._getCollisionOffset(this._tempPosition); } this._position.copyFrom(this._tempPosition); this._isViewMatrixDirty = true; } /** * If camera is actively in flight, will update the target properties and use up the remaining duration from original flyTo call * * To start a new flyTo curve entirely, call into flyToAsync again (it will stop the inflight animation) * @param targetYaw * @param targetPitch * @param targetRadius * @param targetCenter */ updateFlyToDestination(targetYaw, targetPitch, targetRadius, targetCenter) { this._flyToTargets.clear(); // For yaw, use shortest path to target. const deltaYaw = targetYaw !== undefined ? NormalizeRadians(NormalizeRadians(targetYaw) - this._yaw) : 0; this._flyToTargets.set("yaw", deltaYaw === 0 ? undefined : this._yaw + deltaYaw); this._flyToTargets.set("pitch", targetPitch != undefined ? NormalizeRadians(targetPitch) : undefined); this._flyToTargets.set("radius", targetRadius); this._flyToTargets.set("center", targetCenter?.clone()); this._flyingBehavior.updateProperties(this._flyToTargets); } /** * Animate camera towards passed in property values. If undefined, will use current value * @param targetYaw * @param targetPitch * @param targetRadius * @param targetCenter * @param flightDurationMs * @param easingFunction * @param centerHopScale If supplied, will define the parabolic hop height scale for center animation to create a "bounce" effect * @returns Promise that will return when the animation is complete (or interuppted by pointer input) */ async flyToAsync(targetYaw, targetPitch, targetRadius, targetCenter, flightDurationMs = 1000, easingFunction, centerHopScale) { this._flyToTargets.clear(); // For yaw, use shortest path to target. const deltaYaw = targetYaw !== undefined ? NormalizeRadians(NormalizeRadians(targetYaw) - this._yaw) : 0; this._flyToTargets.set("yaw", deltaYaw === 0 ? undefined : this._yaw + deltaYaw); this._flyToTargets.set("pitch", targetPitch !== undefined ? NormalizeRadians(targetPitch) : undefined); this._flyToTargets.set("radius", targetRadius); this._flyToTargets.set("center", targetCenter?.clone()); let overrideAnimationFunction; if (targetCenter !== undefined && !targetCenter.equals(this.center)) { // Animate center directly with custom interpolation overrideAnimationFunction = (key, animation) => { if (key === "center") { // Override the Vector3 interpolation to use SLERP + hop animation.vector3InterpolateFunction = (startValue, endValue, gradient) => { // gradient is the eased value (0 to 1) after easing function is applied // Slerp between start and end const newCenter = Vector3.SlerpToRef(startValue, endValue, gradient, this._tempCenter); // Apply parabolic hop if requested if (centerHopScale && centerHopScale > 0) { // Parabolic formula: peaks at t=0.5, returns to 0 at gradient=0 and gradient=1 // if hopPeakT = .5 the denominator would be hopPeakT * hopPeakT - hopPeakT, which = -.25 const hopPeakOffset = centerHopScale * Vector3Distance(startValue, endValue); const hopOffset = hopPeakOffset * Clamp((gradient * gradient - gradient) / -0.25); // Scale the center outward (away from origin) newCenter.scaleInPlace(1 + hopOffset / newCenter.length()); } return newCenter; }; } }; } return await this._flyingBehavior.animatePropertiesAsync(this._flyToTargets, flightDurationMs, easingFunction, overrideAnimationFunction); } /** * Helper function to move camera towards a given point by `distanceScale` of the current camera-to-destination distance (by default 50%). * @param destination point to move towards * @param distanceScale value between 0 and 1, % of distance to move * @param durationMs duration of flight, default 1s * @param easingFn optional easing function for flight interpolation of properties * @param centerHopScale If supplied, will define the parabolic hop height scale for center animation to create a "bounce" effect */ async flyToPointAsync(destination, distanceScale = 0.5, durationMs = 1000, easingFn, centerHopScale) { // Move by a fraction of the camera-to-destination distance const zoomDistance = Vector3Distance(this.position, destination) * distanceScale; const newRadius = this._getCenterAndRadiusFromZoomToPoint(destination, zoomDistance, this._tempCenter); await this.flyToAsync(undefined, undefined, newRadius, this._tempCenter, durationMs, easingFn, centerHopScale); !this.isDisposed() && this._recalculateCenter(false, true /** force */); } /** * Gets the limits for this camera */ get limits() { return this._limits; } _resetToDefault(limits) { // Camera configuration vars const restingAltitude = limits.radiusMax !== Infinity ? limits.radiusMax : limits.planetRadius * 4; this.position.copyFromFloats(restingAltitude, 0, 0); this._center.copyFromFloats(limits.planetRadius, 0, 0); this._radius = Vector3.Distance(this.position, this.center); // Temp vars this._tempPosition = new Vector3(); // View matrix calculation vars this._viewMatrix = Matrix.Identity(); this._center.subtractToRef(this._position, this._lookAtVector).normalize(); // Lookat vector of the camera this.upVector = Vector3.Up(); // Up vector of the camera (does work for -X look at) this._isViewMatrixDirty = true; this._setOrientation(this._yaw, this._pitch, this._radius, this._center); } /** @internal */ _getViewMatrix() { if (!this._isViewMatrixDirty) { return this._viewMatrix; } this._isViewMatrixDirty = false; // Ensure vectors are normalized this.upVector.normalize(); this._lookAtVector.normalize(); // Apply the same offset to both position and center to preserve orbital relationship // This keeps yaw/pitch/radius intact - just lifts the whole "rig" this._position.addInPlace(this.perFrameCollisionOffset); this._center.addInPlace(this.perFrameCollisionOffset); // Calculate view matrix with camera position and center if (this.getScene().useRightHandedSystem) { Matrix.LookAtRHToRef(this.position, this._center, this.upVector, this._viewMatrix); } else { Matrix.LookAtLHToRef(this.position, this._center, this.upVector, this._viewMatrix); } return this._viewMatrix; } /** @internal */ _isSynchronizedViewMatrix() { if (!super._isSynchronizedViewMatrix() || this._isViewMatrixDirty) { return false; } return true; } _applyGeocentricTranslation() { // Store pending position (without any corrections applied) this.center.addToRef(this.movement.panDeltaCurrentFrame, this._tempPosition); if (!this.movement.isInterpolating) { // Calculate the position correction to keep camera at the same radius when applying translation this._tempPosition.normalize().scaleInPlace(this.center.length()); } // Set center which will call _setOrientation this.center = this._tempPosition; } /** * This rotation keeps the camera oriented towards the globe as it orbits around it. This is different from cameraCentricRotation which is when the camera rotates around its own axis */ _applyGeocentricRotation() { const rotationDeltaCurrentFrame = this.movement.rotationDeltaCurrentFrame; if (rotationDeltaCurrentFrame.x !== 0 || rotationDeltaCurrentFrame.y !== 0) { const pitch = rotationDeltaCurrentFrame.x !== 0 ? Clamp(this._pitch + rotationDeltaCurrentFrame.x, 0, 0.5 * Math.PI - Epsilon) : this._pitch; const yaw = rotationDeltaCurrentFrame.y !== 0 ? this._yaw + rotationDeltaCurrentFrame.y : this._yaw; this._setOrientation(yaw, pitch, this._radius, this._center); } } _getCenterAndRadiusFromZoomToPoint(targetPoint, distance, newCenterResult) { const directionToTarget = Vector3SubtractToRef(targetPoint, this._position, TmpVectors.Vector3[0]); const distanceToTarget = directionToTarget.length(); // Don't zoom past the min radius limit. if (distanceToTarget < this.limits.radiusMin) { newCenterResult.copyFrom(this._center); const requestedRadius = this._radius - distance; const newRadius = Clamp(requestedRadius, this.limits.radiusMin, this.limits.radiusMax); return newRadius; } // Move the camera position towards targetPoint by distanceToTarget directionToTarget.scaleInPlace(distance / distanceToTarget); const newPosition = this._position.addToRef(directionToTarget, TmpVectors.Vector3[1]); // Project the movement onto the look vector to derive the new center/radius. const projectedDistance = Vector3Dot(directionToTarget, this._lookAtVector); const newRadius = this._radius - projectedDistance; const newRadiusClamped = Clamp(newRadius, this.limits.radiusMin, this.limits.radiusMax); newCenterResult.copyFrom(newPosition).addInPlace(this._lookAtVector.scale(newRadiusClamped)); return newRadiusClamped; } /** * Apply zoom by moving the camera toward/away from a target point. */ _applyZoom() { let zoomDelta = this.movement.zoomDeltaCurrentFrame; const pickedPoint = this.movement.computedPerFrameZoomPickPoint; // Clamp zoom delta to limits before applying zoomDelta = this._clampZoomDelta(zoomDelta, pickedPoint); if (Math.abs(zoomDelta) < Epsilon) { return; } if (pickedPoint) { // Zoom toward the picked point under cursor this.zoomToPoint(pickedPoint, zoomDelta); } else { // Zoom along lookAt vector (fallback when no surface under cursor) this.zoomAlongLookAt(zoomDelta); } } _clampZoomDelta(zoomDelta, pickedPoint) { if (Math.abs(zoomDelta) < Epsilon) { return 0; } const distanceToTarget = pickedPoint ? Vector3Distance(this._position, pickedPoint) : undefined; return this.limits.clampZoomDistance(zoomDelta, this._radius, distanceToTarget); } /** * Zoom towards a specific point on the globe * @param targetPoint The point to zoom towards * @param distance The distance to move */ zoomToPoint(targetPoint, distance) { const newRadius = this._getCenterAndRadiusFromZoomToPoint(targetPoint, distance, this._tempCenter); // Apply the new orientation this._setOrientation(this._yaw, this._pitch, newRadius, this._tempCenter); } /** * Zoom along the camera's lookAt direction * @param distance The distance to zoom */ zoomAlongLookAt(distance) { // Clamp radius to limits const requestedRadius = this._radius - distance; const newRadius = Clamp(requestedRadius, this.limits.radiusMin, this.limits.radiusMax); // Simply change radius without moving center this._setOrientation(this._yaw, this._pitch, newRadius, this._center); } /** @internal */ _checkInputs() { this.inputs.checkInputs(); this.perFrameCollisionOffset.setAll(0); // Let movement class handle all per-frame logic this.movement.computeCurrentFrameDeltas(); let isCenterMoving = false; if (this.movement.panDeltaCurrentFrame.lengthSquared() > 0) { this._applyGeocentricTranslation(); // After a drag, recalculate the center point to ensure it's still on the surface. isCenterMoving = true; } if (this.movement.rotationDeltaCurrentFrame.lengthSquared() > 0) { this._applyGeocentricRotation(); } if (Math.abs(this.movement.zoomDeltaCurrentFrame) > Epsilon) { this._applyZoom(); isCenterMoving = true; } // After a movement impacting center or radius, recalculate the center point to ensure it's still on the surface. this._recalculateCenter(isCenterMoving); super._checkInputs(); } _recalculateCenter(isCenterMoving, forceRecalculate = false) { const shouldRecalculateCenterAfterMove = this._wasCenterMovingLastFrame && !isCenterMoving; this._wasCenterMovingLastFrame = isCenterMoving; // Wait until movement impacting center is complete to avoid wasted raycasting if (shouldRecalculateCenterAfterMove || forceRecalculate) { const newCenter = this.movement.pickAlongVector(this._lookAtVector); if (newCenter?.pickedPoint) { // Direction from new center to origin const centerToOrigin = TmpVectors.Vector3[4]; centerToOrigin.copyFrom(newCenter.pickedPoint).negateInPlace().normalize(); // Check if this direction aligns with camera's lookAt vector const dotProduct = Vector3Dot(this._lookAtVector, centerToOrigin); // Only update if the center is looking toward the origin (dot product > 0) to avoid a center on the opposite side of globe if (dotProduct > 0) { // Compute the new radius as distance from camera position to new center const newRadius = Vector3Distance(this._position, newCenter.pickedPoint); // Only update if the new center is in front of the camera if (newRadius > Epsilon) { // Compute yaw/pitch that correspond to current lookAt at new center const yawPitch = TmpVectors.Vector2[0]; ComputeYawPitchFromLookAtToRef(this._lookAtVector, newCenter.pickedPoint, this._scene.useRightHandedSystem, this._yaw, yawPitch, this.movement.calculateUpVectorFromPointToRef); // Call _setOrientation with the computed yaw/pitch and new center this._setOrientation(yawPitch.x, yawPitch.y, newRadius, newCenter.pickedPoint); } } } } } /** * Allows extended classes to override how collision offset is calculated * @param newPosition * @returns */ _getCollisionOffset(newPosition) { const collisionOffset = TmpVectors.Vector3[6].setAll(0); if (!this.checkCollisions || !this._scene.collisionsEnabled) { return collisionOffset; } const coordinator = this.getScene().collisionCoordinator; if (!coordinator) { return collisionOffset; } if (!this._collider) { this._collider = coordinator.createCollider(); } this._collider._radius.setAll(this.limits.radiusMin); // Calculate velocity from old position to new position newPosition.subtractToRef(this._position, this._collisionVelocity); // Get the collision-adjusted position const adjustedPosition = coordinator.getNewPosition(this._position, this._collisionVelocity, this._collider, 3, null, () => { }, this.uniqueId); // Calculate the collision offset (how much the position was pushed) adjustedPosition.subtractToRef(newPosition, collisionOffset); return collisionOffset; } /** @internal */ attachControl(noPreventDefault) { this.inputs.attachElement(noPreventDefault); } /** @internal */ detachControl() { this.inputs.detachElement(); } /** * Gets the class name of the camera. * @returns the class name */ getClassName() { return "GeospatialCamera"; } } __decorate([ serialize() ], GeospatialCamera.prototype, "checkCollisions", void 0); __decorate([ serializeAsVector3() ], GeospatialCamera.prototype, "_center", void 0); __decorate([ serialize() ], GeospatialCamera.prototype, "_yaw", void 0); __decorate([ serialize() ], GeospatialCamera.prototype, "_pitch", void 0); __decorate([ serialize() ], GeospatialCamera.prototype, "_radius", void 0); // Register Class Name RegisterClass("BABYLON.GeospatialCamera", GeospatialCamera); /** * Compute the lookAt direction vector from yaw and pitch angles at a given center point. * This is the forward formula used by GeospatialCamera._setOrientation. * @param yaw - The yaw angle in radians (0 = north, π/2 = east) * @param pitch - The pitch angle in radians (0 = looking at planet center, π/2 = looking at horizon) * @param center - The center point on the globe * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system * @param result - The vector to store the result in * @param calculateUpVectorFromPointToRef - Optional function to calculate the up vector from a point, allowing for non-spherical planets. If not supplied, a perfect sphere is assumed and the up vector is just the normalized center point. * @returns The normalized lookAt direction vector (same as result) */ export function ComputeLookAtFromYawPitchToRef(yaw, pitch, center, useRightHandedSystem, result, calculateUpVectorFromPointToRef) { const east = TmpVectors.Vector3[0]; const north = TmpVectors.Vector3[1]; const up = TmpVectors.Vector3[2]; ComputeLocalBasisToRefs(center, east, north, up, useRightHandedSystem, calculateUpVectorFromPointToRef); const sinPitch = Math.sin(pitch); const cosPitch = Math.cos(pitch); // horiz = north * cos(yaw) + east * sin(yaw) // Handedness is taken into account when defining east vector via ComputeLocalBasisToRefs. const horiz = TmpVectors.Vector3[3]; const t1 = TmpVectors.Vector3[4]; horiz .copyFrom(north) .scaleInPlace(Math.cos(yaw)) .addInPlace(t1.copyFrom(east).scaleInPlace(Math.sin(yaw))); // lookAt = horiz * sinPitch - up * cosPitch const t2 = TmpVectors.Vector3[5]; result.copyFrom(horiz).scaleInPlace(sinPitch).addInPlace(t2.copyFrom(up).scaleInPlace(-cosPitch)); return result.normalize(); } /** * Given a lookAt direction and center, compute the yaw and pitch angles that would produce that lookAt. * This is the inverse of ComputeLookAtFromYawPitchToRef. * @param lookAt - The normalized lookAt direction vector * @param center - The center point on the globe * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system * @param currentYaw - The current yaw value to use as fallback when pitch is near 0 (looking straight down/up) * @param result - The Vector2 to store the result in (x = yaw, y = pitch) * @param calculateUpVectorFromPointToRef - Optional function to calculate the up vector from a point. If supplied, this function will be used instead of assuming a spherical geocentric normal, allowing support for non-spherical planets or custom up vector logic. * @returns The result Vector2 */ export function ComputeYawPitchFromLookAtToRef(lookAt, center, useRightHandedSystem, currentYaw, result, calculateUpVectorFromPointToRef) { // Compute local basis at center const east = TmpVectors.Vector3[6]; const north = TmpVectors.Vector3[7]; const up = TmpVectors.Vector3[8]; ComputeLocalBasisToRefs(center, east, north, up, useRightHandedSystem, calculateUpVectorFromPointToRef); // lookAt = horiz*sinPitch - up*cosPitch // where horiz = north*cos(yaw) + east*sin(yaw) // // The vertical component of lookAt (along up) gives us cosPitch: // lookAt · up = -cosPitch const lookDotUp = Vector3Dot(lookAt, up); const cosPitch = -lookDotUp; // Clamp cosPitch to valid range to avoid NaN from acos const clampedCosPitch = Clamp(cosPitch, -1, 1); const pitch = Math.acos(clampedCosPitch); // The horizontal component gives us yaw // lookHorizontal = lookAt + up*cosPitch = horiz*sinPitch const lookHorizontal = TmpVectors.Vector3[9]; const scaledUp = TmpVectors.Vector3[10]; scaledUp.copyFrom(up).scaleInPlace(cosPitch); lookHorizontal.copyFrom(lookAt).addInPlace(scaledUp); const sinPitch = Math.sin(pitch); if (Math.abs(sinPitch) < Epsilon) { // Looking straight down or up, yaw is undefined - keep current result.x = currentYaw; result.y = pitch; return result; } // horiz = lookHorizontal / sinPitch const horiz = lookHorizontal.scaleInPlace(1 / sinPitch); // From the forward formula: horiz = North*cos(yaw) + East*sin(yaw) // So: cosYaw = horiz · north, sinYaw = horiz · east const cosYaw = Vector3Dot(horiz, north); const sinYaw = Vector3Dot(horiz, east); result.x = Math.atan2(sinYaw, cosYaw); result.y = pitch; return result; } //# sourceMappingURL=geospatialCamera.js.map