@onerjs/core 8.36.7 → 8.36.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,10 +3,9 @@ import type { GeospatialCamera } from "../../Cameras/geospatialCamera.js";
3
3
  import type { Nullable } from "../../types.js";
4
4
  /**
5
5
  * The GeospatialClippingBehavior automatically adjusts the near and far clip planes of a GeospatialCamera
6
- * based on altitude and viewing angle to optimize depth buffer precision for geospatial applications.
6
+ * based on altitude to optimize depth buffer precision for geospatial applications.
7
7
  *
8
- * When viewing from high altitudes looking down, a larger near plane can be used.
9
- * When viewing horizontally at ground level, a smaller near plane is needed to avoid clipping nearby terrain.
8
+ * The near plane scales with altitude (distance to planet surface) to maintain good depth precision.
10
9
  * The far plane is calculated based on the visible horizon distance.
11
10
  */
12
11
  export declare class GeospatialClippingBehavior implements Behavior<GeospatialCamera> {
@@ -34,7 +33,7 @@ export declare class GeospatialClippingBehavior implements Behavior<GeospatialCa
34
33
  */
35
34
  detach(): void;
36
35
  /**
37
- * Updates the camera's near and far clip planes based on altitude and viewing angle.
36
+ * Updates the camera's near and far clip planes based on altitude.
38
37
  */
39
38
  private _updateCameraClipPlanes;
40
39
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * The GeospatialClippingBehavior automatically adjusts the near and far clip planes of a GeospatialCamera
3
- * based on altitude and viewing angle to optimize depth buffer precision for geospatial applications.
3
+ * based on altitude to optimize depth buffer precision for geospatial applications.
4
4
  *
5
- * When viewing from high altitudes looking down, a larger near plane can be used.
6
- * When viewing horizontally at ground level, a smaller near plane is needed to avoid clipping nearby terrain.
5
+ * The near plane scales with altitude (distance to planet surface) to maintain good depth precision.
7
6
  * The far plane is calculated based on the visible horizon distance.
8
7
  */
9
8
  export class GeospatialClippingBehavior {
@@ -54,7 +53,7 @@ export class GeospatialClippingBehavior {
54
53
  this._attachedCamera = null;
55
54
  }
56
55
  /**
57
- * Updates the camera's near and far clip planes based on altitude and viewing angle.
56
+ * Updates the camera's near and far clip planes based on altitude.
58
57
  */
59
58
  _updateCameraClipPlanes() {
60
59
  const camera = this._attachedCamera;
@@ -62,17 +61,13 @@ export class GeospatialClippingBehavior {
62
61
  return;
63
62
  }
64
63
  const planetRadius = camera.limits.planetRadius;
65
- const altitude = Math.max(1, camera.radius);
66
- const pitch = camera.pitch; // 0 = looking down, π/2 = looking at horizon
67
- // Near plane calculation:
68
- // - When looking down (pitch 0): nearest visible point is roughly at altitude distance
69
- // - When looking at horizon (pitch ≈ π/2): nearby terrain can be much closer
70
- // Use pitch to blend between a small near (for horizontal view) and altitude-based near (for top-down)
71
- const pitchFactor = Math.sin(pitch); // 0 when looking down, 1 at horizon
72
- const minNearHorizontal = 1; // When looking horizontally, need small near plane
73
- const minNearVertical = Math.max(1, altitude * 0.01); // When looking down, can use larger near
74
- camera.minZ = minNearHorizontal + (minNearVertical - minNearHorizontal) * (1 - pitchFactor);
64
+ // Camera position length gives distance to world origin (planet center)
65
+ const altitude = Math.max(1, camera.position.length() - planetRadius);
66
+ // Near plane: scale with altitude to maintain depth buffer precision
67
+ // Use a fraction of altitude - the closest visible point on a sphere is straight down at distance = altitude
68
+ camera.minZ = Math.max(1, altitude * 0.001);
75
69
  // Far plane: see to the horizon and beyond
70
+ // Horizon distance formula: √(2Rh + h²) where h is altitude above surface
76
71
  const horizonDist = Math.sqrt(2 * planetRadius * altitude + altitude * altitude);
77
72
  camera.maxZ = horizonDist + planetRadius * 0.1;
78
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"geospatialClippingBehavior.js","sourceRoot":"","sources":["../../../../../dev/core/src/Behaviors/Cameras/geospatialClippingBehavior.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,OAAO,0BAA0B;IAAvC;QAQY,oBAAe,GAA+B,IAAI,CAAC;QACnD,4BAAuB,GAA8B,IAAI,CAAC;IAsEtE,CAAC;IA9EG;;OAEG;IACH,IAAW,IAAI;QACX,OAAO,oBAAoB,CAAC;IAChC,CAAC;IAKD;;OAEG;IACH,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED;;OAEG;IACI,IAAI;QACP,aAAa;IACjB,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAwB;QAClC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAEhC,IAAI,CAAC,uBAAuB,GAAG,KAAK,CAAC,wBAAwB,CAAC,GAAG,CAAC,GAAG,EAAE;YACnE,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACI,MAAM;QACT,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC9C,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC/B,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBACpE,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAC;YACxC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;IAChC,CAAC;IAED;;OAEG;IACK,uBAAuB;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO;QACX,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,6CAA6C;QAEzE,0BAA0B;QAC1B,yFAAyF;QACzF,6EAA6E;QAE7E,uGAAuG;QACvG,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,oCAAoC;QACzE,MAAM,iBAAiB,GAAG,CAAC,CAAC,CAAC,mDAAmD;QAChF,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,yCAAyC;QAC/F,MAAM,CAAC,IAAI,GAAG,iBAAiB,GAAG,CAAC,eAAe,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC;QAE5F,2CAA2C;QAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;QACjF,MAAM,CAAC,IAAI,GAAG,WAAW,GAAG,YAAY,GAAG,GAAG,CAAC;IACnD,CAAC;CACJ","sourcesContent":["import type { Behavior } from \"../../Behaviors/behavior\";\r\nimport type { GeospatialCamera } from \"../../Cameras/geospatialCamera\";\r\nimport type { Nullable } from \"../../types\";\r\nimport type { Observer } from \"../../Misc/observable\";\r\nimport type { Scene } from \"../../scene\";\r\n\r\n/**\r\n * The GeospatialClippingBehavior automatically adjusts the near and far clip planes of a GeospatialCamera\r\n * based on altitude and viewing angle to optimize depth buffer precision for geospatial applications.\r\n *\r\n * When viewing from high altitudes looking down, a larger near plane can be used.\r\n * When viewing horizontally at ground level, a smaller near plane is needed to avoid clipping nearby terrain.\r\n * The far plane is calculated based on the visible horizon distance.\r\n */\r\nexport class GeospatialClippingBehavior implements Behavior<GeospatialCamera> {\r\n /**\r\n * Gets the name of the behavior.\r\n */\r\n public get name(): string {\r\n return \"GeospatialClipping\";\r\n }\r\n\r\n private _attachedCamera: Nullable<GeospatialCamera> = null;\r\n private _onBeforeRenderObserver: Nullable<Observer<Scene>> = null;\r\n\r\n /**\r\n * Gets the attached camera.\r\n */\r\n public get attachedNode(): Nullable<GeospatialCamera> {\r\n return this._attachedCamera;\r\n }\r\n\r\n /**\r\n * Initializes the behavior.\r\n */\r\n public init(): void {\r\n // Do nothing\r\n }\r\n\r\n /**\r\n * Attaches the behavior to its geospatial camera.\r\n * @param camera Defines the camera to attach the behavior to\r\n */\r\n public attach(camera: GeospatialCamera): void {\r\n this._attachedCamera = camera;\r\n const scene = camera.getScene();\r\n\r\n this._onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {\r\n this._updateCameraClipPlanes();\r\n });\r\n }\r\n\r\n /**\r\n * Detaches the behavior from its current geospatial camera.\r\n */\r\n public detach(): void {\r\n if (this._attachedCamera) {\r\n const scene = this._attachedCamera.getScene();\r\n if (this._onBeforeRenderObserver) {\r\n scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);\r\n this._onBeforeRenderObserver = null;\r\n }\r\n }\r\n this._attachedCamera = null;\r\n }\r\n\r\n /**\r\n * Updates the camera's near and far clip planes based on altitude and viewing angle.\r\n */\r\n private _updateCameraClipPlanes(): void {\r\n const camera = this._attachedCamera;\r\n if (!camera) {\r\n return;\r\n }\r\n\r\n const planetRadius = camera.limits.planetRadius;\r\n const altitude = Math.max(1, camera.radius);\r\n const pitch = camera.pitch; // 0 = looking down, π/2 = looking at horizon\r\n\r\n // Near plane calculation:\r\n // - When looking down (pitch ≈ 0): nearest visible point is roughly at altitude distance\r\n // - When looking at horizon (pitch ≈ π/2): nearby terrain can be much closer\r\n\r\n // Use pitch to blend between a small near (for horizontal view) and altitude-based near (for top-down)\r\n const pitchFactor = Math.sin(pitch); // 0 when looking down, 1 at horizon\r\n const minNearHorizontal = 1; // When looking horizontally, need small near plane\r\n const minNearVertical = Math.max(1, altitude * 0.01); // When looking down, can use larger near\r\n camera.minZ = minNearHorizontal + (minNearVertical - minNearHorizontal) * (1 - pitchFactor);\r\n\r\n // Far plane: see to the horizon and beyond\r\n const horizonDist = Math.sqrt(2 * planetRadius * altitude + altitude * altitude);\r\n camera.maxZ = horizonDist + planetRadius * 0.1;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"geospatialClippingBehavior.js","sourceRoot":"","sources":["../../../../../dev/core/src/Behaviors/Cameras/geospatialClippingBehavior.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,OAAO,0BAA0B;IAAvC;QAQY,oBAAe,GAA+B,IAAI,CAAC;QACnD,4BAAuB,GAA8B,IAAI,CAAC;IAiEtE,CAAC;IAzEG;;OAEG;IACH,IAAW,IAAI;QACX,OAAO,oBAAoB,CAAC;IAChC,CAAC;IAKD;;OAEG;IACH,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED;;OAEG;IACI,IAAI;QACP,aAAa;IACjB,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAwB;QAClC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAEhC,IAAI,CAAC,uBAAuB,GAAG,KAAK,CAAC,wBAAwB,CAAC,GAAG,CAAC,GAAG,EAAE;YACnE,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACI,MAAM;QACT,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC9C,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC/B,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBACpE,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAC;YACxC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;IAChC,CAAC;IAED;;OAEG;IACK,uBAAuB;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO;QACX,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;QAChD,wEAAwE;QACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,YAAY,CAAC,CAAC;QAEtE,qEAAqE;QACrE,6GAA6G;QAC7G,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC;QAE5C,2CAA2C;QAC3C,0EAA0E;QAC1E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;QACjF,MAAM,CAAC,IAAI,GAAG,WAAW,GAAG,YAAY,GAAG,GAAG,CAAC;IACnD,CAAC;CACJ","sourcesContent":["import type { Behavior } from \"../../Behaviors/behavior\";\r\nimport type { GeospatialCamera } from \"../../Cameras/geospatialCamera\";\r\nimport type { Nullable } from \"../../types\";\r\nimport type { Observer } from \"../../Misc/observable\";\r\nimport type { Scene } from \"../../scene\";\r\n\r\n/**\r\n * The GeospatialClippingBehavior automatically adjusts the near and far clip planes of a GeospatialCamera\r\n * based on altitude to optimize depth buffer precision for geospatial applications.\r\n *\r\n * The near plane scales with altitude (distance to planet surface) to maintain good depth precision.\r\n * The far plane is calculated based on the visible horizon distance.\r\n */\r\nexport class GeospatialClippingBehavior implements Behavior<GeospatialCamera> {\r\n /**\r\n * Gets the name of the behavior.\r\n */\r\n public get name(): string {\r\n return \"GeospatialClipping\";\r\n }\r\n\r\n private _attachedCamera: Nullable<GeospatialCamera> = null;\r\n private _onBeforeRenderObserver: Nullable<Observer<Scene>> = null;\r\n\r\n /**\r\n * Gets the attached camera.\r\n */\r\n public get attachedNode(): Nullable<GeospatialCamera> {\r\n return this._attachedCamera;\r\n }\r\n\r\n /**\r\n * Initializes the behavior.\r\n */\r\n public init(): void {\r\n // Do nothing\r\n }\r\n\r\n /**\r\n * Attaches the behavior to its geospatial camera.\r\n * @param camera Defines the camera to attach the behavior to\r\n */\r\n public attach(camera: GeospatialCamera): void {\r\n this._attachedCamera = camera;\r\n const scene = camera.getScene();\r\n\r\n this._onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {\r\n this._updateCameraClipPlanes();\r\n });\r\n }\r\n\r\n /**\r\n * Detaches the behavior from its current geospatial camera.\r\n */\r\n public detach(): void {\r\n if (this._attachedCamera) {\r\n const scene = this._attachedCamera.getScene();\r\n if (this._onBeforeRenderObserver) {\r\n scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);\r\n this._onBeforeRenderObserver = null;\r\n }\r\n }\r\n this._attachedCamera = null;\r\n }\r\n\r\n /**\r\n * Updates the camera's near and far clip planes based on altitude.\r\n */\r\n private _updateCameraClipPlanes(): void {\r\n const camera = this._attachedCamera;\r\n if (!camera) {\r\n return;\r\n }\r\n\r\n const planetRadius = camera.limits.planetRadius;\r\n // Camera position length gives distance to world origin (planet center)\r\n const altitude = Math.max(1, camera.position.length() - planetRadius);\r\n\r\n // Near plane: scale with altitude to maintain depth buffer precision\r\n // Use a fraction of altitude - the closest visible point on a sphere is straight down at distance = altitude\r\n camera.minZ = Math.max(1, altitude * 0.001);\r\n\r\n // Far plane: see to the horizon and beyond\r\n // Horizon distance formula: √(2Rh + h²) where h is altitude above surface\r\n const horizonDist = Math.sqrt(2 * planetRadius * altitude + altitude * altitude);\r\n camera.maxZ = horizonDist + planetRadius * 0.1;\r\n }\r\n}\r\n"]}
@@ -67,7 +67,7 @@ export class GeospatialCameraPointersInput extends OrbitCameraPointersInput {
67
67
  const canvasX = this._pinchCentroid.x - canvasRect.left;
68
68
  const canvasY = this._pinchCentroid.y - canvasRect.top;
69
69
  // Pick at centroid
70
- const pickResult = scene.pick(canvasX, canvasY, this.camera.pickPredicate);
70
+ const pickResult = scene.pick(canvasX, canvasY, this.camera.movement.pickPredicate);
71
71
  if (pickResult?.pickedPoint) {
72
72
  // Scale zoom by distance to picked point
73
73
  const distanceToPoint = this.camera.position.subtract(pickResult.pickedPoint).length();
@@ -97,7 +97,7 @@ export class GeospatialCameraPointersInput extends OrbitCameraPointersInput {
97
97
  }
98
98
  }
99
99
  onDoubleTap(type) {
100
- const pickResult = this.camera._scene.pick(this.camera._scene.pointerX, this.camera._scene.pointerY, this.camera.pickPredicate);
100
+ const pickResult = this.camera._scene.pick(this.camera._scene.pointerX, this.camera._scene.pointerY, this.camera.movement.pickPredicate);
101
101
  if (pickResult.pickedPoint) {
102
102
  void this.camera.flyToPointAsync(pickResult.pickedPoint);
103
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"geospatialCameraPointersInput.js","sourceRoot":"","sources":["../../../../../dev/core/src/Cameras/Inputs/geospatialCameraPointersInput.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GeospatialCamera } from \"../../Cameras/geospatialCamera\";\r\nimport type { IPointerEvent } from \"../../Events/deviceInputEvents\";\r\nimport type { PointerTouch } from \"../../Events/pointerEvents\";\r\nimport type { Nullable } from \"../../types\";\r\nimport { OrbitCameraPointersInput } from \"./orbitCameraPointersInput\";\r\n\r\n/**\r\n * @experimental\r\n * Geospatial camera inputs can simulate dragging the globe around or tilting the camera around some point on the globe\r\n * This class will update the GeospatialCameraMovement class's movementDeltaCurrentFrame, and the camera is responsible for using these updates to calculate viewMatrix appropriately\r\n *\r\n * As of right now, the camera correction logic (to keep the camera geospatially oriented around the globe) is happening within the camera class when calculating viewmatrix\r\n * As this is experimental, it is possible we move that correction step to live within the input class (to enable non-corrected translations in the future), say if we want to allow the camera to move outside of the globe's orbit\r\n *\r\n * Left mouse button: drag globe\r\n * Middle mouse button: tilt globe\r\n * Right mouse button: tilt globe\r\n *\r\n */\r\nexport class GeospatialCameraPointersInput extends OrbitCameraPointersInput {\r\n public camera: GeospatialCamera;\r\n\r\n private _initialPinchSquaredDistance: number = 0;\r\n private _pinchCentroid: Nullable<PointerTouch> = null;\r\n\r\n public override getClassName(): string {\r\n return \"GeospatialCameraPointersInput\";\r\n }\r\n\r\n public override onButtonDown(evt: IPointerEvent): void {\r\n this.camera.movement.activeInput = true;\r\n const scene = this.camera.getScene();\r\n switch (evt.button) {\r\n case 0: // Left button - drag/pan globe under cursor\r\n this.camera.movement.startDrag(scene.pointerX, scene.pointerY);\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n public override onTouch(point: Nullable<PointerTouch>, offsetX: number, offsetY: number): void {\r\n // Single finger touch (no button property) or left button (button 0) = drag\r\n const button = point?.button ?? 0; // Default to button 0 (drag) if undefined\r\n const scene = this.camera.getScene();\r\n switch (button) {\r\n case 0: // Left button / single touch - drag/pan globe under cursor\r\n this.camera.movement.handleDrag(scene.pointerX, scene.pointerY);\r\n break;\r\n case 1: // Middle button - tilt camera\r\n case 2: // Right button - tilt camera\r\n this._handleTilt(offsetX, offsetY);\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Move camera from multitouch (pinch) zoom distances.\r\n * Zooms towards the centroid (midpoint between the two fingers).\r\n * @param previousPinchSquaredDistance\r\n * @param pinchSquaredDistance\r\n */\r\n protected override _computePinchZoom(previousPinchSquaredDistance: number, pinchSquaredDistance: number): void {\r\n // Calculate zoom distance based on pinch delta\r\n const previousDistance = Math.sqrt(previousPinchSquaredDistance);\r\n const currentDistance = Math.sqrt(pinchSquaredDistance);\r\n const pinchDelta = currentDistance - previousDistance;\r\n\r\n // Try to zoom towards centroid if we have it\r\n if (this._pinchCentroid) {\r\n const scene = this.camera.getScene();\r\n const engine = scene.getEngine();\r\n const canvasRect = engine.getInputElementClientRect();\r\n\r\n if (canvasRect) {\r\n // Convert centroid from clientX/Y to canvas-relative coordinates (same as scene.pointerX/Y)\r\n const canvasX = this._pinchCentroid.x - canvasRect.left;\r\n const canvasY = this._pinchCentroid.y - canvasRect.top;\r\n\r\n // Pick at centroid\r\n const pickResult = scene.pick(canvasX, canvasY, this.camera.pickPredicate);\r\n if (pickResult?.pickedPoint) {\r\n // Scale zoom by distance to picked point\r\n const distanceToPoint = this.camera.position.subtract(pickResult.pickedPoint).length();\r\n const zoomDistance = pinchDelta * distanceToPoint * 0.005;\r\n const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius, distanceToPoint);\r\n this.camera.zoomToPoint(pickResult.pickedPoint, clampedZoom);\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Fallback: scale zoom by camera radius along lookat vector\r\n const zoomDistance = pinchDelta * this.camera.radius * 0.005;\r\n const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius);\r\n this.camera.zoomAlongLookAt(clampedZoom);\r\n }\r\n\r\n /**\r\n * Move camera from multi touch panning positions.\r\n * In geospatialcamera, multi touch panning tilts the globe (whereas single touch will pan/drag it)\r\n * @param previousMultiTouchPanPosition\r\n * @param multiTouchPanPosition\r\n */\r\n protected override _computeMultiTouchPanning(previousMultiTouchPanPosition: Nullable<PointerTouch>, multiTouchPanPosition: Nullable<PointerTouch>): void {\r\n if (previousMultiTouchPanPosition && multiTouchPanPosition) {\r\n const moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;\r\n const moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;\r\n this._handleTilt(moveDeltaX, moveDeltaY);\r\n }\r\n }\r\n\r\n public override onDoubleTap(type: string): void {\r\n const pickResult = this.camera._scene.pick(this.camera._scene.pointerX, this.camera._scene.pointerY, this.camera.pickPredicate);\r\n if (pickResult.pickedPoint) {\r\n void this.camera.flyToPointAsync(pickResult.pickedPoint);\r\n }\r\n }\r\n\r\n public override onMultiTouch(\r\n pointA: Nullable<PointerTouch>,\r\n pointB: Nullable<PointerTouch>,\r\n previousPinchSquaredDistance: number,\r\n pinchSquaredDistance: number,\r\n previousMultiTouchPanPosition: Nullable<PointerTouch>,\r\n multiTouchPanPosition: Nullable<PointerTouch>\r\n ): void {\r\n // Store centroid for use in _computePinchZoom (it's already calculated by parent)\r\n this._pinchCentroid = multiTouchPanPosition;\r\n\r\n // Reset on gesture end\r\n if (pinchSquaredDistance === 0 && multiTouchPanPosition === null) {\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);\r\n return;\r\n }\r\n\r\n // Track initial distance at gesture start for cumulative threshold detection\r\n if (this._initialPinchSquaredDistance === 0 && pinchSquaredDistance !== 0) {\r\n this._initialPinchSquaredDistance = pinchSquaredDistance;\r\n }\r\n\r\n // Use cumulative delta from gesture start for threshold detection (more forgiving than frame-to-frame)\r\n const cumulativeDelta = Math.abs(Math.sqrt(pinchSquaredDistance) - Math.sqrt(this._initialPinchSquaredDistance));\r\n this._shouldStartPinchZoom = this._twoFingerActivityCount < 20 && cumulativeDelta > this.camera.limits.pinchToPanMax;\r\n\r\n super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);\r\n }\r\n\r\n public override onButtonUp(_evt: IPointerEvent): void {\r\n this.camera.movement.stopDrag();\r\n this.camera.movement.activeInput = false;\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onButtonUp(_evt);\r\n }\r\n\r\n public override onLostFocus(): void {\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onLostFocus();\r\n }\r\n\r\n private _handleTilt(deltaX: number, deltaY: number): void {\r\n this.camera.movement.rotationAccumulatedPixels.y -= deltaX; // yaw - looking side to side\r\n this.camera.movement.rotationAccumulatedPixels.x -= deltaY; // pitch - look up towards sky / down towards ground\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"geospatialCameraPointersInput.js","sourceRoot":"","sources":["../../../../../dev/core/src/Cameras/Inputs/geospatialCameraPointersInput.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GeospatialCamera } from \"../../Cameras/geospatialCamera\";\r\nimport type { IPointerEvent } from \"../../Events/deviceInputEvents\";\r\nimport type { PointerTouch } from \"../../Events/pointerEvents\";\r\nimport type { Nullable } from \"../../types\";\r\nimport { OrbitCameraPointersInput } from \"./orbitCameraPointersInput\";\r\n\r\n/**\r\n * @experimental\r\n * Geospatial camera inputs can simulate dragging the globe around or tilting the camera around some point on the globe\r\n * This class will update the GeospatialCameraMovement class's movementDeltaCurrentFrame, and the camera is responsible for using these updates to calculate viewMatrix appropriately\r\n *\r\n * As of right now, the camera correction logic (to keep the camera geospatially oriented around the globe) is happening within the camera class when calculating viewmatrix\r\n * As this is experimental, it is possible we move that correction step to live within the input class (to enable non-corrected translations in the future), say if we want to allow the camera to move outside of the globe's orbit\r\n *\r\n * Left mouse button: drag globe\r\n * Middle mouse button: tilt globe\r\n * Right mouse button: tilt globe\r\n *\r\n */\r\nexport class GeospatialCameraPointersInput extends OrbitCameraPointersInput {\r\n public camera: GeospatialCamera;\r\n\r\n private _initialPinchSquaredDistance: number = 0;\r\n private _pinchCentroid: Nullable<PointerTouch> = null;\r\n\r\n public override getClassName(): string {\r\n return \"GeospatialCameraPointersInput\";\r\n }\r\n\r\n public override onButtonDown(evt: IPointerEvent): void {\r\n this.camera.movement.activeInput = true;\r\n const scene = this.camera.getScene();\r\n switch (evt.button) {\r\n case 0: // Left button - drag/pan globe under cursor\r\n this.camera.movement.startDrag(scene.pointerX, scene.pointerY);\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n public override onTouch(point: Nullable<PointerTouch>, offsetX: number, offsetY: number): void {\r\n // Single finger touch (no button property) or left button (button 0) = drag\r\n const button = point?.button ?? 0; // Default to button 0 (drag) if undefined\r\n const scene = this.camera.getScene();\r\n switch (button) {\r\n case 0: // Left button / single touch - drag/pan globe under cursor\r\n this.camera.movement.handleDrag(scene.pointerX, scene.pointerY);\r\n break;\r\n case 1: // Middle button - tilt camera\r\n case 2: // Right button - tilt camera\r\n this._handleTilt(offsetX, offsetY);\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Move camera from multitouch (pinch) zoom distances.\r\n * Zooms towards the centroid (midpoint between the two fingers).\r\n * @param previousPinchSquaredDistance\r\n * @param pinchSquaredDistance\r\n */\r\n protected override _computePinchZoom(previousPinchSquaredDistance: number, pinchSquaredDistance: number): void {\r\n // Calculate zoom distance based on pinch delta\r\n const previousDistance = Math.sqrt(previousPinchSquaredDistance);\r\n const currentDistance = Math.sqrt(pinchSquaredDistance);\r\n const pinchDelta = currentDistance - previousDistance;\r\n\r\n // Try to zoom towards centroid if we have it\r\n if (this._pinchCentroid) {\r\n const scene = this.camera.getScene();\r\n const engine = scene.getEngine();\r\n const canvasRect = engine.getInputElementClientRect();\r\n\r\n if (canvasRect) {\r\n // Convert centroid from clientX/Y to canvas-relative coordinates (same as scene.pointerX/Y)\r\n const canvasX = this._pinchCentroid.x - canvasRect.left;\r\n const canvasY = this._pinchCentroid.y - canvasRect.top;\r\n\r\n // Pick at centroid\r\n const pickResult = scene.pick(canvasX, canvasY, this.camera.movement.pickPredicate);\r\n if (pickResult?.pickedPoint) {\r\n // Scale zoom by distance to picked point\r\n const distanceToPoint = this.camera.position.subtract(pickResult.pickedPoint).length();\r\n const zoomDistance = pinchDelta * distanceToPoint * 0.005;\r\n const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius, distanceToPoint);\r\n this.camera.zoomToPoint(pickResult.pickedPoint, clampedZoom);\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Fallback: scale zoom by camera radius along lookat vector\r\n const zoomDistance = pinchDelta * this.camera.radius * 0.005;\r\n const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius);\r\n this.camera.zoomAlongLookAt(clampedZoom);\r\n }\r\n\r\n /**\r\n * Move camera from multi touch panning positions.\r\n * In geospatialcamera, multi touch panning tilts the globe (whereas single touch will pan/drag it)\r\n * @param previousMultiTouchPanPosition\r\n * @param multiTouchPanPosition\r\n */\r\n protected override _computeMultiTouchPanning(previousMultiTouchPanPosition: Nullable<PointerTouch>, multiTouchPanPosition: Nullable<PointerTouch>): void {\r\n if (previousMultiTouchPanPosition && multiTouchPanPosition) {\r\n const moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;\r\n const moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;\r\n this._handleTilt(moveDeltaX, moveDeltaY);\r\n }\r\n }\r\n\r\n public override onDoubleTap(type: string): void {\r\n const pickResult = this.camera._scene.pick(this.camera._scene.pointerX, this.camera._scene.pointerY, this.camera.movement.pickPredicate);\r\n if (pickResult.pickedPoint) {\r\n void this.camera.flyToPointAsync(pickResult.pickedPoint);\r\n }\r\n }\r\n\r\n public override onMultiTouch(\r\n pointA: Nullable<PointerTouch>,\r\n pointB: Nullable<PointerTouch>,\r\n previousPinchSquaredDistance: number,\r\n pinchSquaredDistance: number,\r\n previousMultiTouchPanPosition: Nullable<PointerTouch>,\r\n multiTouchPanPosition: Nullable<PointerTouch>\r\n ): void {\r\n // Store centroid for use in _computePinchZoom (it's already calculated by parent)\r\n this._pinchCentroid = multiTouchPanPosition;\r\n\r\n // Reset on gesture end\r\n if (pinchSquaredDistance === 0 && multiTouchPanPosition === null) {\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);\r\n return;\r\n }\r\n\r\n // Track initial distance at gesture start for cumulative threshold detection\r\n if (this._initialPinchSquaredDistance === 0 && pinchSquaredDistance !== 0) {\r\n this._initialPinchSquaredDistance = pinchSquaredDistance;\r\n }\r\n\r\n // Use cumulative delta from gesture start for threshold detection (more forgiving than frame-to-frame)\r\n const cumulativeDelta = Math.abs(Math.sqrt(pinchSquaredDistance) - Math.sqrt(this._initialPinchSquaredDistance));\r\n this._shouldStartPinchZoom = this._twoFingerActivityCount < 20 && cumulativeDelta > this.camera.limits.pinchToPanMax;\r\n\r\n super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);\r\n }\r\n\r\n public override onButtonUp(_evt: IPointerEvent): void {\r\n this.camera.movement.stopDrag();\r\n this.camera.movement.activeInput = false;\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onButtonUp(_evt);\r\n }\r\n\r\n public override onLostFocus(): void {\r\n this._initialPinchSquaredDistance = 0;\r\n this._pinchCentroid = null;\r\n super.onLostFocus();\r\n }\r\n\r\n private _handleTilt(deltaX: number, deltaY: number): void {\r\n this.camera.movement.rotationAccumulatedPixels.y -= deltaX; // yaw - looking side to side\r\n this.camera.movement.rotationAccumulatedPixels.x -= deltaY; // pitch - look up towards sky / down towards ground\r\n }\r\n}\r\n"]}
@@ -1,3 +1,4 @@
1
+ import { Vector2 } from "../../Maths/math.vector.js";
1
2
  /**
2
3
  * Limits for geospatial camera
3
4
  */
@@ -9,6 +10,13 @@ export declare class GeospatialLimits {
9
10
  pitchMin: number;
10
11
  /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */
11
12
  pitchMax: number;
13
+ /**
14
+ * Controls how pitch is disabled as the camera zooms out.
15
+ * x = radius scale at which full pitch is allowed (e.g., 1.5 means 1.5 * planetRadius)
16
+ * y = radius scale at which pitch is fully disabled (forced to pitchMin)
17
+ * Set to undefined to disable this feature.
18
+ */
19
+ pitchDisabledRadiusScale?: Vector2;
12
20
  /** Gets the minimum yaw angle (rotation around up axis) */
13
21
  yawMin: number;
14
22
  /** Gets the maximum yaw angle (rotation around up axis) */
@@ -47,4 +55,12 @@ export declare class GeospatialLimits {
47
55
  * @returns The clamped zoom distance
48
56
  */
49
57
  clampZoomDistance(zoomDistance: number, currentRadius: number, distanceToTarget?: number): number;
58
+ /**
59
+ * Computes the effective maximum pitch based on the current camera radius.
60
+ * When pitchDisabledRadiusScale is set, pitch is interpolated from pitchMax to pitchMin
61
+ * as the camera zooms out from x*planetRadius to y*planetRadius.
62
+ * @param currentRadius The current camera radius (distance from planet center)
63
+ * @returns The effective maximum pitch angle
64
+ */
65
+ getEffectivePitchMax(currentRadius: number): number;
50
66
  }
@@ -1,4 +1,6 @@
1
1
  import { Epsilon } from "../../Maths/math.constants.js";
2
+ import { Vector2 } from "../../Maths/math.vector.js";
3
+ import { Clamp } from "../../Maths/math.scalar.functions.js";
2
4
  /**
3
5
  * Limits for geospatial camera
4
6
  */
@@ -12,7 +14,14 @@ export class GeospatialLimits {
12
14
  /** Gets the minimum pitch angle (angle from horizon) -- 0 means looking straight down at planet */
13
15
  this.pitchMin = Epsilon;
14
16
  /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */
15
- this.pitchMax = Math.PI / 2 - Epsilon;
17
+ this.pitchMax = Math.PI / 2 - 0.01;
18
+ /**
19
+ * Controls how pitch is disabled as the camera zooms out.
20
+ * x = radius scale at which full pitch is allowed (e.g., 1.5 means 1.5 * planetRadius)
21
+ * y = radius scale at which pitch is fully disabled (forced to pitchMin)
22
+ * Set to undefined to disable this feature.
23
+ */
24
+ this.pitchDisabledRadiusScale = new Vector2(2, 4);
16
25
  /** Gets the minimum yaw angle (rotation around up axis) */
17
26
  this.yawMin = -Infinity;
18
27
  /** Gets the maximum yaw angle (rotation around up axis) */
@@ -73,5 +82,33 @@ export class GeospatialLimits {
73
82
  return Math.max(zoomDistance, -Math.max(0, maxZoomOut));
74
83
  }
75
84
  }
85
+ /**
86
+ * Computes the effective maximum pitch based on the current camera radius.
87
+ * When pitchDisabledRadiusScale is set, pitch is interpolated from pitchMax to pitchMin
88
+ * as the camera zooms out from x*planetRadius to y*planetRadius.
89
+ * @param currentRadius The current camera radius (distance from planet center)
90
+ * @returns The effective maximum pitch angle
91
+ */
92
+ getEffectivePitchMax(currentRadius) {
93
+ if (!this.pitchDisabledRadiusScale) {
94
+ return this.pitchMax;
95
+ }
96
+ const fullPitchRadius = this.pitchDisabledRadiusScale.x * this._planetRadius;
97
+ const noPitchRadius = this.pitchDisabledRadiusScale.y * this._planetRadius;
98
+ if (currentRadius <= fullPitchRadius) {
99
+ // Full pitch allowed
100
+ return this.pitchMax;
101
+ }
102
+ else if (currentRadius >= noPitchRadius) {
103
+ // No pitch allowed
104
+ return this.pitchMin;
105
+ }
106
+ else {
107
+ // Interpolate between pitchMax and pitchMin
108
+ const t = (currentRadius - fullPitchRadius) / (noPitchRadius - fullPitchRadius);
109
+ const clampedT = Clamp(t, 0, 1);
110
+ return this.pitchMax * (1 - clampedT) + this.pitchMin * clampedT;
111
+ }
112
+ }
76
113
  }
77
114
  //# sourceMappingURL=geospatialLimits.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"geospatialLimits.js","sourceRoot":"","sources":["../../../../../dev/core/src/Cameras/Limits/geospatialLimits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAuBzB;;OAEG;IACH,YAAY,YAAoB;QAxBxB,eAAU,GAAW,EAAE,CAAC;QACxB,eAAU,GAAW,QAAQ,CAAC;QAEtC,mGAAmG;QAC5F,aAAQ,GAAW,OAAO,CAAC;QAElC,0FAA0F;QACnF,aAAQ,GAAW,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;QAEhD,2DAA2D;QACpD,WAAM,GAAW,CAAC,QAAQ,CAAC;QAElC,2DAA2D;QACpD,WAAM,GAAW,QAAQ,CAAC;QACjC;;;;WAIG;QACI,kBAAa,GAAW,EAAE,CAAC;QAM9B,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;IACtC,CAAC;IAED,IAAW,SAAS;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAW,SAAS,CAAC,KAAa;QAC9B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,IAAW,SAAS;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAW,SAAS,CAAC,KAAa;QAC9B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IAED,wFAAwF;IACxF,IAAW,YAAY,CAAC,KAAa;QACjC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;IAC/B,CAAC;IAED;;;;;;OAMG;IACI,iBAAiB,CAAC,YAAoB,EAAE,aAAqB,EAAE,gBAAyB;QAC3F,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACnB,8DAA8D;YAC9D,MAAM,SAAS,GAAG,CAAC,gBAAgB,IAAI,aAAa,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;YACxE,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACJ,uCAAuC;YACvC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC;YACnD,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;QAC5D,CAAC;IACL,CAAC;CACJ","sourcesContent":["import { Epsilon } from \"../../Maths/math.constants\";\r\n/**\r\n * Limits for geospatial camera\r\n */\r\nexport class GeospatialLimits {\r\n private _planetRadius: number;\r\n private _radiusMin: number = 10;\r\n private _radiusMax: number = Infinity;\r\n\r\n /** Gets the minimum pitch angle (angle from horizon) -- 0 means looking straight down at planet */\r\n public pitchMin: number = Epsilon;\r\n\r\n /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */\r\n public pitchMax: number = Math.PI / 2 - Epsilon;\r\n\r\n /** Gets the minimum yaw angle (rotation around up axis) */\r\n public yawMin: number = -Infinity;\r\n\r\n /** Gets the maximum yaw angle (rotation around up axis) */\r\n public yawMax: number = Infinity;\r\n /**\r\n * Defines the distance used to consider the camera in pan mode vs pinch/zoom.\r\n * Basically if your fingers moves away from more than this distance you will be considered\r\n * in pinch mode.\r\n */\r\n public pinchToPanMax: number = 20;\r\n\r\n /**\r\n * @param planetRadius The radius of the planet\r\n */\r\n constructor(planetRadius: number) {\r\n this._planetRadius = planetRadius;\r\n this.radiusMax = planetRadius * 4;\r\n }\r\n\r\n public get radiusMin(): number {\r\n return this._radiusMin;\r\n }\r\n\r\n /**\r\n * Sets the minimum radius\r\n */\r\n public set radiusMin(value: number) {\r\n this._radiusMin = value;\r\n }\r\n\r\n public get radiusMax(): number {\r\n return this._radiusMax;\r\n }\r\n\r\n /**\r\n * Sets the maximum radius\r\n */\r\n public set radiusMax(value: number) {\r\n this._radiusMax = value;\r\n }\r\n\r\n /**\r\n * Gets the planet radius used for altitude/radius conversions\r\n */\r\n public get planetRadius(): number {\r\n return this._planetRadius;\r\n }\r\n\r\n /** Sets the planet radius and updates the radius limits to maintain current altitude */\r\n public set planetRadius(value: number) {\r\n this._planetRadius = value;\r\n }\r\n\r\n /**\r\n * Clamps a zoom distance to respect the radius limits.\r\n * @param zoomDistance The requested zoom distance (positive = zoom in, negative = zoom out)\r\n * @param currentRadius The current camera radius\r\n * @param distanceToTarget Optional distance to the zoom target point (used for zoom-in clamping)\r\n * @returns The clamped zoom distance\r\n */\r\n public clampZoomDistance(zoomDistance: number, currentRadius: number, distanceToTarget?: number): number {\r\n if (zoomDistance > 0) {\r\n // Zooming IN - don't zoom past the surface or below radiusMin\r\n const maxZoomIn = (distanceToTarget ?? currentRadius) - this._radiusMin;\r\n return Math.min(zoomDistance, Math.max(0, maxZoomIn));\r\n } else {\r\n // Zooming OUT - don't exceed radiusMax\r\n const maxZoomOut = this._radiusMax - currentRadius;\r\n return Math.max(zoomDistance, -Math.max(0, maxZoomOut));\r\n }\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"geospatialLimits.js","sourceRoot":"","sources":["../../../../../dev/core/src/Cameras/Limits/geospatialLimits.ts"],"names":[],"mappings":"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","sourcesContent":["import { Epsilon } from \"../../Maths/math.constants\";\r\nimport { Vector2 } from \"../../Maths/math.vector\";\r\nimport { Clamp } from \"../../Maths/math.scalar.functions\";\r\n/**\r\n * Limits for geospatial camera\r\n */\r\nexport class GeospatialLimits {\r\n private _planetRadius: number;\r\n private _radiusMin: number = 10;\r\n private _radiusMax: number = Infinity;\r\n\r\n /** Gets the minimum pitch angle (angle from horizon) -- 0 means looking straight down at planet */\r\n public pitchMin: number = Epsilon;\r\n\r\n /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */\r\n public pitchMax: number = Math.PI / 2 - 0.01;\r\n\r\n /**\r\n * Controls how pitch is disabled as the camera zooms out.\r\n * x = radius scale at which full pitch is allowed (e.g., 1.5 means 1.5 * planetRadius)\r\n * y = radius scale at which pitch is fully disabled (forced to pitchMin)\r\n * Set to undefined to disable this feature.\r\n */\r\n public pitchDisabledRadiusScale?: Vector2 = new Vector2(2, 4);\r\n\r\n /** Gets the minimum yaw angle (rotation around up axis) */\r\n public yawMin: number = -Infinity;\r\n\r\n /** Gets the maximum yaw angle (rotation around up axis) */\r\n public yawMax: number = Infinity;\r\n /**\r\n * Defines the distance used to consider the camera in pan mode vs pinch/zoom.\r\n * Basically if your fingers moves away from more than this distance you will be considered\r\n * in pinch mode.\r\n */\r\n public pinchToPanMax: number = 20;\r\n\r\n /**\r\n * @param planetRadius The radius of the planet\r\n */\r\n constructor(planetRadius: number) {\r\n this._planetRadius = planetRadius;\r\n this.radiusMax = planetRadius * 4;\r\n }\r\n\r\n public get radiusMin(): number {\r\n return this._radiusMin;\r\n }\r\n\r\n /**\r\n * Sets the minimum radius\r\n */\r\n public set radiusMin(value: number) {\r\n this._radiusMin = value;\r\n }\r\n\r\n public get radiusMax(): number {\r\n return this._radiusMax;\r\n }\r\n\r\n /**\r\n * Sets the maximum radius\r\n */\r\n public set radiusMax(value: number) {\r\n this._radiusMax = value;\r\n }\r\n\r\n /**\r\n * Gets the planet radius used for altitude/radius conversions\r\n */\r\n public get planetRadius(): number {\r\n return this._planetRadius;\r\n }\r\n\r\n /** Sets the planet radius and updates the radius limits to maintain current altitude */\r\n public set planetRadius(value: number) {\r\n this._planetRadius = value;\r\n }\r\n\r\n /**\r\n * Clamps a zoom distance to respect the radius limits.\r\n * @param zoomDistance The requested zoom distance (positive = zoom in, negative = zoom out)\r\n * @param currentRadius The current camera radius\r\n * @param distanceToTarget Optional distance to the zoom target point (used for zoom-in clamping)\r\n * @returns The clamped zoom distance\r\n */\r\n public clampZoomDistance(zoomDistance: number, currentRadius: number, distanceToTarget?: number): number {\r\n if (zoomDistance > 0) {\r\n // Zooming IN - don't zoom past the surface or below radiusMin\r\n const maxZoomIn = (distanceToTarget ?? currentRadius) - this._radiusMin;\r\n return Math.min(zoomDistance, Math.max(0, maxZoomIn));\r\n } else {\r\n // Zooming OUT - don't exceed radiusMax\r\n const maxZoomOut = this._radiusMax - currentRadius;\r\n return Math.max(zoomDistance, -Math.max(0, maxZoomOut));\r\n }\r\n }\r\n\r\n /**\r\n * Computes the effective maximum pitch based on the current camera radius.\r\n * When pitchDisabledRadiusScale is set, pitch is interpolated from pitchMax to pitchMin\r\n * as the camera zooms out from x*planetRadius to y*planetRadius.\r\n * @param currentRadius The current camera radius (distance from planet center)\r\n * @returns The effective maximum pitch angle\r\n */\r\n public getEffectivePitchMax(currentRadius: number): number {\r\n if (!this.pitchDisabledRadiusScale) {\r\n return this.pitchMax;\r\n }\r\n\r\n const fullPitchRadius = this.pitchDisabledRadiusScale.x * this._planetRadius;\r\n const noPitchRadius = this.pitchDisabledRadiusScale.y * this._planetRadius;\r\n\r\n if (currentRadius <= fullPitchRadius) {\r\n // Full pitch allowed\r\n return this.pitchMax;\r\n } else if (currentRadius >= noPitchRadius) {\r\n // No pitch allowed\r\n return this.pitchMin;\r\n } else {\r\n // Interpolate between pitchMax and pitchMin\r\n const t = (currentRadius - fullPitchRadius) / (noPitchRadius - fullPitchRadius);\r\n const clampedT = Clamp(t, 0, 1);\r\n return this.pitchMax * (1 - clampedT) + this.pitchMin * clampedT;\r\n }\r\n }\r\n}\r\n"]}
@@ -9,19 +9,21 @@ import { GeospatialLimits } from "./Limits/geospatialLimits.js";
9
9
  import { GeospatialCameraMovement } from "./geospatialCameraMovement.js";
10
10
  import type { IVector3Like } from "../Maths/math.like.js";
11
11
  import type { EasingFunction } from "../Animations/easing.js";
12
- type CameraOptions = {
12
+ export type GeospatialCameraOptions = {
13
+ /**
14
+ * Radius of the planet being orbited
15
+ */
13
16
  planetRadius: number;
17
+ /**
18
+ * If supplied, will be used by the movement class when picking the globe. Can later update camera.movement.pickPredicate directly
19
+ */
20
+ pickPredicate?: MeshPredicate;
14
21
  };
15
22
  /**
16
- * @experimental
17
- * This camera's movements are limited to a camera orbiting a globe, and as the API evolves it will introduce conversions between cartesian coordinates and true lat/long/alt
18
- *
19
- * Please note this is marked as experimental and the API (including the constructor!) will change until we remove that flag
23
+ * Camera equipped to orbit a spherical planet centered at world origin
20
24
  */
21
25
  export declare class GeospatialCamera extends Camera {
22
26
  inputs: GeospatialCameraInputsManager;
23
- /** If supplied, will be used when picking the globe */
24
- pickPredicate?: MeshPredicate;
25
27
  /** Movement controller that turns input pixelDeltas into currentFrameDeltas used by camera*/
26
28
  readonly movement: GeospatialCameraMovement;
27
29
  private _tempPosition;
@@ -38,7 +40,7 @@ export declare class GeospatialCamera extends Camera {
38
40
  perFrameCollisionOffset: Vector3;
39
41
  /** Enable or disable collision checking for this camera. Default is false. */
40
42
  checkCollisions: boolean;
41
- constructor(name: string, scene: Scene, options: CameraOptions, pickPredicate?: MeshPredicate);
43
+ constructor(name: string, scene: Scene, options: GeospatialCameraOptions);
42
44
  private _center;
43
45
  /** The point on the globe that we are anchoring around. If no alternate rotation point is supplied, this will represent the center of screen*/
44
46
  get center(): Vector3;
@@ -169,4 +171,3 @@ export declare function ComputeLookAtFromYawPitchToRef(yaw: number, pitch: numbe
169
171
  * @returns The result Vector2
170
172
  */
171
173
  export declare function ComputeYawPitchFromLookAtToRef(lookAt: Vector3, center: Vector3, useRightHandedSystem: boolean, currentYaw: number, result: Vector2): Vector2;
172
- export {};
@@ -8,13 +8,10 @@ import { Vector3CopyToRef, Vector3Distance, Vector3Dot, Vector3SubtractToRef } f
8
8
  import { Clamp, NormalizeRadians } from "../Maths/math.scalar.functions.js";
9
9
  import { InterpolatingBehavior } from "../Behaviors/Cameras/interpolatingBehavior.js";
10
10
  /**
11
- * @experimental
12
- * This camera's movements are limited to a camera orbiting a globe, and as the API evolves it will introduce conversions between cartesian coordinates and true lat/long/alt
13
- *
14
- * Please note this is marked as experimental and the API (including the constructor!) will change until we remove that flag
11
+ * Camera equipped to orbit a spherical planet centered at world origin
15
12
  */
16
13
  export class GeospatialCamera extends Camera {
17
- constructor(name, scene, options, pickPredicate) {
14
+ constructor(name, scene, options) {
18
15
  super(name, new Vector3(), scene);
19
16
  // Temp vars
20
17
  this._tempPosition = new Vector3();
@@ -40,8 +37,7 @@ export class GeospatialCamera extends Camera {
40
37
  this._resetToDefault(this._limits);
41
38
  this._flyingBehavior = new InterpolatingBehavior();
42
39
  this.addBehavior(this._flyingBehavior);
43
- this.movement = new GeospatialCameraMovement(scene, this._limits, this.position, this.center, this._lookAtVector, pickPredicate, this._flyingBehavior);
44
- this.pickPredicate = pickPredicate;
40
+ this.movement = new GeospatialCameraMovement(scene, this._limits, this.position, this.center, this._lookAtVector, options.pickPredicate, this._flyingBehavior);
45
41
  this.inputs = new GeospatialCameraInputsManager(this);
46
42
  this.inputs.addMouse().addMouseWheel().addKeyboard();
47
43
  }
@@ -100,7 +96,8 @@ export class GeospatialCamera extends Camera {
100
96
  _checkLimits() {
101
97
  const limits = this.limits;
102
98
  this._yaw = Clamp(this._yaw, limits.yawMin, limits.yawMax);
103
- this._pitch = Clamp(this._pitch, limits.pitchMin, limits.pitchMax);
99
+ const effectivePitchMax = limits.getEffectivePitchMax(this._radius);
100
+ this._pitch = Clamp(this._pitch, limits.pitchMin, effectivePitchMax);
104
101
  this._radius = Clamp(this._radius, limits.radiusMin, limits.radiusMax);
105
102
  ClampCenterFromPolesInPlace(this._center);
106
103
  }