@onerjs/core 8.36.2 → 8.36.4

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.
Files changed (60) hide show
  1. package/Animations/animation.d.ts +4 -0
  2. package/Animations/animation.js +7 -0
  3. package/Animations/animation.js.map +1 -1
  4. package/Behaviors/Cameras/geospatialClippingBehavior.d.ts +40 -0
  5. package/Behaviors/Cameras/geospatialClippingBehavior.js +80 -0
  6. package/Behaviors/Cameras/geospatialClippingBehavior.js.map +1 -0
  7. package/Behaviors/Cameras/index.d.ts +2 -0
  8. package/Behaviors/Cameras/index.js +2 -0
  9. package/Behaviors/Cameras/index.js.map +1 -1
  10. package/Cameras/Inputs/geospatialCameraPointersInput.js +4 -2
  11. package/Cameras/Inputs/geospatialCameraPointersInput.js.map +1 -1
  12. package/Cameras/Limits/geospatialLimits.d.ts +9 -1
  13. package/Cameras/Limits/geospatialLimits.js +23 -3
  14. package/Cameras/Limits/geospatialLimits.js.map +1 -1
  15. package/Cameras/geospatialCamera.d.ts +23 -0
  16. package/Cameras/geospatialCamera.js +109 -34
  17. package/Cameras/geospatialCamera.js.map +1 -1
  18. package/Cameras/geospatialCameraMovement.js +1 -0
  19. package/Cameras/geospatialCameraMovement.js.map +1 -1
  20. package/Debug/debugLayer.js +11 -3
  21. package/Debug/debugLayer.js.map +1 -1
  22. package/Engines/abstractEngine.js +2 -2
  23. package/Engines/abstractEngine.js.map +1 -1
  24. package/IAssetContainer.d.ts +5 -0
  25. package/IAssetContainer.js.map +1 -1
  26. package/Layers/highlightLayer.d.ts +5 -0
  27. package/Layers/highlightLayer.js +13 -0
  28. package/Layers/highlightLayer.js.map +1 -1
  29. package/Lights/Clustered/clusteredLightContainer.js +1 -1
  30. package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
  31. package/Loading/Plugins/babylonFileLoader.js +2 -0
  32. package/Loading/Plugins/babylonFileLoader.js.map +1 -1
  33. package/Materials/effectRenderer.d.ts +2 -1
  34. package/Materials/effectRenderer.js +2 -12
  35. package/Materials/effectRenderer.js.map +1 -1
  36. package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +6 -1
  37. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +21 -17
  38. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
  39. package/Particles/Node/Blocks/index.d.ts +3 -0
  40. package/Particles/Node/Blocks/index.js +3 -0
  41. package/Particles/Node/Blocks/index.js.map +1 -1
  42. package/Particles/Node/Blocks/particleNLerpBlock.d.ts +35 -0
  43. package/Particles/Node/Blocks/particleNLerpBlock.js +97 -0
  44. package/Particles/Node/Blocks/particleNLerpBlock.js.map +1 -0
  45. package/Particles/Node/Blocks/particleSmoothStepBlock.d.ts +34 -0
  46. package/Particles/Node/Blocks/particleSmoothStepBlock.js +91 -0
  47. package/Particles/Node/Blocks/particleSmoothStepBlock.js.map +1 -0
  48. package/Particles/Node/Blocks/particleStepBlock.d.ts +30 -0
  49. package/Particles/Node/Blocks/particleStepBlock.js +84 -0
  50. package/Particles/Node/Blocks/particleStepBlock.js.map +1 -0
  51. package/Particles/solidParticleSystem.d.ts +3 -0
  52. package/Particles/solidParticleSystem.js +7 -4
  53. package/Particles/solidParticleSystem.js.map +1 -1
  54. package/Sprites/spriteManager.d.ts +3 -0
  55. package/Sprites/spriteManager.js +9 -0
  56. package/Sprites/spriteManager.js.map +1 -1
  57. package/assetContainer.d.ts +5 -0
  58. package/assetContainer.js +32 -0
  59. package/assetContainer.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ import type { Behavior } from "../../Behaviors/behavior.js";
