@onerjs/core 8.36.3 → 8.36.5
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.
- package/Behaviors/Cameras/geospatialClippingBehavior.d.ts +40 -0
- package/Behaviors/Cameras/geospatialClippingBehavior.js +80 -0
- package/Behaviors/Cameras/geospatialClippingBehavior.js.map +1 -0
- package/Behaviors/Cameras/index.d.ts +2 -0
- package/Behaviors/Cameras/index.js +2 -0
- package/Behaviors/Cameras/index.js.map +1 -1
- package/Cameras/Inputs/geospatialCameraPointersInput.js +4 -2
- package/Cameras/Inputs/geospatialCameraPointersInput.js.map +1 -1
- package/Cameras/Limits/geospatialLimits.d.ts +9 -1
- package/Cameras/Limits/geospatialLimits.js +23 -3
- package/Cameras/Limits/geospatialLimits.js.map +1 -1
- package/Cameras/geospatialCamera.d.ts +23 -0
- package/Cameras/geospatialCamera.js +109 -34
- package/Cameras/geospatialCamera.js.map +1 -1
- package/Cameras/geospatialCameraMovement.js +1 -0
- package/Cameras/geospatialCameraMovement.js.map +1 -1
- package/Debug/debugLayer.js +11 -3
- package/Debug/debugLayer.js.map +1 -1
- package/IAssetContainer.d.ts +5 -0
- package/IAssetContainer.js.map +1 -1
- package/Layers/highlightLayer.d.ts +16 -0
- package/Layers/highlightLayer.js +16 -0
- package/Layers/highlightLayer.js.map +1 -1
- package/Lights/Clustered/clusteredLightContainer.js +1 -1
- package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
- package/Loading/Plugins/babylonFileLoader.js +2 -0
- package/Loading/Plugins/babylonFileLoader.js.map +1 -1
- package/Materials/effectRenderer.d.ts +2 -1
- package/Materials/effectRenderer.js +2 -12
- package/Materials/effectRenderer.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +6 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +21 -17
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
- package/Particles/solidParticleSystem.d.ts +3 -0
- package/Particles/solidParticleSystem.js +7 -4
- package/Particles/solidParticleSystem.js.map +1 -1
- package/Sprites/spriteManager.d.ts +3 -0
- package/Sprites/spriteManager.js +9 -0
- package/Sprites/spriteManager.js.map +1 -1
- package/assetContainer.d.ts +5 -0
- package/assetContainer.js +32 -0
- package/assetContainer.js.map +1 -1
- 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 +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.
|
|
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.
|
|
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":"AAIA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,6BAA8B,SAAQ,wBAAwB;IAA3E;;QAGY,iCAA4B,GAAW,CAAC,CAAC;QACzC,mBAAc,GAA2B,IAAI,CAAC;IA+I1D,CAAC;IA7ImB,YAAY;QACxB,OAAO,+BAA+B,CAAC;IAC3C,CAAC;IAEe,YAAY,CAAC,GAAkB;QAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACjB,KAAK,CAAC,EAAE,4CAA4C;gBAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC/D,MAAM;YACV;gBACI,MAAM;QACd,CAAC;IACL,CAAC;IAEe,OAAO,CAAC,KAA6B,EAAE,OAAe,EAAE,OAAe;QACnF,4EAA4E;QAC5E,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,0CAA0C;QAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,QAAQ,MAAM,EAAE,CAAC;YACb,KAAK,CAAC,EAAE,2DAA2D;gBAC/D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAChE,MAAM;YACV,KAAK,CAAC,CAAC,CAAC,8BAA8B;YACtC,KAAK,CAAC,EAAE,6BAA6B;gBACjC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACnC,MAAM;QACd,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACgB,iBAAiB,CAAC,4BAAoC,EAAE,oBAA4B;QACnG,+CAA+C;QAC/C,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QACjE,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,eAAe,GAAG,gBAAgB,CAAC;QAEtD,6CAA6C;QAC7C,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,MAAM,CAAC,yBAAyB,EAAE,CAAC;YAEtD,IAAI,UAAU,EAAE,CAAC;gBACb,4FAA4F;gBAC5F,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC;gBACxD,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC;gBAEvD,mBAAmB;gBACnB,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBAC3E,IAAI,UAAU,EAAE,WAAW,EAAE,CAAC;oBAC1B,yCAAyC;oBACzC,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC;oBACvF,MAAM,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,KAAK,CAAC;oBAC1D,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;oBAC9D,OAAO;gBACX,CAAC;YACL,CAAC;QACL,CAAC;QAED,4DAA4D;QAC5D,MAAM,YAAY,GAAG,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;OAKG;IACgB,yBAAyB,CAAC,6BAAqD,EAAE,qBAA6C;QAC7I,IAAI,6BAA6B,IAAI,qBAAqB,EAAE,CAAC;YACzD,MAAM,UAAU,GAAG,qBAAqB,CAAC,CAAC,GAAG,6BAA6B,CAAC,CAAC,CAAC;YAC7E,MAAM,UAAU,GAAG,qBAAqB,CAAC,CAAC,GAAG,6BAA6B,CAAC,CAAC,CAAC;YAC7E,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAEe,WAAW,CAAC,IAAY;QACpC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAChI,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC;YACzB,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IAEe,YAAY,CACxB,MAA8B,EAC9B,MAA8B,EAC9B,4BAAoC,EACpC,oBAA4B,EAC5B,6BAAqD,EACrD,qBAA6C;QAE7C,kFAAkF;QAClF,IAAI,CAAC,cAAc,GAAG,qBAAqB,CAAC;QAE5C,uBAAuB;QACvB,IAAI,oBAAoB,KAAK,CAAC,IAAI,qBAAqB,KAAK,IAAI,EAAE,CAAC;YAC/D,IAAI,CAAC,4BAA4B,GAAG,CAAC,CAAC;YACtC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,6BAA6B,EAAE,qBAAqB,CAAC,CAAC;YAC7I,OAAO;QACX,CAAC;QAED,6EAA6E;QAC7E,IAAI,IAAI,CAAC,4BAA4B,KAAK,CAAC,IAAI,oBAAoB,KAAK,CAAC,EAAE,CAAC;YACxE,IAAI,CAAC,4BAA4B,GAAG,oBAAoB,CAAC;QAC7D,CAAC;QAED,uGAAuG;QACvG,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;QACjH,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,uBAAuB,GAAG,EAAE,IAAI,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;QAErH,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,6BAA6B,EAAE,qBAAqB,CAAC,CAAC;IACjJ,CAAC;IAEe,UAAU,CAAC,IAAmB;QAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,GAAG,KAAK,CAAC;QACzC,IAAI,CAAC,4BAA4B,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAEe,WAAW;QACvB,IAAI,CAAC,4BAA4B,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,KAAK,CAAC,WAAW,EAAE,CAAC;IACxB,CAAC;IAEO,WAAW,CAAC,MAAc,EAAE,MAAc;QAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,6BAA6B;QACzF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,oDAAoD;IACpH,CAAC;CACJ","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/
|
|
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 =
|
|
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/
|
|
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,
|
|
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
|
-
//
|
|
118
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
402
|
-
|
|
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
|