@babylonjs/lite 1.5.0 → 1.6.0

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 (41) hide show
  1. package/dist/index.js +440 -446
  2. package/dist/index.js.map +1 -1
  3. package/index.d.ts +90 -46
  4. package/lib/camera/geospatial-camera-controls.js +22 -0
  5. package/lib/camera/geospatial-camera-controls.js.map +1 -1
  6. package/lib/camera/geospatial-camera-fly.js +2 -1
  7. package/lib/camera/geospatial-camera-fly.js.map +1 -1
  8. package/lib/effect/effect-renderer.js +1 -1
  9. package/lib/effect/effect-renderer.js.map +1 -1
  10. package/lib/engine/engine.js +1 -1
  11. package/lib/index.js +4 -1
  12. package/lib/index.js.map +1 -1
  13. package/lib/post-process/taa.js +193 -0
  14. package/lib/post-process/taa.js.map +1 -0
  15. package/lib/sprite/billboard-custom-shader.js +32 -32
  16. package/lib/sprite/billboard-custom-shader.js.map +1 -1
  17. package/lib/sprite/billboard-pipeline.js +54 -56
  18. package/lib/sprite/billboard-pipeline.js.map +1 -1
  19. package/lib/sprite/custom-shader-core.js +1 -1
  20. package/lib/sprite/custom-shader-core.js.map +1 -1
  21. package/lib/sprite/shared/sprite-atlas.js +2 -2
  22. package/lib/sprite/shared/sprite-atlas.js.map +1 -1
  23. package/lib/sprite/sprite-2d-coverage-gamma.js +58 -0
  24. package/lib/sprite/sprite-2d-coverage-gamma.js.map +1 -0
  25. package/lib/sprite/sprite-2d-uvscroll.js +39 -0
  26. package/lib/sprite/sprite-2d-uvscroll.js.map +1 -0
  27. package/lib/sprite/sprite-2d.js +6 -40
  28. package/lib/sprite/sprite-2d.js.map +1 -1
  29. package/lib/sprite/sprite-coverage-gamma-hook.js +10 -0
  30. package/lib/sprite/sprite-coverage-gamma-hook.js.map +1 -0
  31. package/lib/sprite/sprite-custom-shader.js +2 -2
  32. package/lib/sprite/sprite-custom-shader.js.map +1 -1
  33. package/lib/sprite/sprite-pipeline.js +56 -71
  34. package/lib/sprite/sprite-pipeline.js.map +1 -1
  35. package/lib/sprite/sprite-renderable.js +5 -5
  36. package/lib/sprite/sprite-renderable.js.map +1 -1
  37. package/lib/sprite/sprite-renderer.js +4 -4
  38. package/lib/sprite/sprite-renderer.js.map +1 -1
  39. package/lib/sprite/sprite-scene.js +1 -1
  40. package/lib/sprite/sprite-scene.js.map +1 -1
  41. package/package.json +3 -3
package/index.d.ts CHANGED
@@ -668,6 +668,7 @@ export declare function attachFreeControl(camera: FreeCamera, canvas: HTMLCanvas
668
668
  * - Touch: single-finger drag = pan; two-finger pinch = zoom toward the centroid,
669
669
  * promoting to a pan once the centroid drifts ≥ 20 px.
670
670
  * - Keyboard: arrows = pan, Ctrl+arrows = tilt (pitch/yaw), +/- = zoom along the look vector.
671
+ * - Double-click (primary button): fly the centre to the point under the cursor.
671
672
  *
672
673
  * Movement uses Babylon.js's framerate-independent physics model (velocity +
673
674
  * inertial decay). Globe picking is analytic ray-sphere against the planet