2
+ import type { GeospatialCamera } from "../../Cameras/geospatialCamera.js";
3
+ import type { Nullable } from "../../types.js";
4
+ /**
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.
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.
10
+ * The far plane is calculated based on the visible horizon distance.
11
+ */
12
+ export declare class GeospatialClippingBehavior implements Behavior<GeospatialCamera> {
13
+ /**
14
+ * Gets the name of the behavior.
15
+ */
16
+ get name(): string;
17
+ private _attachedCamera;
18
+ private _onBeforeRenderObserver;
19
+ /**
20
+ * Gets the attached camera.
21
+ */
22
+ get attachedNode(): Nullable<GeospatialCamera>;
23
+ /**
24
+ * Initializes the behavior.
25
+ */
26
+ init(): void;
27
+ /**
28
+ * Attaches the behavior to its geospatial camera.
29
+ * @param camera Defines the camera to attach the behavior to
30
+ */
31
+ attach(camera: GeospatialCamera): void;
32
+ /**
33
+ * Detaches the behavior from its current geospatial camera.
34
+ */
35
+ detach(): void;
36
+ /**
37
+ * Updates the camera's near and far clip planes based on altitude and viewing angle.
38
+ */
39
+ private _updateCameraClipPlanes;
40
+ }
@@ -0,0 +1,80 @@
1
+ /**
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.
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.
7
+ * The far plane is calculated based on the visible horizon distance.
8
+ */
9
+ export class GeospatialClippingBehavior {
10
+ constructor() {
11
+ this._attachedCamera = null;
12
+ this._onBeforeRenderObserver = null;
13
+ }
14
+ /**
15
+ * Gets the name of the behavior.
16
+ */
17
+ get name() {
18
+ return "GeospatialClipping";
19
+ }
20
+ /**
21
+ * Gets the attached camera.
22
+ */
23
+ get attachedNode() {
24
+ return this._attachedCamera;
25
+ }
26
+ /**
27
+ * Initializes the behavior.
28
+ */
29
+ init() {
30
+ // Do nothing
31
+ }
32
+ /**
33
+ * Attaches the behavior to its geospatial camera.
34
+ * @param camera Defines the camera to attach the behavior to
35
+ */
36
+ attach(camera) {
37
+ this._attachedCamera = camera;
38
+ const scene = camera.getScene();
39
+ this._onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
40
+ this._updateCameraClipPlanes();
41
+ });
42
+ }
43
+ /**
44
+ * Detaches the behavior from its current geospatial camera.
45
+ */
46
+ detach() {
47
+ if (this._attachedCamera) {
48
+ const scene = this._attachedCamera.getScene();
49
+ if (this._onBeforeRenderObserver) {
50
+ scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
51
+ this._onBeforeRenderObserver = null;
52
+ }
53
+ }
54
+ this._attachedCamera = null;
55
+ }
56
+ /**
57
+ * Updates the camera's near and far clip planes based on altitude and viewing angle.
58
+ */
59
+ _updateCameraClipPlanes() {
60
+ const camera = this._attachedCamera;
61
+ if (!camera) {
62
+ return;
63
+ }
64
+ 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);
75
+ // Far plane: see to the horizon and beyond
76
+ const horizonDist = Math.sqrt(2 * planetRadius * altitude + altitude * altitude);
77
+ camera.maxZ = horizonDist + planetRadius * 0.1;
78
+ }
79
+ }
80
+ //# sourceMappingURL=geospatialClippingBehavior.js.map
@@ -0,0 +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,3 +1,5 @@
1
1
  export * from "./autoRotationBehavior.js";
2
2
  export * from "./bouncingBehavior.js";
3
3
  export * from "./framingBehavior.js";
4
+ export * from "./interpolatingBehavior.js";
5
+ export * from "./geospatialClippingBehavior.js";
@@ -1,4 +1,6 @@
1
1
  export * from "./autoRotationBehavior.js";
2
2
  export * from "./bouncingBehavior.js";
3
3
  export * from "./framingBehavior.js";