@@ -2569,6 +2570,15 @@ export declare function createStreamingSoundAsync(engine: AudioEngine, source: S
2569
2570
  */
2570
2571
  export declare function createSurface(engine: EngineContext, canvas: RenderCanvas, options?: SurfaceOptions): SurfaceContext;
2571
2572
 
2573
+ /**
2574
+ * Create a Temporal Anti-Aliasing post-process task.
2575
+ * @param config - Source texture + source render task, blend factor, sample count.
2576
+ * @param engine - The owning engine.
2577
+ * @param scene - Optional owning scene.
2578
+ * @returns The TAA post-process task. Add it at the end of the frame-graph chain.
2579
+ */
2580
+ export declare function createTaaPostProcessTask(config: TaaPostProcessTaskConfig, engine: EngineContext, scene?: SceneContext): TaaPostProcessTask;
2581
+
2572
2582
  /** Create a TextData bound to `storage`. If `runs` is omitted the TextData starts empty;
2573
2583
  * runs can be appended later via `updateTextData({ update: "addRun", … })`. */
2574
2584
  export declare function createTextData(storage: GlyphStorage, runs?: readonly GlyphRun[]): TextData;
@@ -3085,6 +3095,9 @@ export declare interface EffectBindingLayout {
3085
3095
  visibility?: GPUShaderStageFlags;
3086
3096
  uniformByteLength?: number;
3087
3097
  textureSampleType?: GPUTextureSampleType;
3098
+ /** Texture view dimension for a `texture` binding (default `"2d"`). Set `"2d-array"`/`"cube"`/… so a
3099
+ * fullscreen effect can sample an array/cube texture (e.g. the CSM cascade depth array). */
3100
+ viewDimension?: GPUTextureViewDimension;
3088
3101
  samplerType?: GPUSamplerBindingType;
3089
3102
  textureBinding?: string | number;
3090
3103
  }
@@ -3740,6 +3753,14 @@ export declare interface GeospatialControlOptions {
3740
3753
  zoomToCursor?: boolean;
3741
3754
  /** Enable simple sphere collision so the camera cannot dip below the surface. Default false. */
3742
3755
  checkCollisions?: boolean;
3756
+ /** Duration (ms) of the fly-to animation triggered by a primary-pointer double tap. Default 1000. */
3757
+ doubleTapAnimationDurationMs?: number;
3758
+ /**
3759
+ * Optional easing applied to the double-tap fly-to animation (normalized progress `g` ∈ [0,1]).
3760
+ * A single function instance can be created once and reused across double taps. Default null
3761
+ * (uses the fly module's cubic ease-in-out).
3762
+ */
3763
+ doubleTapEasingFunction?: ((g: number) => number) | null;
3743
3764
  }
3744
3765
 
3745
3766
  /** Options for {@link flyGeospatialCameraToAsync}. Omitted target fields keep their current value. */
@@ -3756,6 +3777,8 @@ export declare interface GeospatialFlyOptions {
3756
3777
  durationMs?: number;
3757
3778
  /** Parabolic "hop" height scale for the centre animation (0 = none). */
3758
3779
  centerHopScale?: number;
3780
+ /** Easing applied to the normalized progress `g` ∈ [0,1]. Default cubic ease-in-out. */
3781
+ ease?: (g: number) => number;
3759
3782
  }
3760
3783
 
3761
3784
  /** Pitch/yaw/radius bounds for a {@link GeospatialCamera}.
@@ -7397,6 +7420,30 @@ export declare function setSpatialOrientation(host: AudioGraphHost, orientation:
7397
7420
  */
7398
7421
  export declare function setSpatialPosition(host: AudioGraphHost, position: Vec3): void;
7399
7422
 
7423
+ /**
7424
+ * Enable (or update) coverage gamma on a sprite layer for anti-aliased glyph "stem darkening".
7425
+ *
7426
+ * Coverage gamma raises the layer's sampled texture alpha to `1/coverageGamma` in the fragment
7427
+ * shader, thickening anti-aliased edges to mimic the gamma-space blending of native text
7428
+ * rasterizers (DirectWrite/CoreText). Intended for glyph-atlas (bitmap text) layers drawn into an
7429
+ * sRGB (linear-blended) surface, where correct linear AA otherwise makes text look lighter/thinner.
7430
+ * Values `> 1` thicken; `1` is a no-op (identity) and disables the effect. Non-finite or non-positive
7431
+ * values (`0`, negatives, `NaN`, `Infinity`) are also treated as disabled (the base shader is used).
7432
+ *
7433
+ * **Opt-in & tree-shakable:** importing this function is what pulls the coverage-gamma shader
7434
+ * permutation and UBO writer into the bundle. Sprite scenes that never call it ship zero gamma
7435
+ * bytes. The gamma value is stored internally on the layer and is *only* settable through this
7436
+ * function — there is no create-time option and no public field, so a value can never be written
7437
+ * that the renderer would silently ignore. Call it before `createSpriteRenderer` /
7438
+ * `addDepthHostedSpriteLayer` so the layer's first pipeline is built with the gamma permutation;
7439
+ * calling it later is also safe — the renderer re-fetches the pipeline each frame and rebuilds it
7440
+ * with the gamma permutation on the next frame.
7441
+ *
7442
+ * @param layer - The sprite layer to configure.
7443
+ * @param gamma - Coverage gamma. Typical text values are ~1.8–2.2; `1` disables the effect.
7444
+ */
7445
+ export declare function setSprite2DCoverageGamma(layer: Sprite2DLayer, gamma: number): void;
7446
+
7400
7447
  /**
7401
7448
  * Sets the atlas frame of the sprite referenced by `handle`.
7402
7449
  * @param handle - Handle of the sprite to update.
@@ -7422,12 +7469,19 @@ export declare function setSprite2DFrameIndex(layer: Sprite2DLayer, index: numbe
7422
7469
  export declare function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [number, number, number, number]): void;
7423
7470
 
7424
7471
  /**
7425
- * Set the per-sprite UV scroll offset for one sprite of a `uvScroll` layer (live). The two floats
7426
- * are added to the sprite's sampled UV in the vertex stage — driving parallax / infinite-scroll
7427
- * backgrounds without re-uploading texture coordinates. Marks only this sprite's range dirty.
7472
+ * Set (and enable) the per-sprite UV scroll offset for one sprite of `layer`. The two floats are
7473
+ * added to the sprite's sampled UV in the vertex stage — driving parallax / infinite-scroll
7474
+ * backgrounds without re-uploading texture coordinates.
7428
7475
  *
7429
- * Throws if the layer was not created with `Sprite2DLayerOptions.uvScroll: true` (non-scroll layers
7430
- * carry no uvOffset slot) or if `index` is out of range.
7476
+ * **Opt-in & tree-shakable:** importing this function is what pulls the uvScroll widening into the
7477
+ * bundle. The **first** call on a layer enables scroll: it widens the layer's instance layout by
7478
+ * two floats per sprite (re-striding existing sprites once) and flips it to the wide layout;
7479
+ * subsequent calls just write the offset. There is no create-time option — a layer is always
7480
+ * created narrow and opts into scroll the first time this setter is used.
7481
+ *
7482
+ * @param layer - The sprite layer to scroll.
7483
+ * @param index - Index of the sprite within the layer.
7484
+ * @param uvOffset - The UV offset `[u, v]` added to the sprite's sampled UV.
7431
7485
  */
7432
7486
  export declare function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void;
7433
7487
 
@@ -7983,10 +8037,6 @@ export declare interface Sprite2DLayer {
7983
8037
  readonly atlas: SpriteAtlas;
7984
8038
  readonly depth: Sprite2DDepthMode;
7985
8039
  readonly blendMode: SpriteBlendMode;
7986
8040
  opacity: number;
7987
- /** Coverage gamma applied to anti-aliased edges; see `Sprite2DLayerOptions.coverageGamma`.
7988
- * Default 1 (no-op). Read each frame into the layer UBO. Mutating it live changes the
7989
- * thickness, but the per-fragment `pow` only runs on layers created with `coverageGamma !== 1`. */
7990
- coverageGamma: number;
7991
8041
  visible: boolean;
7992
8042
  order: number;
7993
8043
  view: Sprite2DView;
@@ -8012,20 +8062,6 @@ export declare interface Sprite2DLayerOptions {
8012
8062
  capacity?: number;
8013
8063
  blendMode?: SpriteBlendMode;
8014
8064
  opacity?: number;
8015
- /**
8016
- * Coverage gamma for anti-aliased edges (text rendering). When set to a value other than 1,
8017
- * the sampled texture alpha is raised to `1/coverageGamma` in the fragment shader, thickening
8018
- * anti-aliased edges to mimic the gamma-space blending of native text rasterizers
8019
- * (DirectWrite/CoreText "stem darkening"). Intended for glyph-atlas (bitmap text) layers drawn
8020
- * into an sRGB (linear-blended) surface, where correct linear AA otherwise makes text look
8021
- * lighter/thinner. Values \>1 thicken; 1 (default) is a no-op and ships the base fragment.
8022
- *
8023
- * The per-fragment `pow` is gated on this create-time opt-in (like `uvScroll`), so only gamma
8024
- * layers run it. The plumbing (one extra `Layer` UBO `vec4`, the gamma shader permutation, and
8025
- * this option field) is, however, in the always-loaded sprite path, so it adds a small fixed
8026
- * cost (~0.3 KB raw) to every sprite scene — reflected in the sprite-scene bundle ceilings.
8027
- */
8028
- coverageGamma?: number;
8029
8065
  visible?: boolean;
8030
8066
  order?: number;
8031
8067
  view?: Partial<Sprite2DView>;
@@ -8060,21 +8096,6 @@ export declare interface Sprite2DLayerOptions {
8060
8096
  * depth-hosted sprite, call `updateSprite2DIndex(layer, idx, { z: … })`.
8061
8097
  */
8062
8098
  layerZ?: number;
8063
- /**
8064
- * Opt-in per-sprite UV scroll offset. When `true`, every sprite gains two extra instance
8065
- * floats (`uvOffset.xy`) added to its sampled UV in the vertex stage — enabling parallax /
8066
- * infinite-scroll backgrounds without re-uploading texture coordinates. Set the offset per
8067
- * sprite via `Sprite2DProps.uvOffset` (on add) or `setSprite2DUvOffset` (live).
8068
- *
8069
- * **Zero cross-scene cost:** layers created without `uvScroll` keep the narrow 13/14-float
8070
- * layout, the base vertex attributes, and the base WGSL — they ship none of the uvScroll
8071
- * widening. The wider stride, the extra `@location(7)` attribute, and the `+ iUvOffset` WGSL
8072
- * are gated directly on this per-layer flag. Defaults to `false`.
8073
- *
8074
- * Pairs naturally with a tileable atlas texture sampled in `repeat` wrap mode
8075
- * (`loadSpriteAtlas(..., { textureOptions: { addressModeU: "repeat", addressModeV: "repeat" } })`).
8076
- */
8077
- uvScroll?: boolean;
8078
8099
  }
8079
8100
 
8080
8101
  /** Per-sprite init record passed to `addSprite2DIndex` / `updateSprite2DIndex`. */
@@ -8100,14 +8121,6 @@ export declare interface Sprite2DProps {
8100
8121
  * only the dirty range.
8101
8122
  */
8102
8123
  z?: number;
8103
- /**
8104
- * Per-sprite UV scroll offset added to the sampled UV in the vertex stage. Only stored and
8105
- * consumed by `uvScroll` layers (created with `Sprite2DLayerOptions.uvScroll: true`); non-scroll
8106
- * layers use the narrow 13/14-float layout and do not allocate a uvOffset slot. When omitted on
8107
- * add for a uvScroll layer, defaults to `[0, 0]`. When omitted on update, the sprite's existing
8108
- * offset is preserved. Live updates: `setSprite2DUvOffset`.
8109
- */
8110
- uvOffset?: [number, number];
8111
8124
  }
8112
8125
 
8113
8126
  /** Per-layer 2D camera (pan / zoom / rotation). Identity = pixel-perfect HUD. */
@@ -8678,6 +8691,37 @@ export declare interface SurfaceOptions {
8678
8691
  maxDevicePixelRatio?: number;
8679
8692
  }
8680
8693
 
8694
+ /** A Temporal Anti-Aliasing post-process task: accumulates jittered frames into an
8695
+ * anti-aliased image. Inject at the end of a frame-graph chain. */
8696
+ export declare interface TaaPostProcessTask extends Task, PostProcessTaskSettings {
8697
+ readonly name: string;
8698
+ sourceTexture: RenderTarget;
8699
+ targetTexture: RenderTarget | null;
8700
+ outputTexture: RenderTarget;
8701
+ /** Blend weight of the current frame (smaller = smoother / slower convergence). */
8702
+ factor: number;
8703
+ /** Number of Halton jitter samples. */
8704
+ readonly samples: number;
8705
+ /** Reset to the current frame whenever the camera moves. */
8706
+ disableOnCameraMove: boolean;
8707
+ /** Recompute and upload the blend pass uniforms from current settings. */
8708
+ updateUniforms(): void;
8709
+ }
8710
+
8711
+ /** Configuration for `createTaaPostProcessTask`. */
8712
+ export declare interface TaaPostProcessTaskConfig extends PostProcessTaskSettings {
8713
+ /** The render task that draws the scene into `sourceTexture`. TAA jitters this
8714
+ * task's camera projection each frame (mirrors BJS `FrameGraphTAATask.objectRendererTask`). */
8715
+ sourceRenderTask: RenderTask;
8716
+ /** Blend weight of the current frame against the accumulated history (default: 0.05). */
8717
+ factor?: number;
8718
+ /** Number of jitter samples in the Halton sequence (default: 8). */
8719
+ samples?: number;
8720
+ /** Force a one-frame reset (output == current) whenever the camera moves (default: true).
8721
+ * Avoids ghosting while the view changes. */
8722
+ disableOnCameraMove?: boolean;
8723
+ }
8724
+
8681
8725
  /** Lightweight public description of one target affected by an animation group. */
8682
8726
  export declare interface TargetedAnimation {
8683
8727
  /** Runtime target object when one is directly addressable. */
@@ -1,4 +1,5 @@
1
1
  import { computeLocalBasis, computeYawPitchFromLookAt } from './geospatial-camera.js';
2
+ import { flyGeospatialCameraToAsync } from './geospatial-camera-fly.js';
2
3
  import { GEO_EPSILON, clampZoomDistance } from './geospatial-limits.js';
3
4
  import { getViewProjectionMatrix } from './camera.js';
4
5
  import { createPickingRay } from '../picking/ray.js';
@@ -8,6 +9,8 @@ import { computePanSpeedMultiplier, integrateInertialVelocity, REFERENCE_FRAME_R
8
9
  function attachGeospatialControls(camera, canvas, scene, options) {
9
10
  const zoomToCursor = options?.zoomToCursor ?? true;
10
11
  const checkCollisions = options?.checkCollisions ?? false;
12
+ const doubleTapAnimationDurationMs = options?.doubleTapAnimationDurationMs ?? 1e3;
13
+ const doubleTapEasingFunction = options?.doubleTapEasingFunction ?? null;
11
14
  const speed = 1;
12
15
  const panSpeed = 1;
13
16
  const rotationXSpeed = Math.PI / 500;
@@ -418,6 +421,9 @@ function attachGeospatialControls(camera, canvas, scene, options) {
418
421
  de -= 1;
419
422
  }
420
423
  if (dn !== 0 || de !== 0) {
424
+ const invLen = 1 / Math.hypot(dn, de);
425
+ dn *= invLen;
426
+ de *= invLen;
421
427
  computeLocalBasis(camera.center, east, north, up);
422
428
  panAccumulated.x += (north.x * dn + east.x * de) * panStep;
423
429
  panAccumulated.y += (north.y * dn + east.y * de) * panStep;
@@ -471,6 +477,21 @@ function attachGeospatialControls(camera, canvas, scene, options) {
471
477
  function onContextMenu(e) {
472
478
  e.preventDefault();
473
479
  }
480
+ function onDoubleClick(e) {
481
+ if (e.button !== 0 || e.buttons !== 0) {
482
+ return;
483
+ }
484
+ e.preventDefault();
485
+ const r = canvas.getBoundingClientRect();
486
+ const pick = pickScreen(e.clientX - r.left, e.clientY - r.top);
487
+ if (pick.hit && pick.point) {
488
+ void flyGeospatialCameraToAsync(camera, scene, {
489
+ center: { x: pick.point.x, y: pick.point.y, z: pick.point.z },
490
+ durationMs: doubleTapAnimationDurationMs,
491
+ ease: doubleTapEasingFunction ?? void 0
492
+ });
493
+ }
494
+ }
474
495
  function onKeyDown(e) {
475
496
  keysDown.add(e.code);
476
497
  }
@@ -564,6 +585,7 @@ function attachGeospatialControls(camera, canvas, scene, options) {
564
585
  [canvas, "pointerup", onPointerUp],
565
586
  [canvas, "wheel", onWheel, { passive: false }],
566
587
  [canvas, "contextmenu", onContextMenu],
588
+ [canvas, "dblclick", onDoubleClick],
567
589
  [canvas, "touchstart", onTouchStart, { passive: false }],
568
590
  [canvas, "touchmove", onTouchMove, { passive: false }],
569
591
  [canvas, "touchend", onTouchEnd],
@@ -1 +1 @@
1
- {"version":3,"file":"geospatial-camera-controls.js","sources":["../../../src/camera/geospatial-camera-controls.ts"],"sourcesContent":["import type { GeospatialCamera } from \"./geospatial-camera.js\";\nimport { computeLocalBasis, computeYawPitchFromLookAt } from \"./geospatial-camera.js\";\nimport { clampZoomDistance, GEO_EPSILON } from \"./geospatial-limits.js\";\nimport { getViewProjectionMatrix } from \"./camera.js\";\nimport { createPickingRay } from \"../picking/ray.js\";\nimport { mat4Invert } from \"../math/mat4-invert.js\";\nimport type { SceneContext } from \"../scene/scene-core.js\";\nimport type { Vec3, Mat4, Mat4Storage } from \"../math/types.js\";\nimport { REFERENCE_FRAME_RATE, integrateInertialVelocity, computePanSpeedMultiplier, computeZoomSpeedMultiplier } from \"./geospatial-movement.js\";\n\n/** Options for {@link attachGeospatialControls}. */\nexport interface GeospatialControlOptions {\n /** When true, zooming moves toward the point under the cursor; otherwise along the look vector. Default true. */\n zoomToCursor?: boolean;\n /** Enable simple sphere collision so the camera cannot dip below the surface. Default false. */\n checkCollisions?: boolean;\n}\n\ninterface PickResult {\n hit: boolean;\n point: Vec3 | null;\n ray: { origin: Vec3; direction: Vec3 } | null;\n}\n\n/**\n * Attach orbit / pan / zoom controls to a {@link GeospatialCamera}, matching\n * Babylon.js `GeospatialCamera` interactions:\n * - Left-drag: pan (the cursor stays anchored to the globe surface).\n * - Middle/right-drag: rotate (yaw + pitch / tilt).\n * - Wheel: zoom (toward the cursor by default).\n * - Touch: single-finger drag = pan; two-finger pinch = zoom toward the centroid,\n * promoting to a pan once the centroid drifts ≥ 20 px.\n * - Keyboard: arrows = pan, Ctrl+arrows = tilt (pitch/yaw), +/- = zoom along the look vector.\n *\n * Movement uses Babylon.js's framerate-independent physics model (velocity +\n * inertial decay). Globe picking is analytic ray-sphere against the planet\n * sphere (origin-centred, radius = `limits.planetRadius`) — no mesh picking\n * subsystem is required. Inertia is integrated once per frame from the scene's\n * render loop (`scene._beforeRender`).\n *\n * Returns a disposer that removes all listeners and the per-frame hook.\n */\nexport function attachGeospatialControls(camera: GeospatialCamera, canvas: HTMLCanvasElement, scene: SceneContext, options?: GeospatialControlOptions): () => void {\n const zoomToCursor = options?.zoomToCursor ?? true;\n const checkCollisions = options?.checkCollisions ?? false;\n\n // ── Speed / inertia (Babylon GeospatialCameraMovement defaults) ──\n const speed = 1;\n const panSpeed = 1;\n const rotationXSpeed = Math.PI / 500;\n const rotationYSpeed = Math.PI / 500;\n const zoomSpeed = 2;\n const panInertia = 0;\n const rotationInertia = 0;\n const zoomInertia = 0.9;\n\n // ── Accumulated input (reset each frame) ──\n const panAccumulated: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationAccumulated = { x: 0, y: 0 }; // x = pitch pixels, y = yaw pixels\n let zoomAccumulated = 0;\n let activeInput = false;\n\n // ── Velocities (for inertia) ──\n const panVelocity: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationVelocity = { x: 0, y: 0 };\n let zoomVelocity = 0;\n let prevFrameTimeMs = 0;\n\n // ── Per-frame multipliers ──\n let panSpeedMultiplier = 1;\n let zoomSpeedMultiplier = 1;\n\n // ── Per-frame computed deltas ──\n const panDelta: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationDelta = { x: 0, y: 0 };\n let zoomDelta = 0;\n let computedZoomPickPoint: Vec3 | null = null;\n\n // ── Drag (pan) state ──\n let hitPointRadius: number | undefined;\n const dragPlaneNormal: Vec3 = { x: 0, y: 0, z: 0 };\n const dragPlaneOriginEcef: Vec3 = { x: 0, y: 0, z: 0 };\n const dragPlaneHitPointLocal: Vec3 = { x: 0, y: 0, z: 0 };\n const previousDragPlaneHitPointLocal: Vec3 = { x: 0, y: 0, z: 0 };\n\n // ── Pointer state ──\n let pointerX = 0;\n let pointerY = 0;\n let mode: \"none\" | \"pan\" | \"rotate\" = \"none\";\n let lastX = 0;\n let lastY = 0;\n const keysDown = new Set<string>();\n\n // ── Touch (pinch) state ──\n const activeTouches = new Map<number, { x: number; y: number }>();\n let pinchPrevDist = 0;\n let pinchStartCentroidX = 0;\n let pinchStartCentroidY = 0;\n let pinchPanning = false;\n const PINCH_PAN_THRESHOLD = 20; // px of centroid translation before a pinch also pans\n const PINCH_ZOOM_SCALE = 0.05; // pixels of finger-spread → zoom-accumulator units\n\n function rectSize(): { width: number; height: number } {\n const r = canvas.getBoundingClientRect();\n return { width: r.width || canvas.width, height: r.height || canvas.height };\n }\n\n function toCanvas(e: PointerEvent | WheelEvent): void {\n const r = canvas.getBoundingClientRect();\n pointerX = e.clientX - r.left;\n pointerY = e.clientY - r.top;\n }\n\n // ── Picking (analytic ray-sphere against the planet) ──\n\n function screenRay(x: number, y: number): { origin: Vec3; direction: Vec3 } | null {\n const { width, height } = rectSize();\n const vp = getViewProjectionMatrix(camera, width / height);\n const ray = createPickingRay(x, y, vp, width, height);\n if (!ray) {\n return null;\n }\n return { origin: { x: ray.origin[0], y: ray.origin[1], z: ray.origin[2] }, direction: { x: ray.direction[0], y: ray.direction[1], z: ray.direction[2] } };\n }\n\n /** Nearest positive intersection of a ray with the planet sphere (centre origin). */\n function intersectPlanet(origin: Vec3, dir: Vec3): Vec3 | null {\n const r = camera.limits.planetRadius;\n const b = 2 * (origin.x * dir.x + origin.y * dir.y + origin.z * dir.z);\n const c = origin.x * origin.x + origin.y * origin.y + origin.z * origin.z - r * r;\n const disc = b * b - 4 * c;\n if (disc < 0) {\n return null;\n }\n const sq = Math.sqrt(disc);\n let t = (-b - sq) / 2;\n if (t < 0) {\n t = (-b + sq) / 2;\n }\n if (t < 0) {\n return null;\n }\n return { x: origin.x + dir.x * t, y: origin.y + dir.y * t, z: origin.z + dir.z * t };\n }\n\n function pickScreen(x: number, y: number): PickResult {\n const ray = screenRay(x, y);\n if (!ray) {\n return { hit: false, point: null, ray: null };\n }\n const point = intersectPlanet(ray.origin, ray.direction);\n return { hit: !!point, point, ray };\n }\n\n function pickAlongVector(dir: Vec3): Vec3 | null {\n return intersectPlanet(camera.position, dir);\n }\n\n // ── Pan (drag-plane) math ──\n\n function recalcDragPlaneHitPoint(radius: number, ray: { origin: Vec3; direction: Vec3 }, localToEcef: Mat4Storage): void {\n const posLen = Math.max(1e-5, Math.hypot(camera.position.x, camera.position.y, camera.position.z));\n const s = radius / posLen;\n dragPlaneOriginEcef.x = camera.position.x * s;\n dragPlaneOriginEcef.y = camera.position.y * s;\n dragPlaneOriginEcef.z = camera.position.z * s;\n\n const east: Vec3 = { x: 0, y: 0, z: 0 };\n const north: Vec3 = { x: 0, y: 0, z: 0 };\n computeLocalBasis(dragPlaneOriginEcef, east, north, dragPlaneNormal);\n\n // localToEcef = columns [east, north, up] + translation origin.\n localToEcef[0] = east.x;\n localToEcef[1] = east.y;\n localToEcef[2] = east.z;\n localToEcef[3] = 0;\n localToEcef[4] = north.x;\n localToEcef[5] = north.y;\n localToEcef[6] = north.z;\n localToEcef[7] = 0;\n localToEcef[8] = dragPlaneNormal.x;\n localToEcef[9] = dragPlaneNormal.y;\n localToEcef[10] = dragPlaneNormal.z;\n localToEcef[11] = 0;\n localToEcef[12] = dragPlaneOriginEcef.x;\n localToEcef[13] = dragPlaneOriginEcef.y;\n localToEcef[14] = dragPlaneOriginEcef.z;\n localToEcef[15] = 1;\n\n const ecefToLocal = mat4Invert(localToEcef as unknown as Mat4);\n if (!ecefToLocal) {\n return;\n }\n\n // Plane: normal·P + d = 0, d = -normal·origin.\n const d = -(dragPlaneNormal.x * dragPlaneOriginEcef.x + dragPlaneNormal.y * dragPlaneOriginEcef.y + dragPlaneNormal.z * dragPlaneOriginEcef.z);\n const denom = dragPlaneNormal.x * ray.direction.x + dragPlaneNormal.y * ray.direction.y + dragPlaneNormal.z * ray.direction.z;\n if (Math.abs(denom) > 1e-9) {\n const t = -(dragPlaneNormal.x * ray.origin.x + dragPlaneNormal.y * ray.origin.y + dragPlaneNormal.z * ray.origin.z + d) / denom;\n if (t >= 0) {\n const hx = ray.origin.x + ray.direction.x * t;\n const hy = ray.origin.y + ray.direction.y * t;\n const hz = ray.origin.z + ray.direction.z * t;\n transformCoordinates(hx, hy, hz, ecefToLocal as unknown as Mat4Storage, dragPlaneHitPointLocal);\n }\n }\n }\n\n function startDrag(x: number, y: number): void {\n const pick = pickScreen(x, y);\n if (pick.point && pick.ray) {\n hitPointRadius = Math.hypot(pick.point.x, pick.point.y, pick.point.z);\n const tmp = scratchMat;\n recalcDragPlaneHitPoint(hitPointRadius, pick.ray, tmp);\n previousDragPlaneHitPointLocal.x = dragPlaneHitPointLocal.x;\n previousDragPlaneHitPointLocal.y = dragPlaneHitPointLocal.y;\n previousDragPlaneHitPointLocal.z = dragPlaneHitPointLocal.z;\n } else {\n hitPointRadius = undefined;\n }\n }\n\n function handleDrag(x: number, y: number): void {\n if (hitPointRadius === undefined) {\n return;\n }\n const ray = screenRay(x, y);\n if (!ray) {\n return;\n }\n const localToEcef = scratchMat;\n recalcDragPlaneHitPoint(hitPointRadius, ray, localToEcef);\n\n let dx = dragPlaneHitPointLocal.x - previousDragPlaneHitPointLocal.x;\n let dy = dragPlaneHitPointLocal.y - previousDragPlaneHitPointLocal.y;\n let dz = dragPlaneHitPointLocal.z - previousDragPlaneHitPointLocal.z;\n\n // Clamp to avoid huge jumps when the camera is nearly parallel to the plane.\n const maxDelta = hitPointRadius * 0.1;\n const len = Math.hypot(dx, dy, dz);\n if (len > maxDelta) {\n const k = maxDelta / len;\n dx *= k;\n dy *= k;\n dz *= k;\n }\n\n previousDragPlaneHitPointLocal.x = dragPlaneHitPointLocal.x;\n previousDragPlaneHitPointLocal.y = dragPlaneHitPointLocal.y;\n previousDragPlaneHitPointLocal.z = dragPlaneHitPointLocal.z;\n\n // delta (local) → ECEF normal transform.\n const ex = dx * localToEcef[0]! + dy * localToEcef[4]! + dz * localToEcef[8]!;\n const ey = dx * localToEcef[1]! + dy * localToEcef[5]! + dz * localToEcef[9]!;\n const ez = dx * localToEcef[2]! + dy * localToEcef[6]! + dz * localToEcef[10]!;\n\n dragPlaneOriginEcef.x += ex;\n dragPlaneOriginEcef.y += ey;\n dragPlaneOriginEcef.z += ez;\n\n panAccumulated.x -= ex;\n panAccumulated.y -= ey;\n panAccumulated.z -= ez;\n }\n\n function stopDrag(): void {\n hitPointRadius = undefined;\n }\n\n const scratchMat = new Float32Array(16) as unknown as Mat4Storage;\n\n // ── Zoom input ──\n\n function handleZoom(delta: number, toCursor: boolean): void {\n if (delta === 0) {\n return;\n }\n zoomAccumulated += delta;\n const pick = pickScreen(pointerX, pointerY);\n if (toCursor && pick.hit && pick.point && zoomToCursor) {\n computedZoomPickPoint = pick.point;\n } else {\n computedZoomPickPoint = pickAlongVector(camera._lookAt);\n }\n }\n\n // ── Apply per-frame deltas to the camera ──\n\n function applyGeocentricTranslation(): void {\n const cx = camera.center.x + panDelta.x;\n const cy = camera.center.y + panDelta.y;\n const cz = camera.center.z + panDelta.z;\n // Re-project onto the sphere of the same magnitude as the current centre.\n const len = Math.hypot(cx, cy, cz) || 1;\n const target = Math.hypot(camera.center.x, camera.center.y, camera.center.z);\n const k = target / len;\n camera._setOrientation(camera.yaw, camera.pitch, camera.radius, { x: cx * k, y: cy * k, z: cz * k });\n }\n\n function applyGeocentricRotation(): void {\n if (rotationDelta.x === 0 && rotationDelta.y === 0) {\n return;\n }\n const pitch = rotationDelta.x !== 0 ? clampNum(camera.pitch + rotationDelta.x, 0, 0.5 * Math.PI - GEO_EPSILON) : camera.pitch;\n const yaw = rotationDelta.y !== 0 ? camera.yaw + rotationDelta.y : camera.yaw;\n camera._setOrientation(yaw, pitch, camera.radius, camera.center);\n }\n\n function centerAndRadiusFromZoomToPoint(target: Vec3, distance: number, out: Vec3): number {\n const limits = camera.limits;\n const dx = target.x - camera.position.x;\n const dy = target.y - camera.position.y;\n const dz = target.z - camera.position.z;\n const distToTarget = Math.hypot(dx, dy, dz);\n if (distToTarget < limits.radiusMin) {\n out.x = camera.center.x;\n out.y = camera.center.y;\n out.z = camera.center.z;\n return clampNum(camera.radius - distance, limits.radiusMin, limits.radiusMax);\n }\n const s = distance / distToTarget;\n const npx = camera.position.x + dx * s;\n const npy = camera.position.y + dy * s;\n const npz = camera.position.z + dz * s;\n const projected = dx * s * camera._lookAt.x + dy * s * camera._lookAt.y + dz * s * camera._lookAt.z;\n const newRadius = clampNum(camera.radius - projected, limits.radiusMin, limits.radiusMax);\n out.x = npx + camera._lookAt.x * newRadius;\n out.y = npy + camera._lookAt.y * newRadius;\n out.z = npz + camera._lookAt.z * newRadius;\n return newRadius;\n }\n\n const zoomCenterScratch: Vec3 = { x: 0, y: 0, z: 0 };\n\n function applyZoom(): void {\n const limits = camera.limits;\n const distToTarget = computedZoomPickPoint ? dist(camera.position, computedZoomPickPoint) : undefined;\n const clamped = clampZoomDistance(limits, zoomDelta, camera.radius, distToTarget);\n if (Math.abs(clamped) < GEO_EPSILON) {\n return;\n }\n if (computedZoomPickPoint) {\n const newRadius = centerAndRadiusFromZoomToPoint(computedZoomPickPoint, clamped, zoomCenterScratch);\n camera._setOrientation(camera.yaw, camera.pitch, newRadius, zoomCenterScratch);\n } else {\n const newRadius = clampNum(camera.radius - clamped, limits.radiusMin, limits.radiusMax);\n camera._setOrientation(camera.yaw, camera.pitch, newRadius, camera.center);\n }\n }\n\n let wasCenterMovingLastFrame = false;\n\n function recalculateCenter(isCenterMoving: boolean): void {\n const shouldRecalc = wasCenterMovingLastFrame && !isCenterMoving;\n wasCenterMovingLastFrame = isCenterMoving;\n if (!shouldRecalc) {\n return;\n }\n const picked = pickAlongVector(camera._lookAt);\n if (!picked) {\n return;\n }\n const invLen = 1 / (Math.hypot(picked.x, picked.y, picked.z) || 1);\n const dot = camera._lookAt.x * -picked.x * invLen + camera._lookAt.y * -picked.y * invLen + camera._lookAt.z * -picked.z * invLen;\n if (dot <= 0) {\n return;\n }\n const newRadius = dist(camera.position, picked);\n if (newRadius <= GEO_EPSILON) {\n return;\n }\n const yp = { x: 0, y: 0 };\n computeYawPitchFromLookAt(camera._lookAt, picked, camera.yaw, yp);\n camera._setOrientation(yp.x, yp.y, newRadius, picked);\n }\n\n function applyCollision(): void {\n if (!checkCollisions) {\n return;\n }\n const minDist = camera.limits.planetRadius + camera.limits.radiusMin;\n const posLen = Math.hypot(camera.position.x, camera.position.y, camera.position.z);\n if (posLen >= minDist || posLen < 1e-6) {\n return;\n }\n const lift = minDist - posLen;\n const inv = 1 / posLen;\n const ox = camera.position.x * inv * lift;\n const oy = camera.position.y * inv * lift;\n const oz = camera.position.z * inv * lift;\n // Lift the whole rig: offset the centre, position follows from setOrientation.\n camera._setOrientation(camera.yaw, camera.pitch, camera.radius, { x: camera.center.x + ox, y: camera.center.y + oy, z: camera.center.z + oz });\n }\n\n // ── Framerate-independent physics ──\n\n function effectiveDeltaMs(dt: number): number {\n if (dt > 0) {\n return dt;\n }\n if (prevFrameTimeMs > 0) {\n return prevFrameTimeMs;\n }\n return 1000 / REFERENCE_FRAME_RATE;\n }\n\n function nextVelocity(vel: number, pixelDelta: number, inertia: number, dt: number): number {\n return integrateInertialVelocity(vel, pixelDelta, inertia, effectiveDeltaMs(dt), activeInput);\n }\n\n function isDragging(): boolean {\n return hitPointRadius !== undefined;\n }\n\n function computeFrameDeltas(dt: number): void {\n // Pan dampening near the poles and with altitude.\n const center = camera.center;\n if (panAccumulated.x !== 0 || panAccumulated.y !== 0 || panAccumulated.z !== 0) {\n panSpeedMultiplier = computePanSpeedMultiplier(center, camera.position);\n } else {\n panSpeedMultiplier = 1;\n }\n\n // Zoom speed scales with distance to target; suppressed while dragging/rotating.\n if (isDragging() || rotationAccumulated.x !== 0 || rotationAccumulated.y !== 0) {\n zoomSpeedMultiplier = 0;\n zoomVelocity = 0;\n } else {\n const target = computedZoomPickPoint ? dist(camera.position, computedZoomPickPoint) : dist(camera.position, center);\n zoomSpeedMultiplier = computeZoomSpeedMultiplier(target);\n }\n\n const eff = effectiveDeltaMs(dt);\n\n panVelocity.x = nextVelocity(panVelocity.x, panAccumulated.x, panInertia, dt);\n panVelocity.y = nextVelocity(panVelocity.y, panAccumulated.y, panInertia, dt);\n panVelocity.z = nextVelocity(panVelocity.z, panAccumulated.z, panInertia, dt);\n const panScale = speed * panSpeed * panSpeedMultiplier * eff;\n panDelta.x = panVelocity.x * panScale;\n panDelta.y = panVelocity.y * panScale;\n panDelta.z = panVelocity.z * panScale;\n\n rotationVelocity.x = nextVelocity(rotationVelocity.x, rotationAccumulated.x, rotationInertia, dt);\n rotationVelocity.y = nextVelocity(rotationVelocity.y, rotationAccumulated.y, rotationInertia, dt);\n rotationDelta.x = rotationVelocity.x * speed * rotationXSpeed * eff;\n rotationDelta.y = rotationVelocity.y * speed * rotationYSpeed * eff;\n\n zoomVelocity = nextVelocity(zoomVelocity, zoomAccumulated, zoomInertia, dt);\n zoomDelta = zoomVelocity * (speed * zoomSpeed * zoomSpeedMultiplier) * eff;\n\n if (dt > 0) {\n prevFrameTimeMs = dt;\n }\n zoomAccumulated = 0;\n panAccumulated.x = panAccumulated.y = panAccumulated.z = 0;\n rotationAccumulated.x = rotationAccumulated.y = 0;\n activeInput = false;\n }\n\n // ── Per-frame loop ──\n\n function onBeforeRenderTick(deltaMs: number): void {\n // Keyboard injects accumulated input before physics.\n pollKeyboard();\n\n const hasInput =\n panAccumulated.x !== 0 || panAccumulated.y !== 0 || panAccumulated.z !== 0 || rotationAccumulated.x !== 0 || rotationAccumulated.y !== 0 || zoomAccumulated !== 0;\n if (hasInput && camera._cancelFly) {\n camera._cancelFly();\n }\n\n computeFrameDeltas(deltaMs);\n\n let isCenterMoving = false;\n if (panDelta.x !== 0 || panDelta.y !== 0 || panDelta.z !== 0) {\n applyGeocentricTranslation();\n isCenterMoving = true;\n }\n if (rotationDelta.x !== 0 || rotationDelta.y !== 0) {\n applyGeocentricRotation();\n }\n if (Math.abs(zoomDelta) > GEO_EPSILON) {\n applyZoom();\n isCenterMoving = true;\n }\n recalculateCenter(isCenterMoving);\n applyCollision();\n }\n\n // ── Keyboard ──\n\n function pollKeyboard(): void {\n if (keysDown.size === 0) {\n return;\n }\n activeInput = true;\n const rotateStep = 6; // pixels-equivalent per frame\n const zoomStep = 4;\n const panStep = camera.radius * 0.0015;\n const ctrl = keysDown.has(\"ControlLeft\") || keysDown.has(\"ControlRight\");\n\n // +/- : zoom along the look vector (no zoom-to-point pick).\n if (keysDown.has(\"Equal\") || keysDown.has(\"NumpadAdd\")) {\n zoomAccumulated += zoomStep;\n computedZoomPickPoint = null;\n }\n if (keysDown.has(\"Minus\") || keysDown.has(\"NumpadSubtract\")) {\n zoomAccumulated -= zoomStep;\n computedZoomPickPoint = null;\n }\n\n if (ctrl) {\n // Ctrl + arrows: tilt (pitch) and yaw, matching Babylon.js.\n if (keysDown.has(\"ArrowLeft\")) {\n rotationAccumulated.y -= rotateStep;\n }\n if (keysDown.has(\"ArrowRight\")) {\n rotationAccumulated.y += rotateStep;\n }\n if (keysDown.has(\"ArrowUp\")) {\n rotationAccumulated.x += rotateStep;\n }\n if (keysDown.has(\"ArrowDown\")) {\n rotationAccumulated.x -= rotateStep;\n }\n return;\n }\n\n // Arrows: pan (a drag from the canvas centre). Move the centre along the\n // local tangent basis; Up = north, Right = east.\n const east: Vec3 = { x: 0, y: 0, z: 0 };\n const north: Vec3 = { x: 0, y: 0, z: 0 };\n const up: Vec3 = { x: 0, y: 0, z: 0 };\n let dn = 0;\n let de = 0;\n if (keysDown.has(\"ArrowUp\")) {\n dn += 1;\n }\n if (keysDown.has(\"ArrowDown\")) {\n dn -= 1;\n }\n if (keysDown.has(\"ArrowRight\")) {\n de += 1;\n }\n if (keysDown.has(\"ArrowLeft\")) {\n de -= 1;\n }\n if (dn !== 0 || de !== 0) {\n computeLocalBasis(camera.center, east, north, up);\n panAccumulated.x += (north.x * dn + east.x * de) * panStep;\n panAccumulated.y += (north.y * dn + east.y * de) * panStep;\n panAccumulated.z += (north.z * dn + east.z * de) * panStep;\n }\n }\n\n // ── DOM listeners ──\n\n function onPointerDown(e: PointerEvent): void {\n canvas.setPointerCapture(e.pointerId);\n toCanvas(e);\n lastX = e.clientX;\n lastY = e.clientY;\n if (e.button === 0) {\n mode = \"pan\";\n startDrag(pointerX, pointerY);\n } else {\n mode = \"rotate\";\n }\n }\n\n function onPointerMove(e: PointerEvent): void {\n toCanvas(e);\n const dx = e.clientX - lastX;\n const dy = e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n // While two fingers are down the gesture is a pinch (handled by the touch\n // listeners); suppress the pointer-driven pan/rotate the first finger would\n // otherwise trigger. lastX/Y above stay current so the remaining finger\n // doesn't jump when one lifts.\n if (activeTouches.size >= 2) {\n return;\n }\n if (mode === \"none\") {\n return;\n }\n activeInput = true;\n if (mode === \"pan\") {\n handleDrag(pointerX, pointerY);\n } else if (mode === \"rotate\") {\n rotationAccumulated.y += dx; // yaw\n rotationAccumulated.x += dy; // pitch\n }\n }\n\n function onPointerUp(e: PointerEvent): void {\n canvas.releasePointerCapture(e.pointerId);\n if (mode === \"pan\") {\n stopDrag();\n }\n mode = \"none\";\n }\n\n function onWheel(e: WheelEvent): void {\n e.preventDefault();\n toCanvas(e);\n handleZoom(-Math.sign(e.deltaY), true);\n }\n\n function onContextMenu(e: Event): void {\n e.preventDefault();\n }\n\n function onKeyDown(e: KeyboardEvent): void {\n keysDown.add(e.code);\n }\n\n function onKeyUp(e: KeyboardEvent): void {\n keysDown.delete(e.code);\n }\n\n // ── Touch (two-finger pinch = zoom toward centroid; ≥20 px centroid drift = pan) ──\n\n function firstTwoTouches(): [{ x: number; y: number }, { x: number; y: number }] {\n const it = activeTouches.values();\n const a = it.next().value as { x: number; y: number };\n const b = it.next().value as { x: number; y: number };\n return [a, b];\n }\n\n function onTouchStart(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n const t = e.changedTouches[i]!;\n activeTouches.set(t.identifier, { x: t.clientX, y: t.clientY });\n }\n if (activeTouches.size >= 2) {\n // A second finger landed: this is a pinch, not a single-finger pan.\n // Cancel any in-progress pointer drag and stop the browser hijacking\n // the gesture as a page zoom (iOS ignores touch-action for pinch).\n e.preventDefault();\n if (mode === \"pan\") {\n stopDrag();\n }\n mode = \"none\";\n const [p0, p1] = firstTwoTouches();\n pinchPrevDist = Math.hypot(p1.x - p0.x, p1.y - p0.y);\n pinchStartCentroidX = (p0.x + p1.x) / 2;\n pinchStartCentroidY = (p0.y + p1.y) / 2;\n pinchPanning = false;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n const t = e.changedTouches[i]!;\n if (activeTouches.has(t.identifier)) {\n activeTouches.set(t.identifier, { x: t.clientX, y: t.clientY });\n }\n }\n if (activeTouches.size < 2) {\n return;\n }\n e.preventDefault();\n activeInput = true;\n const [p0, p1] = firstTwoTouches();\n const dist2 = Math.hypot(p1.x - p0.x, p1.y - p0.y);\n const centroidClientX = (p0.x + p1.x) / 2;\n const centroidClientY = (p0.y + p1.y) / 2;\n const rect = canvas.getBoundingClientRect();\n pointerX = centroidClientX - rect.left;\n pointerY = centroidClientY - rect.top;\n\n const centroidDrift = Math.hypot(centroidClientX - pinchStartCentroidX, centroidClientY - pinchStartCentroidY);\n if (!pinchPanning && centroidDrift > PINCH_PAN_THRESHOLD) {\n pinchPanning = true;\n startDrag(pointerX, pointerY);\n }\n\n if (pinchPanning) {\n // Pan dominates: drag the globe under the centroid (zoom is suppressed\n // while dragging, mirroring the mid-drag zoom lockout).\n handleDrag(pointerX, pointerY);\n } else if (pinchPrevDist > 0) {\n // Zoom toward the centroid: finger-spread Δpx feeds the zoom accumulator.\n const dDist = dist2 - pinchPrevDist;\n if (dDist !== 0) {\n zoomAccumulated += dDist * PINCH_ZOOM_SCALE;\n const pick = pickScreen(pointerX, pointerY);\n computedZoomPickPoint = pick.hit && pick.point && zoomToCursor ? pick.point : pickAlongVector(camera._lookAt);\n }\n }\n pinchPrevDist = dist2;\n }\n\n function onTouchEnd(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n activeTouches.delete(e.changedTouches[i]!.identifier);\n }\n if (activeTouches.size < 2) {\n if (pinchPanning) {\n stopDrag();\n pinchPanning = false;\n }\n pinchPrevDist = 0;\n }\n // One finger remains: re-anchor lastX/Y so its pointer drag doesn't jump.\n if (activeTouches.size === 1) {\n const p = activeTouches.values().next().value as { x: number; y: number };\n lastX = p.x;\n lastY = p.y;\n }\n }\n\n // iOS Safari fires non-standard gesture* events and still page-zooms even with\n // touch-action:none; swallow them so the pinch stays with the camera.\n function onGesture(e: Event): void {\n e.preventDefault();\n }\n\n scene._beforeRender.push(onBeforeRenderTick);\n\n const listeners: [EventTarget, string, EventListener, AddEventListenerOptions?][] = [\n [canvas, \"pointerdown\", onPointerDown as EventListener],\n [canvas, \"pointermove\", onPointerMove as EventListener],\n [canvas, \"pointerup\", onPointerUp as EventListener],\n [canvas, \"wheel\", onWheel as EventListener, { passive: false }],\n [canvas, \"contextmenu\", onContextMenu as EventListener],\n [canvas, \"touchstart\", onTouchStart as EventListener, { passive: false }],\n [canvas, \"touchmove\", onTouchMove as EventListener, { passive: false }],\n [canvas, \"touchend\", onTouchEnd as EventListener],\n [canvas, \"gesturestart\", onGesture as EventListener, { passive: false }],\n [canvas, \"gesturechange\", onGesture as EventListener, { passive: false }],\n [canvas, \"gestureend\", onGesture as EventListener, { passive: false }],\n [window, \"keydown\", onKeyDown as EventListener],\n [window, \"keyup\", onKeyUp as EventListener],\n ];\n for (const [t, ev, h, opts] of listeners) {\n t.addEventListener(ev, h, opts);\n }\n\n return () => {\n const idx = scene._beforeRender.indexOf(onBeforeRenderTick);\n if (idx >= 0) {\n scene._beforeRender.splice(idx, 1);\n }\n for (const [t, ev, h] of listeners) {\n t.removeEventListener(ev, h);\n }\n };\n}\n\n// ── shared helpers ──\n\nfunction clampNum(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nfunction dist(a: Vec3, b: Vec3): number {\n return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);\n}\n\n/** Transform a point by a Mat4 (row-vector × column-major matrix, with w divide). */\nfunction transformCoordinates(x: number, y: number, z: number, m: Mat4Storage, out: Vec3): void {\n const rx = x * m[0]! + y * m[4]! + z * m[8]! + m[12]!;\n const ry = x * m[1]! + y * m[5]! + z * m[9]! + m[13]!;\n const rz = x * m[2]! + y * m[6]! + z * m[10]! + m[14]!;\n const rw = x * m[3]! + y * m[7]! + z * m[11]! + m[15]!;\n const inv = rw !== 0 ? 1 / rw : 1;\n out.x = rx * inv;\n out.y = ry * inv;\n out.z = rz * inv;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"geospatial-camera-controls.js","sources":["../../../src/camera/geospatial-camera-controls.ts"],"sourcesContent":["import type { GeospatialCamera } from \"./geospatial-camera.js\";\nimport { computeLocalBasis, computeYawPitchFromLookAt } from \"./geospatial-camera.js\";\nimport { flyGeospatialCameraToAsync } from \"./geospatial-camera-fly.js\";\nimport { clampZoomDistance, GEO_EPSILON } from \"./geospatial-limits.js\";\nimport { getViewProjectionMatrix } from \"./camera.js\";\nimport { createPickingRay } from \"../picking/ray.js\";\nimport { mat4Invert } from \"../math/mat4-invert.js\";\nimport type { SceneContext } from \"../scene/scene-core.js\";\nimport type { Vec3, Mat4, Mat4Storage } from \"../math/types.js\";\nimport { REFERENCE_FRAME_RATE, integrateInertialVelocity, computePanSpeedMultiplier, computeZoomSpeedMultiplier } from \"./geospatial-movement.js\";\n\n/** Options for {@link attachGeospatialControls}. */\nexport interface GeospatialControlOptions {\n /** When true, zooming moves toward the point under the cursor; otherwise along the look vector. Default true. */\n zoomToCursor?: boolean;\n /** Enable simple sphere collision so the camera cannot dip below the surface. Default false. */\n checkCollisions?: boolean;\n /** Duration (ms) of the fly-to animation triggered by a primary-pointer double tap. Default 1000. */\n doubleTapAnimationDurationMs?: number;\n /**\n * Optional easing applied to the double-tap fly-to animation (normalized progress `g` ∈ [0,1]).\n * A single function instance can be created once and reused across double taps. Default null\n * (uses the fly module's cubic ease-in-out).\n */\n doubleTapEasingFunction?: ((g: number) => number) | null;\n}\n\ninterface PickResult {\n hit: boolean;\n point: Vec3 | null;\n ray: { origin: Vec3; direction: Vec3 } | null;\n}\n\n/**\n * Attach orbit / pan / zoom controls to a {@link GeospatialCamera}, matching\n * Babylon.js `GeospatialCamera` interactions:\n * - Left-drag: pan (the cursor stays anchored to the globe surface).\n * - Middle/right-drag: rotate (yaw + pitch / tilt).\n * - Wheel: zoom (toward the cursor by default).\n * - Touch: single-finger drag = pan; two-finger pinch = zoom toward the centroid,\n * promoting to a pan once the centroid drifts ≥ 20 px.\n * - Keyboard: arrows = pan, Ctrl+arrows = tilt (pitch/yaw), +/- = zoom along the look vector.\n * - Double-click (primary button): fly the centre to the point under the cursor.\n *\n * Movement uses Babylon.js's framerate-independent physics model (velocity +\n * inertial decay). Globe picking is analytic ray-sphere against the planet\n * sphere (origin-centred, radius = `limits.planetRadius`) — no mesh picking\n * subsystem is required. Inertia is integrated once per frame from the scene's\n * render loop (`scene._beforeRender`).\n *\n * Returns a disposer that removes all listeners and the per-frame hook.\n */\nexport function attachGeospatialControls(camera: GeospatialCamera, canvas: HTMLCanvasElement, scene: SceneContext, options?: GeospatialControlOptions): () => void {\n const zoomToCursor = options?.zoomToCursor ?? true;\n const checkCollisions = options?.checkCollisions ?? false;\n const doubleTapAnimationDurationMs = options?.doubleTapAnimationDurationMs ?? 1000;\n const doubleTapEasingFunction = options?.doubleTapEasingFunction ?? null;\n\n // ── Speed / inertia (Babylon GeospatialCameraMovement defaults) ──\n const speed = 1;\n const panSpeed = 1;\n const rotationXSpeed = Math.PI / 500;\n const rotationYSpeed = Math.PI / 500;\n const zoomSpeed = 2;\n const panInertia = 0;\n const rotationInertia = 0;\n const zoomInertia = 0.9;\n\n // ── Accumulated input (reset each frame) ──\n const panAccumulated: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationAccumulated = { x: 0, y: 0 }; // x = pitch pixels, y = yaw pixels\n let zoomAccumulated = 0;\n let activeInput = false;\n\n // ── Velocities (for inertia) ──\n const panVelocity: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationVelocity = { x: 0, y: 0 };\n let zoomVelocity = 0;\n let prevFrameTimeMs = 0;\n\n // ── Per-frame multipliers ──\n let panSpeedMultiplier = 1;\n let zoomSpeedMultiplier = 1;\n\n // ── Per-frame computed deltas ──\n const panDelta: Vec3 = { x: 0, y: 0, z: 0 };\n const rotationDelta = { x: 0, y: 0 };\n let zoomDelta = 0;\n let computedZoomPickPoint: Vec3 | null = null;\n\n // ── Drag (pan) state ──\n let hitPointRadius: number | undefined;\n const dragPlaneNormal: Vec3 = { x: 0, y: 0, z: 0 };\n const dragPlaneOriginEcef: Vec3 = { x: 0, y: 0, z: 0 };\n const dragPlaneHitPointLocal: Vec3 = { x: 0, y: 0, z: 0 };\n const previousDragPlaneHitPointLocal: Vec3 = { x: 0, y: 0, z: 0 };\n\n // ── Pointer state ──\n let pointerX = 0;\n let pointerY = 0;\n let mode: \"none\" | \"pan\" | \"rotate\" = \"none\";\n let lastX = 0;\n let lastY = 0;\n const keysDown = new Set<string>();\n\n // ── Touch (pinch) state ──\n const activeTouches = new Map<number, { x: number; y: number }>();\n let pinchPrevDist = 0;\n let pinchStartCentroidX = 0;\n let pinchStartCentroidY = 0;\n let pinchPanning = false;\n const PINCH_PAN_THRESHOLD = 20; // px of centroid translation before a pinch also pans\n const PINCH_ZOOM_SCALE = 0.05; // pixels of finger-spread → zoom-accumulator units\n\n function rectSize(): { width: number; height: number } {\n const r = canvas.getBoundingClientRect();\n return { width: r.width || canvas.width, height: r.height || canvas.height };\n }\n\n function toCanvas(e: PointerEvent | WheelEvent): void {\n const r = canvas.getBoundingClientRect();\n pointerX = e.clientX - r.left;\n pointerY = e.clientY - r.top;\n }\n\n // ── Picking (analytic ray-sphere against the planet) ──\n\n function screenRay(x: number, y: number): { origin: Vec3; direction: Vec3 } | null {\n const { width, height } = rectSize();\n const vp = getViewProjectionMatrix(camera, width / height);\n const ray = createPickingRay(x, y, vp, width, height);\n if (!ray) {\n return null;\n }\n return { origin: { x: ray.origin[0], y: ray.origin[1], z: ray.origin[2] }, direction: { x: ray.direction[0], y: ray.direction[1], z: ray.direction[2] } };\n }\n\n /** Nearest positive intersection of a ray with the planet sphere (centre origin). */\n function intersectPlanet(origin: Vec3, dir: Vec3): Vec3 | null {\n const r = camera.limits.planetRadius;\n const b = 2 * (origin.x * dir.x + origin.y * dir.y + origin.z * dir.z);\n const c = origin.x * origin.x + origin.y * origin.y + origin.z * origin.z - r * r;\n const disc = b * b - 4 * c;\n if (disc < 0) {\n return null;\n }\n const sq = Math.sqrt(disc);\n let t = (-b - sq) / 2;\n if (t < 0) {\n t = (-b + sq) / 2;\n }\n if (t < 0) {\n return null;\n }\n return { x: origin.x + dir.x * t, y: origin.y + dir.y * t, z: origin.z + dir.z * t };\n }\n\n function pickScreen(x: number, y: number): PickResult {\n const ray = screenRay(x, y);\n if (!ray) {\n return { hit: false, point: null, ray: null };\n }\n const point = intersectPlanet(ray.origin, ray.direction);\n return { hit: !!point, point, ray };\n }\n\n function pickAlongVector(dir: Vec3): Vec3 | null {\n return intersectPlanet(camera.position, dir);\n }\n\n // ── Pan (drag-plane) math ──\n\n function recalcDragPlaneHitPoint(radius: number, ray: { origin: Vec3; direction: Vec3 }, localToEcef: Mat4Storage): void {\n const posLen = Math.max(1e-5, Math.hypot(camera.position.x, camera.position.y, camera.position.z));\n const s = radius / posLen;\n dragPlaneOriginEcef.x = camera.position.x * s;\n dragPlaneOriginEcef.y = camera.position.y * s;\n dragPlaneOriginEcef.z = camera.position.z * s;\n\n const east: Vec3 = { x: 0, y: 0, z: 0 };\n const north: Vec3 = { x: 0, y: 0, z: 0 };\n computeLocalBasis(dragPlaneOriginEcef, east, north, dragPlaneNormal);\n\n // localToEcef = columns [east, north, up] + translation origin.\n localToEcef[0] = east.x;\n localToEcef[1] = east.y;\n localToEcef[2] = east.z;\n localToEcef[3] = 0;\n localToEcef[4] = north.x;\n localToEcef[5] = north.y;\n localToEcef[6] = north.z;\n localToEcef[7] = 0;\n localToEcef[8] = dragPlaneNormal.x;\n localToEcef[9] = dragPlaneNormal.y;\n localToEcef[10] = dragPlaneNormal.z;\n localToEcef[11] = 0;\n localToEcef[12] = dragPlaneOriginEcef.x;\n localToEcef[13] = dragPlaneOriginEcef.y;\n localToEcef[14] = dragPlaneOriginEcef.z;\n localToEcef[15] = 1;\n\n const ecefToLocal = mat4Invert(localToEcef as unknown as Mat4);\n if (!ecefToLocal) {\n return;\n }\n\n // Plane: normal·P + d = 0, d = -normal·origin.\n const d = -(dragPlaneNormal.x * dragPlaneOriginEcef.x + dragPlaneNormal.y * dragPlaneOriginEcef.y + dragPlaneNormal.z * dragPlaneOriginEcef.z);\n const denom = dragPlaneNormal.x * ray.direction.x + dragPlaneNormal.y * ray.direction.y + dragPlaneNormal.z * ray.direction.z;\n if (Math.abs(denom) > 1e-9) {\n const t = -(dragPlaneNormal.x * ray.origin.x + dragPlaneNormal.y * ray.origin.y + dragPlaneNormal.z * ray.origin.z + d) / denom;\n if (t >= 0) {\n const hx = ray.origin.x + ray.direction.x * t;\n const hy = ray.origin.y + ray.direction.y * t;\n const hz = ray.origin.z + ray.direction.z * t;\n transformCoordinates(hx, hy, hz, ecefToLocal as unknown as Mat4Storage, dragPlaneHitPointLocal);\n }\n }\n }\n\n function startDrag(x: number, y: number): void {\n const pick = pickScreen(x, y);\n if (pick.point && pick.ray) {\n hitPointRadius = Math.hypot(pick.point.x, pick.point.y, pick.point.z);\n const tmp = scratchMat;\n recalcDragPlaneHitPoint(hitPointRadius, pick.ray, tmp);\n previousDragPlaneHitPointLocal.x = dragPlaneHitPointLocal.x;\n previousDragPlaneHitPointLocal.y = dragPlaneHitPointLocal.y;\n previousDragPlaneHitPointLocal.z = dragPlaneHitPointLocal.z;\n } else {\n hitPointRadius = undefined;\n }\n }\n\n function handleDrag(x: number, y: number): void {\n if (hitPointRadius === undefined) {\n return;\n }\n const ray = screenRay(x, y);\n if (!ray) {\n return;\n }\n const localToEcef = scratchMat;\n recalcDragPlaneHitPoint(hitPointRadius, ray, localToEcef);\n\n let dx = dragPlaneHitPointLocal.x - previousDragPlaneHitPointLocal.x;\n let dy = dragPlaneHitPointLocal.y - previousDragPlaneHitPointLocal.y;\n let dz = dragPlaneHitPointLocal.z - previousDragPlaneHitPointLocal.z;\n\n // Clamp to avoid huge jumps when the camera is nearly parallel to the plane.\n const maxDelta = hitPointRadius * 0.1;\n const len = Math.hypot(dx, dy, dz);\n if (len > maxDelta) {\n const k = maxDelta / len;\n dx *= k;\n dy *= k;\n dz *= k;\n }\n\n previousDragPlaneHitPointLocal.x = dragPlaneHitPointLocal.x;\n previousDragPlaneHitPointLocal.y = dragPlaneHitPointLocal.y;\n previousDragPlaneHitPointLocal.z = dragPlaneHitPointLocal.z;\n\n // delta (local) → ECEF normal transform.\n const ex = dx * localToEcef[0]! + dy * localToEcef[4]! + dz * localToEcef[8]!;\n const ey = dx * localToEcef[1]! + dy * localToEcef[5]! + dz * localToEcef[9]!;\n const ez = dx * localToEcef[2]! + dy * localToEcef[6]! + dz * localToEcef[10]!;\n\n dragPlaneOriginEcef.x += ex;\n dragPlaneOriginEcef.y += ey;\n dragPlaneOriginEcef.z += ez;\n\n panAccumulated.x -= ex;\n panAccumulated.y -= ey;\n panAccumulated.z -= ez;\n }\n\n function stopDrag(): void {\n hitPointRadius = undefined;\n }\n\n const scratchMat = new Float32Array(16) as unknown as Mat4Storage;\n\n // ── Zoom input ──\n\n function handleZoom(delta: number, toCursor: boolean): void {\n if (delta === 0) {\n return;\n }\n zoomAccumulated += delta;\n const pick = pickScreen(pointerX, pointerY);\n if (toCursor && pick.hit && pick.point && zoomToCursor) {\n computedZoomPickPoint = pick.point;\n } else {\n computedZoomPickPoint = pickAlongVector(camera._lookAt);\n }\n }\n\n // ── Apply per-frame deltas to the camera ──\n\n function applyGeocentricTranslation(): void {\n const cx = camera.center.x + panDelta.x;\n const cy = camera.center.y + panDelta.y;\n const cz = camera.center.z + panDelta.z;\n // Re-project onto the sphere of the same magnitude as the current centre.\n const len = Math.hypot(cx, cy, cz) || 1;\n const target = Math.hypot(camera.center.x, camera.center.y, camera.center.z);\n const k = target / len;\n camera._setOrientation(camera.yaw, camera.pitch, camera.radius, { x: cx * k, y: cy * k, z: cz * k });\n }\n\n function applyGeocentricRotation(): void {\n if (rotationDelta.x === 0 && rotationDelta.y === 0) {\n return;\n }\n const pitch = rotationDelta.x !== 0 ? clampNum(camera.pitch + rotationDelta.x, 0, 0.5 * Math.PI - GEO_EPSILON) : camera.pitch;\n const yaw = rotationDelta.y !== 0 ? camera.yaw + rotationDelta.y : camera.yaw;\n camera._setOrientation(yaw, pitch, camera.radius, camera.center);\n }\n\n function centerAndRadiusFromZoomToPoint(target: Vec3, distance: number, out: Vec3): number {\n const limits = camera.limits;\n const dx = target.x - camera.position.x;\n const dy = target.y - camera.position.y;\n const dz = target.z - camera.position.z;\n const distToTarget = Math.hypot(dx, dy, dz);\n if (distToTarget < limits.radiusMin) {\n out.x = camera.center.x;\n out.y = camera.center.y;\n out.z = camera.center.z;\n return clampNum(camera.radius - distance, limits.radiusMin, limits.radiusMax);\n }\n const s = distance / distToTarget;\n const npx = camera.position.x + dx * s;\n const npy = camera.position.y + dy * s;\n const npz = camera.position.z + dz * s;\n const projected = dx * s * camera._lookAt.x + dy * s * camera._lookAt.y + dz * s * camera._lookAt.z;\n const newRadius = clampNum(camera.radius - projected, limits.radiusMin, limits.radiusMax);\n out.x = npx + camera._lookAt.x * newRadius;\n out.y = npy + camera._lookAt.y * newRadius;\n out.z = npz + camera._lookAt.z * newRadius;\n return newRadius;\n }\n\n const zoomCenterScratch: Vec3 = { x: 0, y: 0, z: 0 };\n\n function applyZoom(): void {\n const limits = camera.limits;\n const distToTarget = computedZoomPickPoint ? dist(camera.position, computedZoomPickPoint) : undefined;\n const clamped = clampZoomDistance(limits, zoomDelta, camera.radius, distToTarget);\n if (Math.abs(clamped) < GEO_EPSILON) {\n return;\n }\n if (computedZoomPickPoint) {\n const newRadius = centerAndRadiusFromZoomToPoint(computedZoomPickPoint, clamped, zoomCenterScratch);\n camera._setOrientation(camera.yaw, camera.pitch, newRadius, zoomCenterScratch);\n } else {\n const newRadius = clampNum(camera.radius - clamped, limits.radiusMin, limits.radiusMax);\n camera._setOrientation(camera.yaw, camera.pitch, newRadius, camera.center);\n }\n }\n\n let wasCenterMovingLastFrame = false;\n\n function recalculateCenter(isCenterMoving: boolean): void {\n const shouldRecalc = wasCenterMovingLastFrame && !isCenterMoving;\n wasCenterMovingLastFrame = isCenterMoving;\n if (!shouldRecalc) {\n return;\n }\n const picked = pickAlongVector(camera._lookAt);\n if (!picked) {\n return;\n }\n const invLen = 1 / (Math.hypot(picked.x, picked.y, picked.z) || 1);\n const dot = camera._lookAt.x * -picked.x * invLen + camera._lookAt.y * -picked.y * invLen + camera._lookAt.z * -picked.z * invLen;\n if (dot <= 0) {\n return;\n }\n const newRadius = dist(camera.position, picked);\n if (newRadius <= GEO_EPSILON) {\n return;\n }\n const yp = { x: 0, y: 0 };\n computeYawPitchFromLookAt(camera._lookAt, picked, camera.yaw, yp);\n camera._setOrientation(yp.x, yp.y, newRadius, picked);\n }\n\n function applyCollision(): void {\n if (!checkCollisions) {\n return;\n }\n const minDist = camera.limits.planetRadius + camera.limits.radiusMin;\n const posLen = Math.hypot(camera.position.x, camera.position.y, camera.position.z);\n if (posLen >= minDist || posLen < 1e-6) {\n return;\n }\n const lift = minDist - posLen;\n const inv = 1 / posLen;\n const ox = camera.position.x * inv * lift;\n const oy = camera.position.y * inv * lift;\n const oz = camera.position.z * inv * lift;\n // Lift the whole rig: offset the centre, position follows from setOrientation.\n camera._setOrientation(camera.yaw, camera.pitch, camera.radius, { x: camera.center.x + ox, y: camera.center.y + oy, z: camera.center.z + oz });\n }\n\n // ── Framerate-independent physics ──\n\n function effectiveDeltaMs(dt: number): number {\n if (dt > 0) {\n return dt;\n }\n if (prevFrameTimeMs > 0) {\n return prevFrameTimeMs;\n }\n return 1000 / REFERENCE_FRAME_RATE;\n }\n\n function nextVelocity(vel: number, pixelDelta: number, inertia: number, dt: number): number {\n return integrateInertialVelocity(vel, pixelDelta, inertia, effectiveDeltaMs(dt), activeInput);\n }\n\n function isDragging(): boolean {\n return hitPointRadius !== undefined;\n }\n\n function computeFrameDeltas(dt: number): void {\n // Pan dampening near the poles and with altitude.\n const center = camera.center;\n if (panAccumulated.x !== 0 || panAccumulated.y !== 0 || panAccumulated.z !== 0) {\n panSpeedMultiplier = computePanSpeedMultiplier(center, camera.position);\n } else {\n panSpeedMultiplier = 1;\n }\n\n // Zoom speed scales with distance to target; suppressed while dragging/rotating.\n if (isDragging() || rotationAccumulated.x !== 0 || rotationAccumulated.y !== 0) {\n zoomSpeedMultiplier = 0;\n zoomVelocity = 0;\n } else {\n const target = computedZoomPickPoint ? dist(camera.position, computedZoomPickPoint) : dist(camera.position, center);\n zoomSpeedMultiplier = computeZoomSpeedMultiplier(target);\n }\n\n const eff = effectiveDeltaMs(dt);\n\n panVelocity.x = nextVelocity(panVelocity.x, panAccumulated.x, panInertia, dt);\n panVelocity.y = nextVelocity(panVelocity.y, panAccumulated.y, panInertia, dt);\n panVelocity.z = nextVelocity(panVelocity.z, panAccumulated.z, panInertia, dt);\n const panScale = speed * panSpeed * panSpeedMultiplier * eff;\n panDelta.x = panVelocity.x * panScale;\n panDelta.y = panVelocity.y * panScale;\n panDelta.z = panVelocity.z * panScale;\n\n rotationVelocity.x = nextVelocity(rotationVelocity.x, rotationAccumulated.x, rotationInertia, dt);\n rotationVelocity.y = nextVelocity(rotationVelocity.y, rotationAccumulated.y, rotationInertia, dt);\n rotationDelta.x = rotationVelocity.x * speed * rotationXSpeed * eff;\n rotationDelta.y = rotationVelocity.y * speed * rotationYSpeed * eff;\n\n zoomVelocity = nextVelocity(zoomVelocity, zoomAccumulated, zoomInertia, dt);\n zoomDelta = zoomVelocity * (speed * zoomSpeed * zoomSpeedMultiplier) * eff;\n\n if (dt > 0) {\n prevFrameTimeMs = dt;\n }\n zoomAccumulated = 0;\n panAccumulated.x = panAccumulated.y = panAccumulated.z = 0;\n rotationAccumulated.x = rotationAccumulated.y = 0;\n activeInput = false;\n }\n\n // ── Per-frame loop ──\n\n function onBeforeRenderTick(deltaMs: number): void {\n // Keyboard injects accumulated input before physics.\n pollKeyboard();\n\n const hasInput =\n panAccumulated.x !== 0 || panAccumulated.y !== 0 || panAccumulated.z !== 0 || rotationAccumulated.x !== 0 || rotationAccumulated.y !== 0 || zoomAccumulated !== 0;\n if (hasInput && camera._cancelFly) {\n camera._cancelFly();\n }\n\n computeFrameDeltas(deltaMs);\n\n let isCenterMoving = false;\n if (panDelta.x !== 0 || panDelta.y !== 0 || panDelta.z !== 0) {\n applyGeocentricTranslation();\n isCenterMoving = true;\n }\n if (rotationDelta.x !== 0 || rotationDelta.y !== 0) {\n applyGeocentricRotation();\n }\n if (Math.abs(zoomDelta) > GEO_EPSILON) {\n applyZoom();\n isCenterMoving = true;\n }\n recalculateCenter(isCenterMoving);\n applyCollision();\n }\n\n // ── Keyboard ──\n\n function pollKeyboard(): void {\n if (keysDown.size === 0) {\n return;\n }\n activeInput = true;\n const rotateStep = 6; // pixels-equivalent per frame\n const zoomStep = 4;\n const panStep = camera.radius * 0.0015;\n const ctrl = keysDown.has(\"ControlLeft\") || keysDown.has(\"ControlRight\");\n\n // +/- : zoom along the look vector (no zoom-to-point pick).\n if (keysDown.has(\"Equal\") || keysDown.has(\"NumpadAdd\")) {\n zoomAccumulated += zoomStep;\n computedZoomPickPoint = null;\n }\n if (keysDown.has(\"Minus\") || keysDown.has(\"NumpadSubtract\")) {\n zoomAccumulated -= zoomStep;\n computedZoomPickPoint = null;\n }\n\n if (ctrl) {\n // Ctrl + arrows: tilt (pitch) and yaw, matching Babylon.js.\n if (keysDown.has(\"ArrowLeft\")) {\n rotationAccumulated.y -= rotateStep;\n }\n if (keysDown.has(\"ArrowRight\")) {\n rotationAccumulated.y += rotateStep;\n }\n if (keysDown.has(\"ArrowUp\")) {\n rotationAccumulated.x += rotateStep;\n }\n if (keysDown.has(\"ArrowDown\")) {\n rotationAccumulated.x -= rotateStep;\n }\n return;\n }\n\n // Arrows: pan (a drag from the canvas centre). Move the centre along the\n // local tangent basis; Up = north, Right = east.\n const east: Vec3 = { x: 0, y: 0, z: 0 };\n const north: Vec3 = { x: 0, y: 0, z: 0 };\n const up: Vec3 = { x: 0, y: 0, z: 0 };\n let dn = 0;\n let de = 0;\n if (keysDown.has(\"ArrowUp\")) {\n dn += 1;\n }\n if (keysDown.has(\"ArrowDown\")) {\n dn -= 1;\n }\n if (keysDown.has(\"ArrowRight\")) {\n de += 1;\n }\n if (keysDown.has(\"ArrowLeft\")) {\n de -= 1;\n }\n if (dn !== 0 || de !== 0) {\n // Normalize the (north, east) direction so holding two pan keys at once (e.g. up + left)\n // pans along a diagonal at the same speed as a single direction, instead of the ~1.41x\n // boost (sqrt(2)) that results from applying each axis independently.\n const invLen = 1 / Math.hypot(dn, de);\n dn *= invLen;\n de *= invLen;\n computeLocalBasis(camera.center, east, north, up);\n panAccumulated.x += (north.x * dn + east.x * de) * panStep;\n panAccumulated.y += (north.y * dn + east.y * de) * panStep;\n panAccumulated.z += (north.z * dn + east.z * de) * panStep;\n }\n }\n\n // ── DOM listeners ──\n\n function onPointerDown(e: PointerEvent): void {\n canvas.setPointerCapture(e.pointerId);\n toCanvas(e);\n lastX = e.clientX;\n lastY = e.clientY;\n if (e.button === 0) {\n mode = \"pan\";\n startDrag(pointerX, pointerY);\n } else {\n mode = \"rotate\";\n }\n }\n\n function onPointerMove(e: PointerEvent): void {\n toCanvas(e);\n const dx = e.clientX - lastX;\n const dy = e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n // While two fingers are down the gesture is a pinch (handled by the touch\n // listeners); suppress the pointer-driven pan/rotate the first finger would\n // otherwise trigger. lastX/Y above stay current so the remaining finger\n // doesn't jump when one lifts.\n if (activeTouches.size >= 2) {\n return;\n }\n if (mode === \"none\") {\n return;\n }\n activeInput = true;\n if (mode === \"pan\") {\n handleDrag(pointerX, pointerY);\n } else if (mode === \"rotate\") {\n rotationAccumulated.y += dx; // yaw\n rotationAccumulated.x += dy; // pitch\n }\n }\n\n function onPointerUp(e: PointerEvent): void {\n canvas.releasePointerCapture(e.pointerId);\n if (mode === \"pan\") {\n stopDrag();\n }\n mode = \"none\";\n }\n\n function onWheel(e: WheelEvent): void {\n e.preventDefault();\n toCanvas(e);\n handleZoom(-Math.sign(e.deltaY), true);\n }\n\n function onContextMenu(e: Event): void {\n e.preventDefault();\n }\n\n function onDoubleClick(e: MouseEvent): void {\n // Only respond to a double tap from the primary pointer (left button), and ignore it if\n // any other button is still pressed (e.g. right button held for rotation while double-tapping\n // left). `e.buttons` is a bitmask of currently-pressed buttons. This mirrors Babylon.js's\n // GeospatialCameraPointersInput primary-pointer guard.\n if (e.button !== 0 || e.buttons !== 0) {\n return;\n }\n // Suppress browser defaults for the gesture (text selection / page zoom) now that it\n // is recognised as a primary-pointer double tap.\n e.preventDefault();\n const r = canvas.getBoundingClientRect();\n const pick = pickScreen(e.clientX - r.left, e.clientY - r.top);\n if (pick.hit && pick.point) {\n void flyGeospatialCameraToAsync(camera, scene, {\n center: { x: pick.point.x, y: pick.point.y, z: pick.point.z },\n durationMs: doubleTapAnimationDurationMs,\n ease: doubleTapEasingFunction ?? undefined,\n });\n }\n }\n\n function onKeyDown(e: KeyboardEvent): void {\n keysDown.add(e.code);\n }\n\n function onKeyUp(e: KeyboardEvent): void {\n keysDown.delete(e.code);\n }\n\n // ── Touch (two-finger pinch = zoom toward centroid; ≥20 px centroid drift = pan) ──\n\n function firstTwoTouches(): [{ x: number; y: number }, { x: number; y: number }] {\n const it = activeTouches.values();\n const a = it.next().value as { x: number; y: number };\n const b = it.next().value as { x: number; y: number };\n return [a, b];\n }\n\n function onTouchStart(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n const t = e.changedTouches[i]!;\n activeTouches.set(t.identifier, { x: t.clientX, y: t.clientY });\n }\n if (activeTouches.size >= 2) {\n // A second finger landed: this is a pinch, not a single-finger pan.\n // Cancel any in-progress pointer drag and stop the browser hijacking\n // the gesture as a page zoom (iOS ignores touch-action for pinch).\n e.preventDefault();\n if (mode === \"pan\") {\n stopDrag();\n }\n mode = \"none\";\n const [p0, p1] = firstTwoTouches();\n pinchPrevDist = Math.hypot(p1.x - p0.x, p1.y - p0.y);\n pinchStartCentroidX = (p0.x + p1.x) / 2;\n pinchStartCentroidY = (p0.y + p1.y) / 2;\n pinchPanning = false;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n const t = e.changedTouches[i]!;\n if (activeTouches.has(t.identifier)) {\n activeTouches.set(t.identifier, { x: t.clientX, y: t.clientY });\n }\n }\n if (activeTouches.size < 2) {\n return;\n }\n e.preventDefault();\n activeInput = true;\n const [p0, p1] = firstTwoTouches();\n const dist2 = Math.hypot(p1.x - p0.x, p1.y - p0.y);\n const centroidClientX = (p0.x + p1.x) / 2;\n const centroidClientY = (p0.y + p1.y) / 2;\n const rect = canvas.getBoundingClientRect();\n pointerX = centroidClientX - rect.left;\n pointerY = centroidClientY - rect.top;\n\n const centroidDrift = Math.hypot(centroidClientX - pinchStartCentroidX, centroidClientY - pinchStartCentroidY);\n if (!pinchPanning && centroidDrift > PINCH_PAN_THRESHOLD) {\n pinchPanning = true;\n startDrag(pointerX, pointerY);\n }\n\n if (pinchPanning) {\n // Pan dominates: drag the globe under the centroid (zoom is suppressed\n // while dragging, mirroring the mid-drag zoom lockout).\n handleDrag(pointerX, pointerY);\n } else if (pinchPrevDist > 0) {\n // Zoom toward the centroid: finger-spread Δpx feeds the zoom accumulator.\n const dDist = dist2 - pinchPrevDist;\n if (dDist !== 0) {\n zoomAccumulated += dDist * PINCH_ZOOM_SCALE;\n const pick = pickScreen(pointerX, pointerY);\n computedZoomPickPoint = pick.hit && pick.point && zoomToCursor ? pick.point : pickAlongVector(camera._lookAt);\n }\n }\n pinchPrevDist = dist2;\n }\n\n function onTouchEnd(e: TouchEvent): void {\n for (let i = 0; i < e.changedTouches.length; i++) {\n activeTouches.delete(e.changedTouches[i]!.identifier);\n }\n if (activeTouches.size < 2) {\n if (pinchPanning) {\n stopDrag();\n pinchPanning = false;\n }\n pinchPrevDist = 0;\n }\n // One finger remains: re-anchor lastX/Y so its pointer drag doesn't jump.\n if (activeTouches.size === 1) {\n const p = activeTouches.values().next().value as { x: number; y: number };\n lastX = p.x;\n lastY = p.y;\n }\n }\n\n // iOS Safari fires non-standard gesture* events and still page-zooms even with\n // touch-action:none; swallow them so the pinch stays with the camera.\n function onGesture(e: Event): void {\n e.preventDefault();\n }\n\n scene._beforeRender.push(onBeforeRenderTick);\n\n const listeners: [EventTarget, string, EventListener, AddEventListenerOptions?][] = [\n [canvas, \"pointerdown\", onPointerDown as EventListener],\n [canvas, \"pointermove\", onPointerMove as EventListener],\n [canvas, \"pointerup\", onPointerUp as EventListener],\n [canvas, \"wheel\", onWheel as EventListener, { passive: false }],\n [canvas, \"contextmenu\", onContextMenu as EventListener],\n [canvas, \"dblclick\", onDoubleClick as EventListener],\n [canvas, \"touchstart\", onTouchStart as EventListener, { passive: false }],\n [canvas, \"touchmove\", onTouchMove as EventListener, { passive: false }],\n [canvas, \"touchend\", onTouchEnd as EventListener],\n [canvas, \"gesturestart\", onGesture as EventListener, { passive: false }],\n [canvas, \"gesturechange\", onGesture as EventListener, { passive: false }],\n [canvas, \"gestureend\", onGesture as EventListener, { passive: false }],\n [window, \"keydown\", onKeyDown as EventListener],\n [window, \"keyup\", onKeyUp as EventListener],\n ];\n for (const [t, ev, h, opts] of listeners) {\n t.addEventListener(ev, h, opts);\n }\n\n return () => {\n const idx = scene._beforeRender.indexOf(onBeforeRenderTick);\n if (idx >= 0) {\n scene._beforeRender.splice(idx, 1);\n }\n for (const [t, ev, h] of listeners) {\n t.removeEventListener(ev, h);\n }\n };\n}\n\n// ── shared helpers ──\n\nfunction clampNum(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nfunction dist(a: Vec3, b: Vec3): number {\n return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);\n}\n\n/** Transform a point by a Mat4 (row-vector × column-major matrix, with w divide). */\nfunction transformCoordinates(x: number, y: number, z: number, m: Mat4Storage, out: Vec3): void {\n const rx = x * m[0]! + y * m[4]! + z * m[8]! + m[12]!;\n const ry = x * m[1]! + y * m[5]! + z * m[9]! + m[13]!;\n const rz = x * m[2]! + y * m[6]! + z * m[10]! + m[14]!;\n const rw = x * m[3]! + y * m[7]! + z * m[11]! + m[15]!;\n const inv = rw !== 0 ? 1 / rw : 1;\n out.x = rx * inv;\n out.y = ry * inv;\n out.z = rz * inv;\n}\n"],"names":[],"mappings":"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"}
@@ -40,6 +40,7 @@ function flyGeospatialCameraToAsync(camera, scene, options) {
40
40
  camera._cancelFly?.();
41
41
  const duration = Math.max(1, options.durationMs ?? 1e3);
42
42
  const hopScale = options.centerHopScale ?? 0;
43
+ const ease = options.ease ?? easeInOut;
43
44
  const yaw0 = camera.yaw;
44
45
  const pitch0 = camera.pitch;
45
46
  const radius0 = camera.radius;
@@ -56,7 +57,7 @@ function flyGeospatialCameraToAsync(camera, scene, options) {
56
57
  const driver = (deltaMs) => {
57
58
  elapsed += deltaMs > 0 ? deltaMs : 1e3 / 60;
58
59
  const g = Math.min(1, elapsed / duration);
59
- const e = easeInOut(g);
60
+ const e = g >= 1 ? 1 : ease(g);
60
61
  const yaw = yaw0 + (targetYaw - yaw0) * e;
61
62
  const pitch = pitch0 + (targetPitch - pitch0) * e;
62
63
  const radius = radius0 + (targetRadius - radius0) * e;