4
+ export * from "./interpolatingBehavior.js";
5
+ export * from "./geospatialClippingBehavior.js";
4
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../dev/core/src/Behaviors/Cameras/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC","sourcesContent":["export * from \"./autoRotationBehavior\";\r\nexport * from \"./bouncingBehavior\";\r\nexport * from \"./framingBehavior\";\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../dev/core/src/Behaviors/Cameras/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC","sourcesContent":["export * from \"./autoRotationBehavior\";\r\nexport * from \"./bouncingBehavior\";\r\nexport * from \"./framingBehavior\";\r\nexport * from \"./interpolatingBehavior\";\r\nexport * from \"./geospatialClippingBehavior\";\r\n"]}
@@ -72,14 +72,16 @@ export class GeospatialCameraPointersInput extends OrbitCameraPointersInput {
72
72
  // Scale zoom by distance to picked point
73
73
  const distanceToPoint = this.camera.position.subtract(pickResult.pickedPoint).length();
74
74
  const zoomDistance = pinchDelta * distanceToPoint * 0.005;
75
- this.camera.zoomToPoint(pickResult.pickedPoint, zoomDistance);
75
+ const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius, distanceToPoint);
76
+ this.camera.zoomToPoint(pickResult.pickedPoint, clampedZoom);
76
77
  return;
77
78
  }
78
79
  }
79
80
  }
80
81
  // Fallback: scale zoom by camera radius along lookat vector
81
82
  const zoomDistance = pinchDelta * this.camera.radius * 0.005;
82
- this.camera.zoomAlongLookAt(zoomDistance);
83
+ const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius);
84
+ this.camera.zoomAlongLookAt(clampedZoom);
83
85
  }
84
86
  /**
85
87
  * Move camera from multi touch panning positions.
@@ -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 this.camera.zoomToPoint(pickResult.pickedPoint, zoomDistance);\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 this.camera.zoomAlongLookAt(zoomDistance);\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.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"]}
@@ -7,7 +7,7 @@ export declare class GeospatialLimits {
7
7
  private _radiusMax;
8
8
  /** Gets the minimum pitch angle (angle from horizon) -- 0 means looking straight down at planet */
9
9
  pitchMin: number;
10
- /** Gets the maximum pitch angle (angle from horizon) -- Pi/1 means looking at horizon */
10
+ /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */
11
11
  pitchMax: number;
12
12
  /** Gets the minimum yaw angle (rotation around up axis) */
13
13
  yawMin: number;
@@ -39,4 +39,12 @@ export declare class GeospatialLimits {
39
39
  get planetRadius(): number;
40
40
  /** Sets the planet radius and updates the radius limits to maintain current altitude */
41
41
  set planetRadius(value: number);
42
+ /**
43
+ * Clamps a zoom distance to respect the radius limits.
44
+ * @param zoomDistance The requested zoom distance (positive = zoom in, negative = zoom out)
45
+ * @param currentRadius The current camera radius
46
+ * @param distanceToTarget Optional distance to the zoom target point (used for zoom-in clamping)
47
+ * @returns The clamped zoom distance
48
+ */
49
+ clampZoomDistance(zoomDistance: number, currentRadius: number, distanceToTarget?: number): number;
42
50
  }
@@ -7,12 +7,12 @@ export class GeospatialLimits {
7
7
  * @param planetRadius The radius of the planet
8
8
  */
9
9
  constructor(planetRadius) {
10
- this._radiusMin = Epsilon;
10
+ this._radiusMin = 10;
11
11
  this._radiusMax = Infinity;
12
12
  /** Gets the minimum pitch angle (angle from horizon) -- 0 means looking straight down at planet */
13
13
  this.pitchMin = Epsilon;
14
- /** Gets the maximum pitch angle (angle from horizon) -- Pi/1 means looking at horizon */
15
- this.pitchMax = Math.PI / 2;
14
+ /** Gets the maximum pitch angle (angle from horizon) -- Pi/2 means looking at horizon */
15
+ this.pitchMax = Math.PI / 2 - Epsilon;
16
16
  /** Gets the minimum yaw angle (rotation around up axis) */
17
17
  this.yawMin = -Infinity;
18
18
  /** Gets the maximum yaw angle (rotation around up axis) */
@@ -24,6 +24,7 @@ export class GeospatialLimits {
24
24
  */
25
25
  this.pinchToPanMax = 20;
26
26
  this._planetRadius = planetRadius;
27
+ this.radiusMax = planetRadius * 4;
27
28
  }
28
29
  get radiusMin() {
29
30
  return this._radiusMin;
@@ -53,5 +54,24 @@ export class GeospatialLimits {
53
54
  set planetRadius(value) {
54
55
  this._planetRadius = value;
55
56
  }
57
+ /**
58
+ * Clamps a zoom distance to respect the radius limits.
59
+ * @param zoomDistance The requested zoom distance (positive = zoom in, negative = zoom out)
60
+ * @param currentRadius The current camera radius
61
+ * @param distanceToTarget Optional distance to the zoom target point (used for zoom-in clamping)
62
+ * @returns The clamped zoom distance
63
+ */
64
+ clampZoomDistance(zoomDistance, currentRadius, distanceToTarget) {
65
+ if (zoomDistance > 0) {
66
+ // Zooming IN - don't zoom past the surface or below radiusMin
67
+ const maxZoomIn = (distanceToTarget ?? currentRadius) - this._radiusMin;
68
+ return Math.min(zoomDistance, Math.max(0, maxZoomIn));
69
+ }
70
+ else {
71
+ // Zooming OUT - don't exceed radiusMax
72
+ const maxZoomOut = this._radiusMax - currentRadius;
73
+ return Math.max(zoomDistance, -Math.max(0, maxZoomOut));
74
+ }
75
+ }
56
76
  }
57
77
  //# 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,OAAO,CAAC;QAC7B,eAAU,GAAW,QAAQ,CAAC;QAEtC,mGAAmG;QAC5F,aAAQ,GAAW,OAAO,CAAC;QAElC,0FAA0F;QACnF,aAAQ,GAAW,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QAEtC,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;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;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 = Epsilon;\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/1 means looking at horizon */\r\n public pitchMax: number = Math.PI / 2;\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 }\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"]}
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,5 +1,6 @@
1
1
  import { GeospatialCameraInputsManager } from "./geospatialCameraInputsManager.js";
2
2
  import { Vector3, Matrix } from "../Maths/math.vector.js";
3
+ import type { Vector2 } from "../Maths/math.vector.js";
3
4
  import { Camera } from "./camera.js";
4
5
  import type { Scene } from "../scene.js";
5
6
  import type { MeshPredicate } from "../Culling/ray.core.js";
@@ -146,4 +147,26 @@ export declare class GeospatialCamera extends Camera {
146
147
  attachControl(noPreventDefault?: boolean): void;
147
148
  detachControl(): void;
148
149
  }
150
+ /**
151
+ * Compute the lookAt direction vector from yaw and pitch angles at a given center point.
152
+ * This is the forward formula used by GeospatialCamera._setOrientation.
153
+ * @param yaw - The yaw angle in radians (0 = north, π/2 = east)
154
+ * @param pitch - The pitch angle in radians (0 = looking at planet center, π/2 = looking at horizon)
155
+ * @param center - The center point on the globe
156
+ * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system
157
+ * @param result - The vector to store the result in
158
+ * @returns The normalized lookAt direction vector (same as result)
159
+ */
160
+ export declare function ComputeLookAtFromYawPitchToRef(yaw: number, pitch: number, center: Vector3, useRightHandedSystem: boolean, result: Vector3): Vector3;
161
+ /**
162
+ * Given a lookAt direction and center, compute the yaw and pitch angles that would produce that lookAt.
163
+ * This is the inverse of ComputeLookAtFromYawPitchToRef.
164
+ * @param lookAt - The normalized lookAt direction vector
165
+ * @param center - The center point on the globe
166
+ * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system
167
+ * @param currentYaw - The current yaw value to use as fallback when pitch is near 0 (looking straight down/up)
168
+ * @param result - The Vector2 to store the result in (x = yaw, y = pitch)
169
+ * @returns The result Vector2
170
+ */
171
+ export declare function ComputeYawPitchFromLookAtToRef(lookAt: Vector3, center: Vector3, useRightHandedSystem: boolean, currentYaw: number, result: Vector2): Vector2;
149
172
  export {};
@@ -1,5 +1,5 @@
1
1
  import { GeospatialCameraInputsManager } from "./geospatialCameraInputsManager.js";
2
- import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector.js";
2
+ import { Vector3, Matrix, TmpVectors, Quaternion } from "../Maths/math.vector.js";
3
3
  import { Epsilon } from "../Maths/math.constants.js";
4
4
  import { Camera } from "./camera.js";
5
5
  import { GeospatialLimits } from "./Limits/geospatialLimits.js";
@@ -114,26 +114,26 @@ export class GeospatialCamera extends Camera {
114
114
  this._checkLimits();
115
115
  // Refresh local basis at center (treat these as read-only for the whole call)
116
116
  ComputeLocalBasisToRefs(this._center, this._tempEast, this._tempNorth, this._tempUp);
117
- // Trig
118
- const yawScale = this._scene.useRightHandedSystem ? 1 : -1;
119
- const cosYaw = Math.cos(this._yaw * yawScale);
120
- const sinYaw = Math.sin(this._yaw * yawScale);
121
- const sinPitch = Math.sin(this._pitch); // horizontal weight
122
- const cosPitch = Math.cos(this._pitch); // vertical weight (toward center)
123
- // Temps
124
- const horiz = TmpVectors.Vector3[0];
125
- const t1 = TmpVectors.Vector3[1];
126
- const t2 = TmpVectors.Vector3[2];
127
- const right = TmpVectors.Vector3[3];
128
- // horizontalDirection = North*cosYaw + East*sinYaw (avoids mutating _temp basis vectors)
129
- horiz.copyFrom(this._tempNorth).scaleInPlace(cosYaw).addInPlace(t1.copyFrom(this._tempEast).scaleInPlace(sinYaw));
130
- // look = horiz*sinPitch - Up*cosPitch
131
- this._lookAtVector.copyFrom(horiz).scaleInPlace(sinPitch).addInPlace(t2.copyFrom(this._tempUp).scaleInPlace(-cosPitch)).normalize(); // keep it unit
117
+ // Compute lookAt from yaw/pitch
118
+ ComputeLookAtFromYawPitchToRef(this._yaw, this._pitch, this._center, this._scene.useRightHandedSystem, this._lookAtVector);
132
119
  // Build an orthonormal up aligned with geocentric Up
133
- // right = normalize(cross(upRef, look))
120
+ // When looking straight down (pitch ≈ 0), lookAt is parallel to Up, so use the horizontal direction as the camera's up.
121
+ const right = TmpVectors.Vector3[10];
134
122
  Vector3.CrossToRef(this._tempUp, this._lookAtVector, right);
123
+ if (right.lengthSquared() < Epsilon) {
124
+ // Looking straight down (or up) - use quaternion rotation to compute horiz
125
+ const horiz = TmpVectors.Vector3[11];
126
+ const yawScale = this._scene.useRightHandedSystem ? 1 : -1;
127
+ const yawQuat = TmpVectors.Quaternion[1];
128
+ Quaternion.RotationAxisToRef(this._tempUp, this._yaw * yawScale, yawQuat);
129
+ this._tempNorth.rotateByQuaternionToRef(yawQuat, horiz);
130
+ // right = cross(horiz, lookAt)
131
+ Vector3.CrossToRef(horiz, this._lookAtVector, right);
132
+ }
133
+ right.normalize();
135
134
  // up = normalize(cross(look, right))
136
135
  Vector3.CrossToRef(this._lookAtVector, right, this.upVector);
136
+ this.upVector.normalize();
137
137
  // Position = center - look * radius (preserve unit look)
138
138
  this._tempVect.copyFrom(this._lookAtVector).scaleInPlace(-this._radius);
139
139
  this._tempPosition.copyFrom(this._center).addInPlace(this._tempVect);
@@ -334,21 +334,8 @@ export class GeospatialCamera extends Camera {
334
334
  if (Math.abs(zoomDelta) < Epsilon) {
335
335
  return 0;
336
336
  }
337
- if (zoomDelta > 0) {
338
- // Zooming IN - respect radiusMin as distance to surface
339
- if (pickedPoint) {
340
- const pickDistance = Vector3Distance(this._position, pickedPoint);
341
- // Don't zoom past the picked surface point + radiusMin
342
- const maxZoomToSurface = pickDistance - this.limits.radiusMin;
343
- return Math.min(zoomDelta, Math.max(0, maxZoomToSurface));
344
- }
345
- return zoomDelta;
346
- }
347
- else {
348
- // Zooming OUT - respect radiusMax
349
- const maxZoomOut = this.limits.radiusMax - this._radius;
350
- return Math.max(zoomDelta, -Math.max(0, maxZoomOut));
351
- }
337
+ const distanceToTarget = pickedPoint ? Vector3Distance(this._position, pickedPoint) : undefined;
338
+ return this.limits.clampZoomDistance(zoomDelta, this._radius, distanceToTarget);
352
339
  }
353
340
  zoomToPoint(targetPoint, distance) {
354
341
  const newRadius = this._getCenterAndRadiusFromZoomToPoint(targetPoint, distance, this._tempCenter);
@@ -398,8 +385,16 @@ export class GeospatialCamera extends Camera {
398
385
  const dotProduct = Vector3Dot(this._lookAtVector, centerToOrigin);
399
386
  // Only update if the center is looking toward the origin (dot product > 0) to avoid a center on the opposite side of globe
400
387
  if (dotProduct > 0) {
401
- const newRadius = Vector3Distance(this.position, newCenter.pickedPoint);
402
- this._setOrientation(this._yaw, this._pitch, newRadius, newCenter.pickedPoint);
388
+ // Compute the new radius as distance from camera position to new center
389
+ const newRadius = Vector3Distance(this._position, newCenter.pickedPoint);
390
+ // Only update if the new center is in front of the camera
391
+ if (newRadius > Epsilon) {
392
+ // Compute yaw/pitch that correspond to current lookAt at new center
393
+ const yawPitch = TmpVectors.Vector2[0];
394
+ ComputeYawPitchFromLookAtToRef(this._lookAtVector, newCenter.pickedPoint, this._scene.useRightHandedSystem, this._yaw, yawPitch);
395
+ // Call _setOrientation with the computed yaw/pitch and new center
396
+ this._setOrientation(yawPitch.x, yawPitch.y, newRadius, newCenter.pickedPoint);
397
+ }
403
398
  }
404
399
  }
405
400
  }
@@ -437,4 +432,84 @@ export class GeospatialCamera extends Camera {
437
432
  this.inputs.detachElement();
438
433
  }
439
434
  }
435
+ /**
436
+ * Compute the lookAt direction vector from yaw and pitch angles at a given center point.
437
+ * This is the forward formula used by GeospatialCamera._setOrientation.
438
+ * @param yaw - The yaw angle in radians (0 = north, π/2 = east)
439
+ * @param pitch - The pitch angle in radians (0 = looking at planet center, π/2 = looking at horizon)
440
+ * @param center - The center point on the globe
441
+ * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system
442
+ * @param result - The vector to store the result in
443
+ * @returns The normalized lookAt direction vector (same as result)
444
+ */
445
+ export function ComputeLookAtFromYawPitchToRef(yaw, pitch, center, useRightHandedSystem, result) {
446
+ const east = TmpVectors.Vector3[0];
447
+ const north = TmpVectors.Vector3[1];
448
+ const up = TmpVectors.Vector3[2];
449
+ ComputeLocalBasisToRefs(center, east, north, up);
450
+ const sinPitch = Math.sin(pitch);
451
+ const cosPitch = Math.cos(pitch);
452
+ // Use quaternion rotation to compute horiz = rotate(north, up, yaw * yawScale)
453
+ const yawScale = useRightHandedSystem ? 1 : -1;
454
+ const yawQuat = TmpVectors.Quaternion[0];
455
+ Quaternion.RotationAxisToRef(up, yaw * yawScale, yawQuat);
456
+ const horiz = TmpVectors.Vector3[3];
457
+ north.rotateByQuaternionToRef(yawQuat, horiz);
458
+ // lookAt = horiz * sinPitch - up * cosPitch
459
+ const t2 = TmpVectors.Vector3[4];
460
+ result.copyFrom(horiz).scaleInPlace(sinPitch).addInPlace(t2.copyFrom(up).scaleInPlace(-cosPitch));
461
+ return result.normalize();
462
+ }
463
+ /**
464
+ * Given a lookAt direction and center, compute the yaw and pitch angles that would produce that lookAt.
465
+ * This is the inverse of ComputeLookAtFromYawPitchToRef.
466
+ * @param lookAt - The normalized lookAt direction vector
467
+ * @param center - The center point on the globe
468
+ * @param useRightHandedSystem - Whether the scene uses a right-handed coordinate system
469
+ * @param currentYaw - The current yaw value to use as fallback when pitch is near 0 (looking straight down/up)
470
+ * @param result - The Vector2 to store the result in (x = yaw, y = pitch)
471
+ * @returns The result Vector2
472
+ */
473
+ export function ComputeYawPitchFromLookAtToRef(lookAt, center, useRightHandedSystem, currentYaw, result) {
474
+ // Compute local basis at center
475
+ const east = TmpVectors.Vector3[6];
476
+ const north = TmpVectors.Vector3[7];
477
+ const up = TmpVectors.Vector3[8];
478
+ ComputeLocalBasisToRefs(center, east, north, up);
479
+ // lookAt = horiz*sinPitch - up*cosPitch
480
+ // where horiz = rotate(north, up, yaw * yawScale) via quaternion
481
+ //
482
+ // The vertical component of lookAt (along up) gives us cosPitch:
483
+ // lookAt · up = -cosPitch
484
+ const lookDotUp = Vector3Dot(lookAt, up);
485
+ const cosPitch = -lookDotUp;
486
+ // Clamp cosPitch to valid range to avoid NaN from acos
487
+ const clampedCosPitch = Clamp(cosPitch, -1, 1);
488
+ const pitch = Math.acos(clampedCosPitch);
489
+ // The horizontal component gives us yaw
490
+ // lookHorizontal = lookAt + up*cosPitch = horiz*sinPitch
491
+ const lookHorizontal = TmpVectors.Vector3[9];
492
+ const scaledUp = TmpVectors.Vector3[10];
493
+ scaledUp.copyFrom(up).scaleInPlace(cosPitch);
494
+ lookHorizontal.copyFrom(lookAt).addInPlace(scaledUp);
495
+ const sinPitch = Math.sin(pitch);
496
+ if (Math.abs(sinPitch) < Epsilon) {
497
+ // Looking straight down or up, yaw is undefined - keep current
498
+ result.x = currentYaw;
499
+ result.y = pitch;
500
+ return result;
501
+ }
502
+ // horiz = lookHorizontal / sinPitch
503
+ const horiz = lookHorizontal.scaleInPlace(1 / sinPitch);
504
+ // The quaternion rotation produces: horiz = rotate(north, up, angle)
505
+ // This is equivalent to: horiz = north*cos(angle) + cross(up, north)*sin(angle) = north*cos(angle) - east*sin(angle)
506
+ // (since cross(up, north) = -east in our basis)
507
+ // So: cosYaw = horiz · north, sinYaw = -(horiz · east)
508
+ const cosYaw = Vector3Dot(horiz, north);
509
+ const sinYaw = -Vector3Dot(horiz, east);
510
+ const yawScale = useRightHandedSystem ? 1 : -1;
511
+ result.x = Math.atan2(sinYaw, cosYaw) * yawScale;
512
+ result.y = pitch;
513
+ return result;
514
+ }
440
515
  //# sourceMappingURL=geospatialCamera.js.map