@navaramap/three-plugins 0.0.8 → 0.1.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.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2026 MapLibre contributors
1
+ Copyright (c) 2026 Navara contributors
2
2
 
3
3
  This project is dual-licensed under either of
4
4
 
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  Optional feature plugins for `@navaramap/three`. Each plugin packages a self-contained capability on top of the core view:
4
4
 
5
- - **`PersonViewPlugin`** — first-person walk mode with a controllable character (GLTF model, animations, key bindings, teleport, view-mode switching).
6
- - **`OverlayPlugin`** — anchors DOM elements to geographic positions and keeps them projected to screen space as the camera moves.
7
- - **`CesiumIonPlugin`** — resolves a Cesium Ion asset endpoint and registers the asset as a quantized-mesh terrain layer via `addTerrain()`.
8
- - **`TileJsonPlugin`** — fetches a TileJSON 3.0.0 document and registers it as a Navara source, surfacing its attribution through the view's built-in credit UI.
5
+ - **`PersonViewPlugin`**: first-person walk mode with a controllable character (GLTF model, animations, key bindings, teleport, view-mode switching).
6
+ - **`OverlayPlugin`**: anchors DOM elements to geographic positions and keeps them projected to screen space as the camera moves.
7
+ - **`CesiumIonPlugin`**: resolves a Cesium Ion asset endpoint and registers the asset as a quantized-mesh terrain layer via `addTerrain()`.
8
+ - **`TileJsonPlugin`**: fetches a TileJSON 3.0.0 document and registers it as a Navara source, surfacing its attribution through the view's built-in credit UI.
9
9
 
10
10
  ## Usage
11
11
 
@@ -51,7 +51,7 @@ overlay.onUpdate(({ projected }) => {
51
51
 
52
52
  ## Documentation
53
53
 
54
- Each plugin's options and events are documented at https://navara-docs.reearth.workers.dev//.
54
+ Each plugin's options and events are documented at https://navara-docs.reearth.workers.dev/.
55
55
 
56
56
  ## License
57
57
 
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { MeshHandle } from '@navaramap/three';
6
6
  import { Plugin as Plugin_2 } from '@navaramap/three';
7
7
  import { QuantizedMeshSource } from '@navaramap/three';
8
8
  import { Source } from '@navaramap/three';
9
+ import { SourceRef } from '@navaramap/three';
9
10
  import { TerrainSourceLayer } from '@navaramap/three';
10
11
  import { ViewContext } from '@navaramap/three';
11
12
 
@@ -122,9 +123,9 @@ export declare type CollisionConfig = {
122
123
  */
123
124
  slopeSampleDistance?: number;
124
125
  /**
125
- * Largest tilt (radians) {@link alignToSlope} may apply, so ground far
126
+ * Largest tilt (degrees) {@link alignToSlope} may apply, so ground far
126
127
  * steeper than anything walkable does not lay the character flat against it.
127
- * @defaultValue `Math.PI / 4` (45°)
128
+ * @defaultValue `45`
128
129
  */
129
130
  maxSlopeTilt?: number;
130
131
  /**
@@ -252,7 +253,7 @@ export declare type PersonViewConfig = {
252
253
  maxAlt?: number;
253
254
  cameraDistance?: number;
254
255
  /**
255
- * Downward camera pitch in radians for **TPV**. `0` keeps the camera
256
+ * Downward camera pitch in degrees for **TPV**. `0` keeps the camera
256
257
  * behind the model at eye level; positive values orbit the camera up and
257
258
  * over so it looks down at the model while keeping it centered. Has no
258
259
  * effect while the free camera is active (Alt-hold or `allowCameraControl`),
@@ -269,7 +270,7 @@ export declare type PersonViewConfig = {
269
270
  */
270
271
  fpvHeightOffset?: number;
271
272
  /**
272
- * Downward camera pitch in radians for **FPV**. `0` looks straight ahead
273
+ * Downward camera pitch in degrees for **FPV**. `0` looks straight ahead
273
274
  * (horizontal); positive values tilt the view down in place without moving
274
275
  * the eye. Has no effect while the free camera is active.
275
276
  */
@@ -277,7 +278,7 @@ export declare type PersonViewConfig = {
277
278
  startLat?: number;
278
279
  startLng?: number;
279
280
  startHeight?: number;
280
- /** radians */
281
+ /** Heading in degrees (0 = north, increasing clockwise). */
281
282
  startHeading?: number;
282
283
  keys?: KeyBindings;
283
284
  };
@@ -310,6 +311,13 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
310
311
  private animId;
311
312
  /** Whether the character has been placed, so `start()` resumes from here. */
312
313
  private placed;
314
+ /**
315
+ * Height resolved by {@link resolveStartHeight}, held against the terrain
316
+ * collision until the first movement input. The coarse tiles that stream in
317
+ * right after load would otherwise pull the character off the resolved
318
+ * height and onto their own, tens of meters away.
319
+ */
320
+ private pinnedStartHeight?;
313
321
  private lastTime;
314
322
  private state;
315
323
  private listeners;
@@ -321,7 +329,9 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
321
329
  private orbitKeyHeld;
322
330
  private orbitLatched;
323
331
  private currentAnimState;
332
+ /** Character heading in radians — public heading APIs convert at the boundary. */
324
333
  private modelHeading;
334
+ /** Camera heading in radians. */
325
335
  private cameraHeading;
326
336
  private viewMode;
327
337
  /** Set to `true` to ignore all movement keys (e.g. while a modal is open). */
@@ -355,6 +365,33 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
355
365
  private groundSurfaceHeight?;
356
366
  constructor(config?: PersonViewConfig);
357
367
  init(view: View, _ctx: ViewContext): Promise<void>;
368
+ /**
369
+ * Pin `startHeight` to the terrain surface at the start position, sampled
370
+ * from the source's **most detailed** data — so `start()` places the
371
+ * character on the ground without a hand-measured `startHeight`, and
372
+ * without the initial settle once finer terrain tiles stream in. A one-shot
373
+ * sample fits here because the start position is fixed; the per-frame
374
+ * collision keeps following the terrain afterwards.
375
+ *
376
+ * The resolved height is **held until the first movement input** (or
377
+ * {@link teleport}): the render-resident tiles right after load are coarse,
378
+ * and letting the collision follow them would pull the character off the
379
+ * resolved height and onto theirs. Movement hands the altitude over to the
380
+ * normal terrain following.
381
+ *
382
+ * Call between `view.init()` (the sampler needs the initialized view) and
383
+ * {@link start}. Once the character has been placed this no longer moves
384
+ * it — use {@link teleport} with the returned height instead.
385
+ *
386
+ * @param source - A registered `quantized-mesh` or `raster-dem` source
387
+ * (handle or id) to sample, always explicit — the same contract as
388
+ * `ThreeView.sampleTerrainMostDetailed`.
389
+ * @returns The resolved start height in meters, including the collision's
390
+ * `groundOffset` (matching what the collision itself enforces), or
391
+ * `undefined` when the source has no height at the start position (the
392
+ * configured `startHeight` is then kept).
393
+ */
394
+ resolveStartHeight(source: SourceRef): Promise<number | undefined>;
358
395
  /**
359
396
  * Take over the camera and start reading the movement keys. Calling it again
360
397
  * after {@link stop} resumes from wherever the character was left, rather
@@ -397,28 +434,28 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
397
434
  /** Current terrain collision settings. */
398
435
  getCollision(): Readonly<ResolvedCollision>;
399
436
  /**
400
- * Rotate the character to the given heading in radians (0 = north,
437
+ * Rotate the character to the given heading in degrees (0 = north,
401
438
  * increasing clockwise) without changing position. Snaps the chase camera to
402
439
  * match; in free-camera mode only the model rotates.
403
440
  */
404
- setHeading(radians: number): void;
405
- /** Current character heading in radians. */
441
+ setHeading(degrees: number): void;
442
+ /** Current character heading in degrees. */
406
443
  getHeading(): number;
407
444
  /**
408
- * Set the downward TPV camera pitch in radians (0 = behind at eye level,
445
+ * Set the downward TPV camera pitch in degrees (0 = behind at eye level,
409
446
  * positive orbits up and over the model). Takes effect immediately for the
410
447
  * chase / locked camera.
411
448
  */
412
- setCameraPitch(radians: number): void;
413
- /** Current downward TPV camera pitch in radians. */
449
+ setCameraPitch(degrees: number): void;
450
+ /** Current downward TPV camera pitch in degrees. */
414
451
  getCameraPitch(): number;
415
452
  /**
416
- * Set the downward FPV camera pitch in radians (0 = horizontal, positive
453
+ * Set the downward FPV camera pitch in degrees (0 = horizontal, positive
417
454
  * tilts the view down in place). Takes effect immediately for the chase /
418
455
  * locked camera.
419
456
  */
420
- setFpvPitch(radians: number): void;
421
- /** Current downward FPV camera pitch in radians. */
457
+ setFpvPitch(degrees: number): void;
458
+ /** Current downward FPV camera pitch in degrees. */
422
459
  getFpvPitch(): number;
423
460
  /**
424
461
  * Set the FPV eye height offset in meters (added to the position to get the
@@ -508,11 +545,11 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
508
545
  private composeCharacterFrame;
509
546
  /** Eye-line height (meters) for a character standing at `alt`. */
510
547
  private eyeHeightAt;
511
- /** Terrain height at a position in radians, or `undefined` if not loaded. */
548
+ /** Terrain height at a position in degrees, or `undefined` if not loaded. */
512
549
  private sampleTerrain;
513
550
  /**
514
551
  * Apply terrain collision to a candidate altitude (meters). Positions are in
515
- * radians. `deltaTime` drives the absorption of terrain data changes — pass
552
+ * degrees. `deltaTime` drives the absorption of terrain data changes — pass
516
553
  * `0` to land on the terrain the sampler reports right now.
517
554
  */
518
555
  private resolveGroundHeight;
@@ -532,13 +569,13 @@ export declare class PersonViewPlugin extends Plugin_2<View, ViewContext> {
532
569
  private forgetGroundSamples;
533
570
  /**
534
571
  * Refresh the terrain gradient the slope tilt is built from, by probing a
535
- * ring of points around the character (position in radians, `alt` in meters).
572
+ * ring of points around the character (position in degrees, `alt` in meters).
536
573
  * `deltaTime` drives the smoothing — pass `0` to snap.
537
574
  */
538
575
  private updateSlopeTilt;
539
576
  /**
540
577
  * Terrain gradient (rise per meter, east and north) under a point given in
541
- * radians, fitted to a ring of probes, or `undefined` while any of them is
578
+ * degrees, fitted to a ring of probes, or `undefined` while any of them is
542
579
  * over terrain that has not loaded. The probes sit a few meters apart, so
543
580
  * they share one ENU frame rather than each re-deriving its own.
544
581
  */
@@ -557,7 +594,7 @@ export declare type PersonViewState = {
557
594
  lng: number;
558
595
  lat: number;
559
596
  alt: number;
560
- /** Heading in radians — 0 = north, increasing clockwise. */
597
+ /** Heading in degrees — 0 = north, increasing clockwise. */
561
598
  heading: number;
562
599
  speed: number;
563
600
  /** Current animation clip name, or null when no character is configured. */
@@ -585,7 +622,7 @@ export declare type TeleportOptions = {
585
622
  /** Altitude in meters. */
586
623
  alt: number;
587
624
  /**
588
- * Heading in radians (0 = north, increasing clockwise). If omitted, the
625
+ * Heading in degrees (0 = north, increasing clockwise). If omitted, the
589
626
  * current camera heading is kept. (Use `setHeading` to rotate in place
590
627
  * without moving, and `setCameraPitch` / `setFpvPitch` for camera pitch.)
591
628
  */
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var p = {
24
24
  groundOffset: 0,
25
25
  alignToSlope: !0,
26
26
  slopeSampleDistance: 4,
27
- maxSlopeTilt: Math.PI / 4,
27
+ maxSlopeTilt: 45,
28
28
  cameraSlopeFollow: 1
29
29
  }, g = 10, _ = 6, v = Math.PI / 4, y = [
30
30
  0,
@@ -59,7 +59,7 @@ var p = {
59
59
  startLat: 35.6812,
60
60
  startLng: 139.7671,
61
61
  startHeight: 500,
62
- startHeading: Math.PI * 1.3
62
+ startHeading: 234
63
63
  }, S = 3, C = 1e3, w = class extends n {
64
64
  view;
65
65
  config;
@@ -71,6 +71,7 @@ var p = {
71
71
  modelRef = null;
72
72
  animId = null;
73
73
  placed = !1;
74
+ pinnedStartHeight;
74
75
  lastTime = 0;
75
76
  state;
76
77
  listeners = /* @__PURE__ */ new Set();
@@ -114,7 +115,7 @@ var p = {
114
115
  }, this.character = t ? this.resolveCharacter(t) : null, this.keys = {
115
116
  ...m,
116
117
  ...n
117
- }, this.keyToAction = this.buildKeyMap(this.keys), this.modelHeading = this.config.startHeading, this.cameraHeading = this.config.startHeading, this.viewMode = this.config.initialView, this.currentAnimState = this.character ? this.character.animation.idleClip : null, this.boundKeyDown = this.onKeyDown.bind(this), this.boundKeyUp = this.onKeyUp.bind(this);
118
+ }, this.keyToAction = this.buildKeyMap(this.keys), this.modelHeading = u.degToRad(this.config.startHeading), this.cameraHeading = this.modelHeading, this.viewMode = this.config.initialView, this.currentAnimState = this.character ? this.character.animation.idleClip : null, this.boundKeyDown = this.onKeyDown.bind(this), this.boundKeyUp = this.onKeyUp.bind(this);
118
119
  }
119
120
  async init(e, t) {
120
121
  this.view = e, this.state = {
@@ -127,6 +128,20 @@ var p = {
127
128
  mode: this.viewMode
128
129
  };
129
130
  }
131
+ async resolveStartHeight(e) {
132
+ let t = this.view;
133
+ if (!t) return;
134
+ let { startLat: n, startLng: r } = this.config, [i] = await t.sampleTerrainMostDetailed(e, [{
135
+ lat: n,
136
+ lng: r
137
+ }]);
138
+ if (i?.height === void 0) return;
139
+ let { mode: a, groundOffset: o } = this.collision, s = i.height + (a === "off" ? 0 : o);
140
+ return this.config.startHeight = s, this.placed || (this.state = {
141
+ ...this.state,
142
+ alt: s
143
+ }, this.pinnedStartHeight = s, a !== "off" && (this.groundSurfaceHeight = s)), s;
144
+ }
130
145
  start() {
131
146
  if (!this.view || this.animId != null) return;
132
147
  if (this.animId = -1, document.addEventListener("keydown", this.boundKeyDown), document.addEventListener("keyup", this.boundKeyUp), this.placed) {
@@ -135,17 +150,17 @@ var p = {
135
150
  }
136
151
  this.placed = !0;
137
152
  let { startLat: e, startLng: t, startHeight: n, startHeading: r } = this.config, i = s({
138
- lat: a(e),
139
- lng: a(t),
153
+ lat: e,
154
+ lng: t,
140
155
  height: n
141
156
  });
142
- if (this.modelHeading = r, this.cameraHeading = r, this.character) {
143
- let { animation: e, modelRotationOffset: t, modelScale: n, castShadow: a, receiveShadow: o } = this.character;
157
+ if (this.modelHeading = a(r), this.cameraHeading = this.modelHeading, this.character) {
158
+ let { animation: e, modelRotationOffset: t, modelScale: n, castShadow: r, receiveShadow: a } = this.character;
144
159
  this.handle = this.view.addMesh({
145
160
  gltfModel: {
146
161
  url: this.character.modelUrl,
147
- castShadow: a,
148
- receiveShadow: o,
162
+ castShadow: r,
163
+ receiveShadow: a,
149
164
  animationEnabled: !0,
150
165
  animationAutoPlay: !0,
151
166
  animationActiveClip: e.idleClip,
@@ -153,7 +168,7 @@ var p = {
153
168
  animationLoop: !0,
154
169
  animationCrossfadeDuration: e.crossfadeDuration
155
170
  },
156
- matrixWorld: this.composeCharacterFrame(i, r),
171
+ matrixWorld: this.composeCharacterFrame(i, this.modelHeading),
157
172
  rotation: {
158
173
  x: t.x,
159
174
  y: t.y,
@@ -188,22 +203,23 @@ var p = {
188
203
  }
189
204
  teleport(e) {
190
205
  if (!this.view) return;
191
- let { lng: t, lat: n } = e, r = e.heading == null ? this.cameraHeading : e.heading, i = {
192
- lat: a(n),
193
- lng: a(t)
194
- }, o = this.resolveGroundHeight(i, i, e.alt, 0);
195
- if (this.updateSlopeTilt(i.lat, i.lng, o, 0), this.handle && this.character) {
206
+ this.pinnedStartHeight = void 0;
207
+ let { lng: t, lat: n } = e, r = e.heading == null ? c(this.cameraHeading) : e.heading, i = a(r), o = {
208
+ lat: n,
209
+ lng: t
210
+ }, l = this.resolveGroundHeight(o, o, e.alt, 0);
211
+ if (this.updateSlopeTilt(o.lat, o.lng, l, 0), this.handle && this.character) {
196
212
  let e = s({
197
- lat: i.lat,
198
- lng: i.lng,
199
- height: o
213
+ lat: o.lat,
214
+ lng: o.lng,
215
+ height: l
200
216
  });
201
- this.handle.update({ matrixWorld: this.composeCharacterFrame(e, r) });
217
+ this.handle.update({ matrixWorld: this.composeCharacterFrame(e, i) });
202
218
  }
203
- this.modelHeading = r, this.cameraHeading = r, this.placeChaseCamera(n, t, o, r), this.state = {
219
+ this.modelHeading = i, this.cameraHeading = i, this.placeChaseCamera(n, t, l, i), this.state = {
204
220
  lng: t,
205
221
  lat: n,
206
- alt: o,
222
+ alt: l,
207
223
  heading: r,
208
224
  speed: 0,
209
225
  animationState: this.currentAnimState,
@@ -242,16 +258,16 @@ var p = {
242
258
  }
243
259
  setHeading(e) {
244
260
  if (!this.view) return;
245
- let { lat: t, lng: n, alt: r } = this.state;
246
- if (this.modelHeading = e, this.cameraHeading = e, this.handle && this.character) {
247
- let i = s({
248
- lat: a(t),
249
- lng: a(n),
261
+ let { lat: t, lng: n, alt: r } = this.state, i = a(e);
262
+ if (this.modelHeading = i, this.cameraHeading = i, this.handle && this.character) {
263
+ let e = s({
264
+ lat: t,
265
+ lng: n,
250
266
  height: r
251
267
  });
252
- this.handle.update({ matrixWorld: this.composeCharacterFrame(i, e) });
268
+ this.handle.update({ matrixWorld: this.composeCharacterFrame(e, i) });
253
269
  }
254
- this.isFreeCamera() || this.placeChaseCamera(t, n, r, e), this.state = {
270
+ this.isFreeCamera() || this.placeChaseCamera(t, n, r, i), this.state = {
255
271
  ...this.state,
256
272
  heading: e
257
273
  }, this.notify();
@@ -291,7 +307,7 @@ var p = {
291
307
  return t ? e === t.dashClip ? t.dashSpeed ?? t.speed ?? 1 : t.walkClip != null && e === t.walkClip ? t.walkSpeed ?? t.speed ?? 1 : t.idleSpeed ?? t.speed ?? 1 : 1;
292
308
  }
293
309
  dispose() {
294
- this.releaseControl(), this.handle &&= (this.handle.delete(), null), this.modelRef = null, this.placed = !1, this.listeners.clear(), this.actionListeners.clear(), this.view = void 0;
310
+ this.releaseControl(), this.handle &&= (this.handle.delete(), null), this.modelRef = null, this.placed = !1, this.pinnedStartHeight = void 0, this.listeners.clear(), this.actionListeners.clear(), this.view = void 0;
295
311
  }
296
312
  playAnimation(e) {
297
313
  !e || !this.character || !this.modelRef || e !== this.currentAnimState && (this.modelRef.crossFadeAnimation(this.currentAnimState ?? "", e, this.character.animation.crossfadeDuration), this.modelRef.setAnimationSpeed(this.clipSpeed(e)), this.currentAnimState = e);
@@ -329,25 +345,25 @@ var p = {
329
345
  }
330
346
  applyInitialCamera() {
331
347
  let { startLat: e, startLng: t, startHeight: n, startHeading: r } = this.config;
332
- this.placeChaseCamera(e, t, n, r);
348
+ this.placeChaseCamera(e, t, n, a(r));
333
349
  }
334
350
  placeChaseCamera(e, t, n, r, i = 0) {
335
351
  if (!this.view) return;
336
- let { cameraDistance: a, fpvPitch: o, fpvForwardOffset: s } = this.config, c = this.viewMode === "fpv", l = this.eyeHeightAt(n);
337
- if (c) {
338
- let a = C, c = s + C, u = this.followSlopeWithPitch(o, e, t, n, r, i), d = Math.tan(u) * a, f = this.advanceLatLng(e, t, r, c);
339
- this._offset.set(-Math.sin(r) * a, -Math.cos(r) * a, d), this.view.cameraFollow(!0, {
340
- lat: f.lat,
341
- lng: f.lng,
342
- height: l - d
352
+ let { cameraDistance: o, fpvPitch: s, fpvForwardOffset: c } = this.config, l = this.viewMode === "fpv", u = this.eyeHeightAt(n);
353
+ if (l) {
354
+ let o = C, l = c + C, d = this.followSlopeWithPitch(a(s), e, t, n, r, i), f = Math.tan(d) * o, p = this.advanceLatLng(e, t, r, l);
355
+ this._offset.set(-Math.sin(r) * o, -Math.cos(r) * o, f), this.view.cameraFollow(!0, {
356
+ lat: p.lat,
357
+ lng: p.lng,
358
+ height: u - f
343
359
  }, this._offset);
344
360
  return;
345
361
  }
346
- let u = this.followSlopeWithPitch(this.config.cameraPitch, e, t, n, r, i), d = a * Math.cos(u), f = a * Math.sin(u);
347
- this._offset.set(-Math.sin(r) * d, -Math.cos(r) * d, f), this.view.cameraFollow(!0, {
362
+ let d = this.followSlopeWithPitch(a(this.config.cameraPitch), e, t, n, r, i), f = o * Math.cos(d), p = o * Math.sin(d);
363
+ this._offset.set(-Math.sin(r) * f, -Math.cos(r) * f, p), this.view.cameraFollow(!0, {
348
364
  lat: e,
349
365
  lng: t,
350
- height: l
366
+ height: u
351
367
  }, this._offset);
352
368
  }
353
369
  followSlopeWithPitch(e, t, n, r, i, a) {
@@ -364,15 +380,15 @@ var p = {
364
380
  }
365
381
  terrainAlong(e, t, n, r) {
366
382
  let i = this.advanceLatLng(e, t, n, r);
367
- return this.sampleTerrain(a(i.lat), a(i.lng));
383
+ return this.sampleTerrain(i.lat, i.lng);
368
384
  }
369
385
  composeCharacterFrame(e, t) {
370
386
  let n = o(e);
371
387
  this._headingMatrix.makeRotationZ(-t), this._characterFrame.multiplyMatrices(n, this._headingMatrix);
372
388
  let r = this.slopeGradEast, i = this.slopeGradNorth;
373
389
  if (r !== 0 || i !== 0) {
374
- let e = Math.sin(t), n = Math.cos(t), a = this.collision.maxSlopeTilt, o = u.clamp(Math.atan(r * e + i * n), -a, a), s = u.clamp(-Math.atan(r * n - i * e), -a, a);
375
- this._tiltMatrix.makeRotationX(o), this._characterFrame.multiply(this._tiltMatrix), this._tiltMatrix.makeRotationY(s), this._characterFrame.multiply(this._tiltMatrix);
390
+ let e = Math.sin(t), n = Math.cos(t), o = a(this.collision.maxSlopeTilt), s = u.clamp(Math.atan(r * e + i * n), -o, o), c = u.clamp(-Math.atan(r * n - i * e), -o, o);
391
+ this._tiltMatrix.makeRotationX(s), this._characterFrame.multiply(this._tiltMatrix), this._tiltMatrix.makeRotationY(c), this._characterFrame.multiply(this._tiltMatrix);
376
392
  }
377
393
  return this._characterFrame;
378
394
  }
@@ -446,15 +462,15 @@ var p = {
446
462
  }
447
463
  advanceLatLng(e, t, n, r) {
448
464
  let i = s({
449
- lat: a(e),
450
- lng: a(t),
465
+ lat: e,
466
+ lng: t,
451
467
  height: 0
452
- }), u = o(i);
453
- this._east.setFromMatrixColumn(u, 0).normalize(), this._north.setFromMatrixColumn(u, 1).normalize(), this._worldForward.copy(this._east).multiplyScalar(Math.sin(n)).addScaledVector(this._north, Math.cos(n)), i.addScaledVector(this._worldForward, r);
454
- let d = l(i);
468
+ }), a = o(i);
469
+ this._east.setFromMatrixColumn(a, 0).normalize(), this._north.setFromMatrixColumn(a, 1).normalize(), this._worldForward.copy(this._east).multiplyScalar(Math.sin(n)).addScaledVector(this._north, Math.cos(n)), i.addScaledVector(this._worldForward, r);
470
+ let c = l(i);
455
471
  return {
456
- lat: c(d.lat),
457
- lng: c(d.lng)
472
+ lat: c.lat,
473
+ lng: c.lng
458
474
  };
459
475
  }
460
476
  notify() {
@@ -482,7 +498,7 @@ var p = {
482
498
  this.dashHeld = !0;
483
499
  return;
484
500
  }
485
- this.isMovementAction(n) && (this.orbitLatched = !1), this.heldActions.add(n);
501
+ this.isMovementAction(n) && (this.orbitLatched = !1, this.pinnedStartHeight = void 0), this.heldActions.add(n);
486
502
  }
487
503
  }
488
504
  }
@@ -515,15 +531,15 @@ var p = {
515
531
  if (!e) return;
516
532
  v.copy(e);
517
533
  } else v.copy(s({
518
- lat: a(this.state.lat),
519
- lng: a(this.state.lng),
534
+ lat: this.state.lat,
535
+ lng: this.state.lng,
520
536
  height: this.state.alt
521
537
  }));
522
538
  let y = o(v);
523
539
  this._east.setFromMatrixColumn(y, 0).normalize(), this._north.setFromMatrixColumn(y, 1).normalize();
524
540
  let x = l(v);
525
541
  this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north, Math.cos(this.modelHeading)), r !== 0 && v.addScaledVector(this._worldForward, d * _ * t * r);
526
- let S = l(v), C = this.collision.mode === "ground" ? x.height : u.clamp(x.height + i * p * t, m, h), w = this.resolveGroundHeight(S, x, C, t), T = c(S.lat), E = c(S.lng), D = w;
542
+ let S = l(v), C = this.collision.mode === "ground" ? x.height : u.clamp(x.height + i * p * t, m, h), w = this.pinnedStartHeight ?? this.resolveGroundHeight(S, x, C, t), T = S.lat, E = S.lng, D = w;
527
543
  if (this.updateSlopeTilt(S.lat, S.lng, w, t), this.handle && this.character) {
528
544
  let e = s({
529
545
  lat: S.lat,
@@ -556,7 +572,7 @@ var p = {
556
572
  lng: E,
557
573
  lat: T,
558
574
  alt: D,
559
- heading: this.modelHeading,
575
+ heading: c(this.modelHeading),
560
576
  speed: d * _,
561
577
  animationState: j,
562
578
  mode: this.viewMode
@@ -597,36 +613,36 @@ var E = 1e5, D = class extends n {
597
613
  worldToScreen(e, t, n) {
598
614
  if (!this.view) return null;
599
615
  let r = s({
600
- lng: a(e),
601
- lat: a(t),
616
+ lng: e,
617
+ lat: t,
602
618
  height: n
603
- }), o = {
619
+ }), a = {
604
620
  width: this.view.screenSize.x,
605
621
  height: this.view.screenSize.y,
606
622
  pixelRatio: this.view.pixelRatio
607
- }, c = i(o, this.view.camera.raw, r);
608
- return c ? {
609
- x: c.x,
610
- y: c.y
623
+ }, o = i(a, this.view.camera.raw, r);
624
+ return o ? {
625
+ x: o.x,
626
+ y: o.y
611
627
  } : null;
612
628
  }
613
629
  handlePreRender() {
614
630
  if (!this.view) return;
615
631
  let e = this.view.camera.positionGeographic, t = s({
616
- lng: a(e.lng),
617
- lat: a(e.lat),
632
+ lng: e.lng,
633
+ lat: e.lat,
618
634
  height: e.height
619
635
  }), n = /* @__PURE__ */ new Map();
620
636
  for (let e of this.positions) {
621
637
  let r = s({
622
- lng: a(e.lng),
623
- lat: a(e.lat),
638
+ lng: e.lng,
639
+ lat: e.lat,
624
640
  height: e.alt
625
641
  }), i = t.distanceTo(r);
626
642
  if (i > this.maxDistance) continue;
627
- let o = this.worldToScreen(e.lng, e.lat, e.alt);
628
- o && n.set(e.id, {
629
- ...o,
643
+ let a = this.worldToScreen(e.lng, e.lat, e.alt);
644
+ a && n.set(e.id, {
645
+ ...a,
630
646
  distance: i
631
647
  });
632
648
  }
@@ -1 +1 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/three"),require("three")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/three`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreePlugins={},e._navaramap_three,e.three))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={x:0,y:0,z:0},d={forward:[`KeyW`],backward:[`KeyS`],turnLeft:[`KeyA`],turnRight:[`KeyD`],ascend:[`ArrowUp`,`Space`],descend:[`ArrowDown`,`ControlLeft`,`ControlRight`],dash:[`ShiftLeft`,`ShiftRight`],orbitCamera:[`AltLeft`,`AltRight`],toggleView:[`KeyV`]},f={mode:`off`,groundOffset:0,alignToSlope:!0,slopeSampleDistance:4,maxSlopeTilt:Math.PI/4,cameraSlopeFollow:1},p=10,m=6,h=Math.PI/4,g=[0,1,2,3,4,5,6,7].map(e=>{let t=e*Math.PI/4;return{sin:Math.sin(t),cos:Math.cos(t)}}),_=(e,t)=>e>0&&t>0?1-Math.exp(-e*t):1,v={allowCameraControl:!1,initialView:`tpv`,moveSpeed:50,rotationSpeed:3,altSpeed:30,dashSpeedMultiplier:2.5,minAlt:50,maxAlt:5e3,cameraDistance:50,cameraPitch:0,cameraLerpSpeed:3,fpvForwardOffset:0,fpvHeightOffset:1,fpvPitch:0,startLat:35.6812,startLng:139.7671,startHeight:500,startHeading:Math.PI*1.3},y=3,b=1e3,x=class extends t.Plugin{view;config;collision;character;keys;keyToAction;handle=null;modelRef=null;animId=null;placed=!1;lastTime=0;state;listeners=new Set;actionListeners=new Set;heldActions=new Set;dashHeld=!1;orbitKeyHeld=!1;orbitLatched=!1;currentAnimState;modelHeading;cameraHeading;viewMode;movementSuppressed=!1;boundKeyDown;boundKeyUp;_curPos=new n.Vector3;_east=new n.Vector3;_north=new n.Vector3;_worldForward=new n.Vector3;_offset=new n.Vector3;_probePos=new n.Vector3;_headingMatrix=new n.Matrix4;_tiltMatrix=new n.Matrix4;_characterFrame=new n.Matrix4;cameraSlopePitch=0;slopeGradEast=0;slopeGradNorth=0;lastGroundSample;unabsorbedGroundShift=0;settleVelocity=0;groundSurfaceHeight;constructor(e={}){super();let{character:t,keys:n,collision:r,...i}=e;this.config={...v,...i},this.collision={...f,...r},this.character=t?this.resolveCharacter(t):null,this.keys={...d,...n},this.keyToAction=this.buildKeyMap(this.keys),this.modelHeading=this.config.startHeading,this.cameraHeading=this.config.startHeading,this.viewMode=this.config.initialView,this.currentAnimState=this.character?this.character.animation.idleClip:null,this.boundKeyDown=this.onKeyDown.bind(this),this.boundKeyUp=this.onKeyUp.bind(this)}async init(e,t){this.view=e,this.state={lng:this.config.startLng,lat:this.config.startLat,alt:this.config.startHeight,heading:this.config.startHeading,speed:0,animationState:this.currentAnimState,mode:this.viewMode}}start(){if(!this.view||this.animId!=null)return;if(this.animId=-1,document.addEventListener(`keydown`,this.boundKeyDown),document.addEventListener(`keyup`,this.boundKeyUp),this.placed){this.resumeLoop(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.applyModelVisibility();return}this.placed=!0;let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config,a=(0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(e),lng:(0,t.degreeToRadian)(n),height:r});if(this.modelHeading=i,this.cameraHeading=i,this.character){let{animation:e,modelRotationOffset:t,modelScale:n,castShadow:r,receiveShadow:o}=this.character;this.handle=this.view.addMesh({gltfModel:{url:this.character.modelUrl,castShadow:r,receiveShadow:o,animationEnabled:!0,animationAutoPlay:!0,animationActiveClip:e.idleClip,animationSpeed:this.clipSpeed(e.idleClip),animationLoop:!0,animationCrossfadeDuration:e.crossfadeDuration},matrixWorld:this.composeCharacterFrame(a,i),rotation:{x:t.x,y:t.y,z:t.z},scale:{x:n,y:n,z:n}}),this.modelRef=this.handle.ref,this.modelRef.on(`load`,()=>{!this.view||this.animId==null||this.resumeLoop()})}else this.resumeLoop();this.applyInitialCamera(),this.applyModelVisibility()}stop(){if(this.animId==null)return;this.releaseControl(),this.view?.cameraFollow(!1),this.view?.cameraFreeLook(!1),this.playAnimation(this.character?.animation.idleClip);let e={...this.state,speed:0,animationState:this.currentAnimState};this.hasStateChanged(this.state,e)&&(this.state=e,this.notify())}releaseControl(){this.animId!=null&&this.animId>=0&&cancelAnimationFrame(this.animId),this.animId=null,document.removeEventListener(`keydown`,this.boundKeyDown),document.removeEventListener(`keyup`,this.boundKeyUp),this.heldActions.clear(),this.orbitKeyHeld=!1,this.orbitLatched=!1,this.dashHeld=!1}resumeLoop(){this.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick)}teleport(e){if(!this.view)return;let{lng:n,lat:r}=e,i=e.heading==null?this.cameraHeading:e.heading,a={lat:(0,t.degreeToRadian)(r),lng:(0,t.degreeToRadian)(n)},o=this.resolveGroundHeight(a,a,e.alt,0);if(this.updateSlopeTilt(a.lat,a.lng,o,0),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:a.lat,lng:a.lng,height:o});this.handle.update({matrixWorld:this.composeCharacterFrame(e,i)})}this.modelHeading=i,this.cameraHeading=i,this.placeChaseCamera(r,n,o,i),this.state={lng:n,lat:r,alt:o,heading:i,speed:0,animationState:this.currentAnimState,mode:this.viewMode},this.notify()}getState(){return this.state}onStateChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}onAction(e){return this.actionListeners.add(e),()=>this.actionListeners.delete(e)}setViewMode(e){this.viewMode!==e&&(this.viewMode=e,this.state={...this.state,mode:e},this.applyModelVisibility(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.notify())}toggleViewMode(){this.setViewMode(this.viewMode===`tpv`?`fpv`:`tpv`)}setAllowCameraControl(e){this.config.allowCameraControl=e}setCollision(e){this.collision={...this.collision,...e}}getCollision(){return this.collision}setHeading(e){if(!this.view)return;let{lat:n,lng:r,alt:i}=this.state;if(this.modelHeading=e,this.cameraHeading=e,this.handle&&this.character){let a=(0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(n),lng:(0,t.degreeToRadian)(r),height:i});this.handle.update({matrixWorld:this.composeCharacterFrame(a,e)})}this.isFreeCamera()||this.placeChaseCamera(n,r,i,e),this.state={...this.state,heading:e},this.notify()}getHeading(){return this.state.heading}setCameraPitch(e){this.config.cameraPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getCameraPitch(){return this.config.cameraPitch}setFpvPitch(e){this.config.fpvPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvPitch(){return this.config.fpvPitch}setFpvHeightOffset(e){this.config.fpvHeightOffset=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvHeightOffset(){return this.config.fpvHeightOffset}setAnimationSpeed(e){this.character&&(this.character.animation.speed=e),this.modelRef?.setAnimationSpeed(this.clipSpeed(this.currentAnimState))}getAnimationSpeed(){return this.character?.animation.speed??1}get model(){return this.handle}clipSpeed(e){let t=this.character?.animation;return t?e===t.dashClip?t.dashSpeed??t.speed??1:t.walkClip!=null&&e===t.walkClip?t.walkSpeed??t.speed??1:t.idleSpeed??t.speed??1:1}dispose(){this.releaseControl(),this.handle&&=(this.handle.delete(),null),this.modelRef=null,this.placed=!1,this.listeners.clear(),this.actionListeners.clear(),this.view=void 0}playAnimation(e){!e||!this.character||!this.modelRef||e!==this.currentAnimState&&(this.modelRef.crossFadeAnimation(this.currentAnimState??``,e,this.character.animation.crossfadeDuration),this.modelRef.setAnimationSpeed(this.clipSpeed(e)),this.currentAnimState=e)}resolveCharacter(e){return{modelUrl:e.modelUrl,animation:e.animation,modelRotationOffset:e.modelRotationOffset?{...u,...e.modelRotationOffset}:u,modelScale:e.modelScale??y,hideModelInFpv:e.hideModelInFpv??!0,castShadow:e.castShadow??!1,receiveShadow:e.receiveShadow??!1}}buildKeyMap(e){let t=new Map,n=(e,n)=>{for(let r of e)t.set(r,n)};return n(e.forward,`forward`),n(e.backward,`backward`),n(e.turnLeft,`turnLeft`),n(e.turnRight,`turnRight`),n(e.ascend,`ascend`),n(e.descend,`descend`),n(e.dash,`dash`),n(e.orbitCamera,`orbitCamera`),n(e.toggleView,`toggleView`),t}isFreeCamera(){return this.config.allowCameraControl||this.orbitKeyHeld||this.orbitLatched}isMovementAction(e){return e===`forward`||e===`backward`||e===`turnLeft`||e===`turnRight`||e===`ascend`||e===`descend`}applyModelVisibility(){if(!this.handle||!this.character)return;let e=this.viewMode===`fpv`&&this.character.hideModelInFpv;this.handle.visible=!e}applyInitialCamera(){let{startLat:e,startLng:t,startHeight:n,startHeading:r}=this.config;this.placeChaseCamera(e,t,n,r)}placeChaseCamera(e,t,n,r,i=0){if(!this.view)return;let{cameraDistance:a,fpvPitch:o,fpvForwardOffset:s}=this.config,c=this.viewMode===`fpv`,l=this.eyeHeightAt(n);if(c){let a=b,c=s+b,u=this.followSlopeWithPitch(o,e,t,n,r,i),d=Math.tan(u)*a,f=this.advanceLatLng(e,t,r,c);this._offset.set(-Math.sin(r)*a,-Math.cos(r)*a,d),this.view.cameraFollow(!0,{lat:f.lat,lng:f.lng,height:l-d},this._offset);return}let u=this.followSlopeWithPitch(this.config.cameraPitch,e,t,n,r,i),d=a*Math.cos(u),f=a*Math.sin(u);this._offset.set(-Math.sin(r)*d,-Math.cos(r)*d,f),this.view.cameraFollow(!0,{lat:e,lng:t,height:l},this._offset)}followSlopeWithPitch(e,t,r,i,a,o){let{cameraDistance:s,cameraLerpSpeed:c}=this.config,{mode:l,cameraSlopeFollow:u}=this.collision,d=0;if(l!==`off`&&u>0){let e=this.terrainAlong(t,r,a,s),o=this.terrainAlong(t,r,a+Math.PI,s);e!==void 0&&o!==void 0&&(d=n.MathUtils.clamp(Math.atan2(e-o,2*s)*u*this.groundedFactor(i,s),-h,h))}return this.cameraSlopePitch+=(d-this.cameraSlopePitch)*_(c,o),e-this.cameraSlopePitch}groundedFactor(e,t){let r=this.groundSurfaceHeight;return r===void 0?0:n.MathUtils.clamp(1-(e-r)/t,0,1)}terrainAlong(e,n,r,i){let a=this.advanceLatLng(e,n,r,i);return this.sampleTerrain((0,t.degreeToRadian)(a.lat),(0,t.degreeToRadian)(a.lng))}composeCharacterFrame(e,r){let i=(0,t.eastNorthUpToFixedFrame)(e);this._headingMatrix.makeRotationZ(-r),this._characterFrame.multiplyMatrices(i,this._headingMatrix);let a=this.slopeGradEast,o=this.slopeGradNorth;if(a!==0||o!==0){let e=Math.sin(r),t=Math.cos(r),i=this.collision.maxSlopeTilt,s=n.MathUtils.clamp(Math.atan(a*e+o*t),-i,i),c=n.MathUtils.clamp(-Math.atan(a*t-o*e),-i,i);this._tiltMatrix.makeRotationX(s),this._characterFrame.multiply(this._tiltMatrix),this._tiltMatrix.makeRotationY(c),this._characterFrame.multiply(this._tiltMatrix)}return this._characterFrame}eyeHeightAt(e){return e+this.config.fpvHeightOffset}sampleTerrain(e,t){return this.view?.sampleTerrainHeight({lat:e,lng:t})}resolveGroundHeight(e,t,n,r){let{mode:i,groundOffset:a}=this.collision;if(i===`off`)return this.forgetGroundSamples(),n;let o=this.sampleTerrain(e.lat,e.lng);if(o===void 0)return this.forgetGroundSamples(),n;this.groundSurfaceHeight=o+a;let s=this.groundSurfaceHeight-this.absorbGroundDataShift(e,t,o,n,r);return i===`ground`?s:Math.max(n,s)}absorbGroundDataShift(e,t,n,r,i){let a=this.lastGroundSample,o;if(a===void 0)o=(this.sampleTerrain(t.lat,t.lng)??n)+this.collision.groundOffset-r;else{let e=this.sampleTerrain(a.lat,a.lng);o=e===void 0?0:e-a.height}let s=this.unabsorbedGroundShift+o,c=Math.sign(s);i>0&&(c!==Math.sign(this.settleVelocity)&&(this.settleVelocity=0),this.settleVelocity+=c*p*i);let l=i>0?Math.abs(this.settleVelocity)*i:Math.abs(s);return this.unabsorbedGroundShift=c*Math.max(0,Math.abs(s)-l),this.unabsorbedGroundShift===0&&(this.settleVelocity=0),this.lastGroundSample={lat:e.lat,lng:e.lng,height:n},this.unabsorbedGroundShift}forgetGroundSamples(){this.lastGroundSample=void 0,this.unabsorbedGroundShift=0,this.settleVelocity=0,this.groundSurfaceHeight=void 0}updateSlopeTilt(e,t,n,r){let{mode:i,alignToSlope:a,slopeSampleDistance:o}=this.collision,s=0,c=0;if(i!==`off`&&a){let r=this.measureSlopeGradient(e,t);if(!r)return;let i=this.groundedFactor(n,o);s=r.east*i,c=r.north*i}else if(this.slopeGradEast===0&&this.slopeGradNorth===0)return;let l=_(m,r);this.slopeGradEast+=(s-this.slopeGradEast)*l,this.slopeGradNorth+=(c-this.slopeGradNorth)*l}measureSlopeGradient(e,n){let r=this.collision.slopeSampleDistance,i=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),a=(0,t.eastNorthUpToFixedFrame)(i);this._east.setFromMatrixColumn(a,0).normalize(),this._north.setFromMatrixColumn(a,1).normalize();let o=0,s=0;for(let{sin:e,cos:n}of g){this._probePos.copy(i).addScaledVector(this._east,e*r).addScaledVector(this._north,n*r);let a=(0,t.vector3ToGeodetic)(this._probePos),c=this.sampleTerrain(a.lat,a.lng);if(c===void 0)return;o+=c*e,s+=c*n}let c=2/(g.length*r);return{east:o*c,north:s*c}}advanceLatLng(e,n,r,i){let a=(0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(e),lng:(0,t.degreeToRadian)(n),height:0}),o=(0,t.eastNorthUpToFixedFrame)(a);this._east.setFromMatrixColumn(o,0).normalize(),this._north.setFromMatrixColumn(o,1).normalize(),this._worldForward.copy(this._east).multiplyScalar(Math.sin(r)).addScaledVector(this._north,Math.cos(r)),a.addScaledVector(this._worldForward,i);let s=(0,t.vector3ToGeodetic)(a);return{lat:(0,t.radianToDegree)(s.lat),lng:(0,t.radianToDegree)(s.lng)}}notify(){for(let e of this.listeners)e(this.state)}emitAction(e){for(let t of this.actionListeners)t(e)}onKeyDown(e){let t=e.target;if(t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.isContentEditable)return;let n=this.keyToAction.get(e.code);if(n!==void 0){if(e.repeat||this.emitAction(n),n===`toggleView`){if(e.repeat)return;this.toggleViewMode();return}if(n===`orbitCamera`){this.orbitKeyHeld=!0,this.orbitLatched=!0;return}if(!this.movementSuppressed){if(n===`dash`){this.dashHeld=!0;return}this.isMovementAction(n)&&(this.orbitLatched=!1),this.heldActions.add(n)}}}onKeyUp(e){let t=this.keyToAction.get(e.code);if(t!==void 0&&t!==`toggleView`){if(t===`orbitCamera`){this.orbitKeyHeld=!1;return}if(t===`dash`){this.dashHeld=!1;return}this.heldActions.delete(t)}}tick=e=>{if(!this.view||this.animId==null)return;this.animId=requestAnimationFrame(this.tick);let r=Math.min((e-this.lastTime)/1e3,.1);this.lastTime=e;let i=0,a=0,o=0;this.heldActions.has(`forward`)&&(a+=1),this.heldActions.has(`backward`)&&--a,this.heldActions.has(`turnLeft`)&&--i,this.heldActions.has(`turnRight`)&&(i+=1),this.heldActions.has(`ascend`)&&(o+=1),this.heldActions.has(`descend`)&&--o;let{moveSpeed:s,rotationSpeed:c,altSpeed:l,minAlt:u,maxAlt:d,cameraLerpSpeed:f}=this.config,p=this.dashHeld?this.config.dashSpeedMultiplier:1;i!==0&&(this.modelHeading+=(0,t.degreeToRadian)(c*i));let m=this._curPos;if(this.handle&&this.modelRef){let e=this.modelRef.getWorldPosition();if(!e)return;m.copy(e)}else m.copy((0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(this.state.lat),lng:(0,t.degreeToRadian)(this.state.lng),height:this.state.alt}));let h=(0,t.eastNorthUpToFixedFrame)(m);this._east.setFromMatrixColumn(h,0).normalize(),this._north.setFromMatrixColumn(h,1).normalize();let g=(0,t.vector3ToGeodetic)(m);this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north,Math.cos(this.modelHeading)),a!==0&&m.addScaledVector(this._worldForward,s*p*r*a);let v=(0,t.vector3ToGeodetic)(m),y=this.collision.mode===`ground`?g.height:n.MathUtils.clamp(g.height+o*l*r,u,d),b=this.resolveGroundHeight(v,g,y,r),x=(0,t.radianToDegree)(v.lat),S=(0,t.radianToDegree)(v.lng),C=b;if(this.updateSlopeTilt(v.lat,v.lng,b,r),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:v.lat,lng:v.lng,height:b});this.handle.update({matrixWorld:this.composeCharacterFrame(e,this.modelHeading)})}let w=this.modelHeading-this.cameraHeading;if(w-=Math.round(w/(Math.PI*2))*(Math.PI*2),this.cameraHeading+=w*_(f,r),this.isFreeCamera()){if(this.viewMode===`fpv`){let e=this.advanceLatLng(x,S,this.cameraHeading,this.config.fpvForwardOffset);this.view.cameraFreeLook(!0,{lat:e.lat,lng:e.lng,height:this.eyeHeightAt(C)})}else this.view.cameraFollow(!0,{lat:x,lng:S,height:this.eyeHeightAt(C)})}else this.placeChaseCamera(x,S,C,this.cameraHeading,r);let T=a!==0||i!==0||o!==0,E=T&&this.dashHeld,D=this.currentAnimState;if(this.character){let{animation:e}=this.character,t=e.walkClip??e.idleClip;D=E?e.dashClip:T?t:e.idleClip,this.playAnimation(D)}let O={lng:S,lat:x,alt:C,heading:this.modelHeading,speed:s*p,animationState:D,mode:this.viewMode};this.hasStateChanged(this.state,O)&&(this.state=O,this.notify())};hasStateChanged(e,t){return e.lng!==t.lng||e.lat!==t.lat||e.alt!==t.alt||e.heading!==t.heading||e.speed!==t.speed||e.animationState!==t.animationState||e.mode!==t.mode}};function S(e,t,n){e.style.transform=`translate(${t}px, ${n}px)`,e.style.willChange=`transform`}var C=1e5,w=class extends t.Plugin{view;maxDistance;positions=[];projected=new Map;listeners=new Set;boundHandler;constructor(e){super(),this.maxDistance=e?.maxDistance??C,this.boundHandler=this.handlePreRender.bind(this)}async init(e,t){this.view=e,e.on(`preRender`,this.boundHandler)}setPositions(e){this.positions=e}onUpdate(e){return this.listeners.add(e),()=>this.listeners.delete(e)}dispose(){this.view&&this.view.off(`preRender`,this.boundHandler),this.listeners.clear(),this.positions=[],this.projected.clear()}worldToScreen(e,n,r){if(!this.view)return null;let i=(0,t.geodeticToVector3)({lng:(0,t.degreeToRadian)(e),lat:(0,t.degreeToRadian)(n),height:r}),a={width:this.view.screenSize.x,height:this.view.screenSize.y,pixelRatio:this.view.pixelRatio},o=(0,t.convertWorldToScreen)(a,this.view.camera.raw,i);return o?{x:o.x,y:o.y}:null}handlePreRender(){if(!this.view)return;let e=this.view.camera.positionGeographic,n=(0,t.geodeticToVector3)({lng:(0,t.degreeToRadian)(e.lng),lat:(0,t.degreeToRadian)(e.lat),height:e.height}),r=new Map;for(let e of this.positions){let i=(0,t.geodeticToVector3)({lng:(0,t.degreeToRadian)(e.lng),lat:(0,t.degreeToRadian)(e.lat),height:e.alt}),a=n.distanceTo(i);if(a>this.maxDistance)continue;let o=this.worldToScreen(e.lng,e.lat,e.alt);o&&r.set(e.id,{...o,distance:a})}this.projected=r;let i={projected:this.projected};for(let e of this.listeners)e(i)}},T=`https://api.cesium.com/v1/assets`,E=class extends t.Plugin{view;config;endpoint;constructor(e){super(),this.config=e}async init(e,t){this.view=e;let{assetId:n,accessToken:r,endpoint:i=T}=this.config,a=`${i}/${encodeURIComponent(String(n))}/endpoint?access_token=${encodeURIComponent(r)}`,o=await fetch(a);if(!o.ok)throw Error(`CesiumIonPlugin: failed to fetch asset ${n} endpoint (${o.status} ${o.statusText})`);let s=await o.json();this.endpoint={url:s.url,accessToken:s.accessToken}}addTerrain(e={}){if(!this.view||!this.endpoint)throw Error(`CesiumIonPlugin: addTerrain() must be called after view.init().`);let{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o,...s}=e,c=this.view.addSource({type:`quantized-mesh`,url:`${this.endpoint.url}{z}/{x}/{y}.terrain`,token:this.endpoint.accessToken,...s}),l=this.view.addLayer({type:`terrain`,source:c,terrain:{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o}});return l.once(`deleted`,()=>c.delete()),l}},D=/^3\.\d+\.\d+$/,O=512,k=`mapbox`;function A(e){if(typeof e.tilejson!=`string`||!D.test(e.tilejson))throw Error(`TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);if(!Array.isArray(e.tiles)||e.tiles.length===0)throw Error(`TileJsonPlugin: "tiles" must be a non-empty array of URL templates.`)}function j(e){switch(e){case`mapbox`:return(0,t.MAPBOX_ELEVATION_DECODER)();case`terrarium`:return(0,t.TERRARIUM_ELEVATION_DECODER)();default:throw Error(`TileJsonPlugin: unsupported raster-dem encoding ${JSON.stringify(e)}. Supported encodings: "mapbox", "terrarium".`)}}var M=class extends t.Plugin{view;events=new t.EventHandler;credits=[];async init(e,t){this.view=e}on(e,t){this.events.on(e,t)}once(e,t){this.events.once(e,t)}off(e,t){this.events.off(e,t)}async addSource(e){let t=this.view;if(!t)throw Error(`TileJsonPlugin: addSource() must be called after view.init().`);let n=await this.fetchTileJson(e.url);A(n);let[r,...i]=n.tiles;i.length>0&&console.warn(`TileJsonPlugin: TileJSON at ${e.url} lists ${n.tiles.length} mirror tile endpoints; Navara sources use a single URL, so only the first is used.`);let a=e.minzoom??n.minzoom,o=e.maxzoom??n.maxzoom,s=e.scheme??n.scheme,c=e.id,l={url:r,...c===void 0?{}:{id:c},...o===void 0?{}:{maxZoom:o}},u={...l,...a===void 0?{}:{minZoom:a},...s===`tms`?{tms:!0}:{}},d;return d=e.type===`raster-tile`?t.addSource({type:`raster-tile`,...u}):e.type===`raster-dem`?t.addSource({type:`raster-dem`,...u,tileSize:e.tileSize??n.tileSize??O,elevationDecoder:j(e.encoding??n.encoding??k)}):t.addSource({type:`vector-tile`,...l}),n.attribution&&this.addCredit(n.attribution),this.events.emit(`loaded`,{source:d,tilejson:n,attribution:n.attribution}),d}dispose(){let e=this.view?.attribution;e&&this.credits.length>0&&e.remove(this.credits.map(e=>({attributionHtml:e}))),this.credits.length=0,this.events.clear(`loaded`),this.view=void 0}async fetchTileJson(e){let t=await fetch(e);if(!t.ok)throw Error(`TileJsonPlugin: failed to fetch TileJSON from ${e} (${t.status} ${t.statusText})`);return await t.json()}addCredit(e){this.credits.includes(e)||(this.credits.push(e),this.view?.attribution?.add([{attributionHtml:e}]))}};e.CesiumIonPlugin=E,e.OverlayPlugin=w,e.PersonViewPlugin=x,e.TileJsonPlugin=M,e.moveOverlayElement=S});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/three"),require("three")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/three`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreePlugins={},e._navaramap_three,e.three))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={x:0,y:0,z:0},d={forward:[`KeyW`],backward:[`KeyS`],turnLeft:[`KeyA`],turnRight:[`KeyD`],ascend:[`ArrowUp`,`Space`],descend:[`ArrowDown`,`ControlLeft`,`ControlRight`],dash:[`ShiftLeft`,`ShiftRight`],orbitCamera:[`AltLeft`,`AltRight`],toggleView:[`KeyV`]},f={mode:`off`,groundOffset:0,alignToSlope:!0,slopeSampleDistance:4,maxSlopeTilt:45,cameraSlopeFollow:1},p=10,m=6,h=Math.PI/4,g=[0,1,2,3,4,5,6,7].map(e=>{let t=e*Math.PI/4;return{sin:Math.sin(t),cos:Math.cos(t)}}),_=(e,t)=>e>0&&t>0?1-Math.exp(-e*t):1,v={allowCameraControl:!1,initialView:`tpv`,moveSpeed:50,rotationSpeed:3,altSpeed:30,dashSpeedMultiplier:2.5,minAlt:50,maxAlt:5e3,cameraDistance:50,cameraPitch:0,cameraLerpSpeed:3,fpvForwardOffset:0,fpvHeightOffset:1,fpvPitch:0,startLat:35.6812,startLng:139.7671,startHeight:500,startHeading:234},y=3,b=1e3,x=class extends t.Plugin{view;config;collision;character;keys;keyToAction;handle=null;modelRef=null;animId=null;placed=!1;pinnedStartHeight;lastTime=0;state;listeners=new Set;actionListeners=new Set;heldActions=new Set;dashHeld=!1;orbitKeyHeld=!1;orbitLatched=!1;currentAnimState;modelHeading;cameraHeading;viewMode;movementSuppressed=!1;boundKeyDown;boundKeyUp;_curPos=new n.Vector3;_east=new n.Vector3;_north=new n.Vector3;_worldForward=new n.Vector3;_offset=new n.Vector3;_probePos=new n.Vector3;_headingMatrix=new n.Matrix4;_tiltMatrix=new n.Matrix4;_characterFrame=new n.Matrix4;cameraSlopePitch=0;slopeGradEast=0;slopeGradNorth=0;lastGroundSample;unabsorbedGroundShift=0;settleVelocity=0;groundSurfaceHeight;constructor(e={}){super();let{character:t,keys:r,collision:i,...a}=e;this.config={...v,...a},this.collision={...f,...i},this.character=t?this.resolveCharacter(t):null,this.keys={...d,...r},this.keyToAction=this.buildKeyMap(this.keys),this.modelHeading=n.MathUtils.degToRad(this.config.startHeading),this.cameraHeading=this.modelHeading,this.viewMode=this.config.initialView,this.currentAnimState=this.character?this.character.animation.idleClip:null,this.boundKeyDown=this.onKeyDown.bind(this),this.boundKeyUp=this.onKeyUp.bind(this)}async init(e,t){this.view=e,this.state={lng:this.config.startLng,lat:this.config.startLat,alt:this.config.startHeight,heading:this.config.startHeading,speed:0,animationState:this.currentAnimState,mode:this.viewMode}}async resolveStartHeight(e){let t=this.view;if(!t)return;let{startLat:n,startLng:r}=this.config,[i]=await t.sampleTerrainMostDetailed(e,[{lat:n,lng:r}]);if(i?.height===void 0)return;let{mode:a,groundOffset:o}=this.collision,s=i.height+(a===`off`?0:o);return this.config.startHeight=s,this.placed||(this.state={...this.state,alt:s},this.pinnedStartHeight=s,a!==`off`&&(this.groundSurfaceHeight=s)),s}start(){if(!this.view||this.animId!=null)return;if(this.animId=-1,document.addEventListener(`keydown`,this.boundKeyDown),document.addEventListener(`keyup`,this.boundKeyUp),this.placed){this.resumeLoop(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.applyModelVisibility();return}this.placed=!0;let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config,a=(0,t.geodeticToVector3)({lat:e,lng:n,height:r});if(this.modelHeading=(0,t.degreeToRadian)(i),this.cameraHeading=this.modelHeading,this.character){let{animation:e,modelRotationOffset:t,modelScale:n,castShadow:r,receiveShadow:i}=this.character;this.handle=this.view.addMesh({gltfModel:{url:this.character.modelUrl,castShadow:r,receiveShadow:i,animationEnabled:!0,animationAutoPlay:!0,animationActiveClip:e.idleClip,animationSpeed:this.clipSpeed(e.idleClip),animationLoop:!0,animationCrossfadeDuration:e.crossfadeDuration},matrixWorld:this.composeCharacterFrame(a,this.modelHeading),rotation:{x:t.x,y:t.y,z:t.z},scale:{x:n,y:n,z:n}}),this.modelRef=this.handle.ref,this.modelRef.on(`load`,()=>{!this.view||this.animId==null||this.resumeLoop()})}else this.resumeLoop();this.applyInitialCamera(),this.applyModelVisibility()}stop(){if(this.animId==null)return;this.releaseControl(),this.view?.cameraFollow(!1),this.view?.cameraFreeLook(!1),this.playAnimation(this.character?.animation.idleClip);let e={...this.state,speed:0,animationState:this.currentAnimState};this.hasStateChanged(this.state,e)&&(this.state=e,this.notify())}releaseControl(){this.animId!=null&&this.animId>=0&&cancelAnimationFrame(this.animId),this.animId=null,document.removeEventListener(`keydown`,this.boundKeyDown),document.removeEventListener(`keyup`,this.boundKeyUp),this.heldActions.clear(),this.orbitKeyHeld=!1,this.orbitLatched=!1,this.dashHeld=!1}resumeLoop(){this.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick)}teleport(e){if(!this.view)return;this.pinnedStartHeight=void 0;let{lng:n,lat:r}=e,i=e.heading==null?(0,t.radianToDegree)(this.cameraHeading):e.heading,a=(0,t.degreeToRadian)(i),o={lat:r,lng:n},s=this.resolveGroundHeight(o,o,e.alt,0);if(this.updateSlopeTilt(o.lat,o.lng,s,0),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:o.lat,lng:o.lng,height:s});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.modelHeading=a,this.cameraHeading=a,this.placeChaseCamera(r,n,s,a),this.state={lng:n,lat:r,alt:s,heading:i,speed:0,animationState:this.currentAnimState,mode:this.viewMode},this.notify()}getState(){return this.state}onStateChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}onAction(e){return this.actionListeners.add(e),()=>this.actionListeners.delete(e)}setViewMode(e){this.viewMode!==e&&(this.viewMode=e,this.state={...this.state,mode:e},this.applyModelVisibility(),this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading),this.notify())}toggleViewMode(){this.setViewMode(this.viewMode===`tpv`?`fpv`:`tpv`)}setAllowCameraControl(e){this.config.allowCameraControl=e}setCollision(e){this.collision={...this.collision,...e}}getCollision(){return this.collision}setHeading(e){if(!this.view)return;let{lat:n,lng:r,alt:i}=this.state,a=(0,t.degreeToRadian)(e);if(this.modelHeading=a,this.cameraHeading=a,this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:n,lng:r,height:i});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.isFreeCamera()||this.placeChaseCamera(n,r,i,a),this.state={...this.state,heading:e},this.notify()}getHeading(){return this.state.heading}setCameraPitch(e){this.config.cameraPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getCameraPitch(){return this.config.cameraPitch}setFpvPitch(e){this.config.fpvPitch=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvPitch(){return this.config.fpvPitch}setFpvHeightOffset(e){this.config.fpvHeightOffset=e,this.isFreeCamera()||this.placeChaseCamera(this.state.lat,this.state.lng,this.state.alt,this.cameraHeading)}getFpvHeightOffset(){return this.config.fpvHeightOffset}setAnimationSpeed(e){this.character&&(this.character.animation.speed=e),this.modelRef?.setAnimationSpeed(this.clipSpeed(this.currentAnimState))}getAnimationSpeed(){return this.character?.animation.speed??1}get model(){return this.handle}clipSpeed(e){let t=this.character?.animation;return t?e===t.dashClip?t.dashSpeed??t.speed??1:t.walkClip!=null&&e===t.walkClip?t.walkSpeed??t.speed??1:t.idleSpeed??t.speed??1:1}dispose(){this.releaseControl(),this.handle&&=(this.handle.delete(),null),this.modelRef=null,this.placed=!1,this.pinnedStartHeight=void 0,this.listeners.clear(),this.actionListeners.clear(),this.view=void 0}playAnimation(e){!e||!this.character||!this.modelRef||e!==this.currentAnimState&&(this.modelRef.crossFadeAnimation(this.currentAnimState??``,e,this.character.animation.crossfadeDuration),this.modelRef.setAnimationSpeed(this.clipSpeed(e)),this.currentAnimState=e)}resolveCharacter(e){return{modelUrl:e.modelUrl,animation:e.animation,modelRotationOffset:e.modelRotationOffset?{...u,...e.modelRotationOffset}:u,modelScale:e.modelScale??y,hideModelInFpv:e.hideModelInFpv??!0,castShadow:e.castShadow??!1,receiveShadow:e.receiveShadow??!1}}buildKeyMap(e){let t=new Map,n=(e,n)=>{for(let r of e)t.set(r,n)};return n(e.forward,`forward`),n(e.backward,`backward`),n(e.turnLeft,`turnLeft`),n(e.turnRight,`turnRight`),n(e.ascend,`ascend`),n(e.descend,`descend`),n(e.dash,`dash`),n(e.orbitCamera,`orbitCamera`),n(e.toggleView,`toggleView`),t}isFreeCamera(){return this.config.allowCameraControl||this.orbitKeyHeld||this.orbitLatched}isMovementAction(e){return e===`forward`||e===`backward`||e===`turnLeft`||e===`turnRight`||e===`ascend`||e===`descend`}applyModelVisibility(){if(!this.handle||!this.character)return;let e=this.viewMode===`fpv`&&this.character.hideModelInFpv;this.handle.visible=!e}applyInitialCamera(){let{startLat:e,startLng:n,startHeight:r,startHeading:i}=this.config;this.placeChaseCamera(e,n,r,(0,t.degreeToRadian)(i))}placeChaseCamera(e,n,r,i,a=0){if(!this.view)return;let{cameraDistance:o,fpvPitch:s,fpvForwardOffset:c}=this.config,l=this.viewMode===`fpv`,u=this.eyeHeightAt(r);if(l){let o=b,l=c+b,d=this.followSlopeWithPitch((0,t.degreeToRadian)(s),e,n,r,i,a),f=Math.tan(d)*o,p=this.advanceLatLng(e,n,i,l);this._offset.set(-Math.sin(i)*o,-Math.cos(i)*o,f),this.view.cameraFollow(!0,{lat:p.lat,lng:p.lng,height:u-f},this._offset);return}let d=this.followSlopeWithPitch((0,t.degreeToRadian)(this.config.cameraPitch),e,n,r,i,a),f=o*Math.cos(d),p=o*Math.sin(d);this._offset.set(-Math.sin(i)*f,-Math.cos(i)*f,p),this.view.cameraFollow(!0,{lat:e,lng:n,height:u},this._offset)}followSlopeWithPitch(e,t,r,i,a,o){let{cameraDistance:s,cameraLerpSpeed:c}=this.config,{mode:l,cameraSlopeFollow:u}=this.collision,d=0;if(l!==`off`&&u>0){let e=this.terrainAlong(t,r,a,s),o=this.terrainAlong(t,r,a+Math.PI,s);e!==void 0&&o!==void 0&&(d=n.MathUtils.clamp(Math.atan2(e-o,2*s)*u*this.groundedFactor(i,s),-h,h))}return this.cameraSlopePitch+=(d-this.cameraSlopePitch)*_(c,o),e-this.cameraSlopePitch}groundedFactor(e,t){let r=this.groundSurfaceHeight;return r===void 0?0:n.MathUtils.clamp(1-(e-r)/t,0,1)}terrainAlong(e,t,n,r){let i=this.advanceLatLng(e,t,n,r);return this.sampleTerrain(i.lat,i.lng)}composeCharacterFrame(e,r){let i=(0,t.eastNorthUpToFixedFrame)(e);this._headingMatrix.makeRotationZ(-r),this._characterFrame.multiplyMatrices(i,this._headingMatrix);let a=this.slopeGradEast,o=this.slopeGradNorth;if(a!==0||o!==0){let e=Math.sin(r),i=Math.cos(r),s=(0,t.degreeToRadian)(this.collision.maxSlopeTilt),c=n.MathUtils.clamp(Math.atan(a*e+o*i),-s,s),l=n.MathUtils.clamp(-Math.atan(a*i-o*e),-s,s);this._tiltMatrix.makeRotationX(c),this._characterFrame.multiply(this._tiltMatrix),this._tiltMatrix.makeRotationY(l),this._characterFrame.multiply(this._tiltMatrix)}return this._characterFrame}eyeHeightAt(e){return e+this.config.fpvHeightOffset}sampleTerrain(e,t){return this.view?.sampleTerrainHeight({lat:e,lng:t})}resolveGroundHeight(e,t,n,r){let{mode:i,groundOffset:a}=this.collision;if(i===`off`)return this.forgetGroundSamples(),n;let o=this.sampleTerrain(e.lat,e.lng);if(o===void 0)return this.forgetGroundSamples(),n;this.groundSurfaceHeight=o+a;let s=this.groundSurfaceHeight-this.absorbGroundDataShift(e,t,o,n,r);return i===`ground`?s:Math.max(n,s)}absorbGroundDataShift(e,t,n,r,i){let a=this.lastGroundSample,o;if(a===void 0)o=(this.sampleTerrain(t.lat,t.lng)??n)+this.collision.groundOffset-r;else{let e=this.sampleTerrain(a.lat,a.lng);o=e===void 0?0:e-a.height}let s=this.unabsorbedGroundShift+o,c=Math.sign(s);i>0&&(c!==Math.sign(this.settleVelocity)&&(this.settleVelocity=0),this.settleVelocity+=c*p*i);let l=i>0?Math.abs(this.settleVelocity)*i:Math.abs(s);return this.unabsorbedGroundShift=c*Math.max(0,Math.abs(s)-l),this.unabsorbedGroundShift===0&&(this.settleVelocity=0),this.lastGroundSample={lat:e.lat,lng:e.lng,height:n},this.unabsorbedGroundShift}forgetGroundSamples(){this.lastGroundSample=void 0,this.unabsorbedGroundShift=0,this.settleVelocity=0,this.groundSurfaceHeight=void 0}updateSlopeTilt(e,t,n,r){let{mode:i,alignToSlope:a,slopeSampleDistance:o}=this.collision,s=0,c=0;if(i!==`off`&&a){let r=this.measureSlopeGradient(e,t);if(!r)return;let i=this.groundedFactor(n,o);s=r.east*i,c=r.north*i}else if(this.slopeGradEast===0&&this.slopeGradNorth===0)return;let l=_(m,r);this.slopeGradEast+=(s-this.slopeGradEast)*l,this.slopeGradNorth+=(c-this.slopeGradNorth)*l}measureSlopeGradient(e,n){let r=this.collision.slopeSampleDistance,i=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),a=(0,t.eastNorthUpToFixedFrame)(i);this._east.setFromMatrixColumn(a,0).normalize(),this._north.setFromMatrixColumn(a,1).normalize();let o=0,s=0;for(let{sin:e,cos:n}of g){this._probePos.copy(i).addScaledVector(this._east,e*r).addScaledVector(this._north,n*r);let a=(0,t.vector3ToGeodetic)(this._probePos),c=this.sampleTerrain(a.lat,a.lng);if(c===void 0)return;o+=c*e,s+=c*n}let c=2/(g.length*r);return{east:o*c,north:s*c}}advanceLatLng(e,n,r,i){let a=(0,t.geodeticToVector3)({lat:e,lng:n,height:0}),o=(0,t.eastNorthUpToFixedFrame)(a);this._east.setFromMatrixColumn(o,0).normalize(),this._north.setFromMatrixColumn(o,1).normalize(),this._worldForward.copy(this._east).multiplyScalar(Math.sin(r)).addScaledVector(this._north,Math.cos(r)),a.addScaledVector(this._worldForward,i);let s=(0,t.vector3ToGeodetic)(a);return{lat:s.lat,lng:s.lng}}notify(){for(let e of this.listeners)e(this.state)}emitAction(e){for(let t of this.actionListeners)t(e)}onKeyDown(e){let t=e.target;if(t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.isContentEditable)return;let n=this.keyToAction.get(e.code);if(n!==void 0){if(e.repeat||this.emitAction(n),n===`toggleView`){if(e.repeat)return;this.toggleViewMode();return}if(n===`orbitCamera`){this.orbitKeyHeld=!0,this.orbitLatched=!0;return}if(!this.movementSuppressed){if(n===`dash`){this.dashHeld=!0;return}this.isMovementAction(n)&&(this.orbitLatched=!1,this.pinnedStartHeight=void 0),this.heldActions.add(n)}}}onKeyUp(e){let t=this.keyToAction.get(e.code);if(t!==void 0&&t!==`toggleView`){if(t===`orbitCamera`){this.orbitKeyHeld=!1;return}if(t===`dash`){this.dashHeld=!1;return}this.heldActions.delete(t)}}tick=e=>{if(!this.view||this.animId==null)return;this.animId=requestAnimationFrame(this.tick);let r=Math.min((e-this.lastTime)/1e3,.1);this.lastTime=e;let i=0,a=0,o=0;this.heldActions.has(`forward`)&&(a+=1),this.heldActions.has(`backward`)&&--a,this.heldActions.has(`turnLeft`)&&--i,this.heldActions.has(`turnRight`)&&(i+=1),this.heldActions.has(`ascend`)&&(o+=1),this.heldActions.has(`descend`)&&--o;let{moveSpeed:s,rotationSpeed:c,altSpeed:l,minAlt:u,maxAlt:d,cameraLerpSpeed:f}=this.config,p=this.dashHeld?this.config.dashSpeedMultiplier:1;i!==0&&(this.modelHeading+=(0,t.degreeToRadian)(c*i));let m=this._curPos;if(this.handle&&this.modelRef){let e=this.modelRef.getWorldPosition();if(!e)return;m.copy(e)}else m.copy((0,t.geodeticToVector3)({lat:this.state.lat,lng:this.state.lng,height:this.state.alt}));let h=(0,t.eastNorthUpToFixedFrame)(m);this._east.setFromMatrixColumn(h,0).normalize(),this._north.setFromMatrixColumn(h,1).normalize();let g=(0,t.vector3ToGeodetic)(m);this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north,Math.cos(this.modelHeading)),a!==0&&m.addScaledVector(this._worldForward,s*p*r*a);let v=(0,t.vector3ToGeodetic)(m),y=this.collision.mode===`ground`?g.height:n.MathUtils.clamp(g.height+o*l*r,u,d),b=this.pinnedStartHeight??this.resolveGroundHeight(v,g,y,r),x=v.lat,S=v.lng,C=b;if(this.updateSlopeTilt(v.lat,v.lng,b,r),this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:v.lat,lng:v.lng,height:b});this.handle.update({matrixWorld:this.composeCharacterFrame(e,this.modelHeading)})}let w=this.modelHeading-this.cameraHeading;if(w-=Math.round(w/(Math.PI*2))*(Math.PI*2),this.cameraHeading+=w*_(f,r),this.isFreeCamera()){if(this.viewMode===`fpv`){let e=this.advanceLatLng(x,S,this.cameraHeading,this.config.fpvForwardOffset);this.view.cameraFreeLook(!0,{lat:e.lat,lng:e.lng,height:this.eyeHeightAt(C)})}else this.view.cameraFollow(!0,{lat:x,lng:S,height:this.eyeHeightAt(C)})}else this.placeChaseCamera(x,S,C,this.cameraHeading,r);let T=a!==0||i!==0||o!==0,E=T&&this.dashHeld,D=this.currentAnimState;if(this.character){let{animation:e}=this.character,t=e.walkClip??e.idleClip;D=E?e.dashClip:T?t:e.idleClip,this.playAnimation(D)}let O={lng:S,lat:x,alt:C,heading:(0,t.radianToDegree)(this.modelHeading),speed:s*p,animationState:D,mode:this.viewMode};this.hasStateChanged(this.state,O)&&(this.state=O,this.notify())};hasStateChanged(e,t){return e.lng!==t.lng||e.lat!==t.lat||e.alt!==t.alt||e.heading!==t.heading||e.speed!==t.speed||e.animationState!==t.animationState||e.mode!==t.mode}};function S(e,t,n){e.style.transform=`translate(${t}px, ${n}px)`,e.style.willChange=`transform`}var C=1e5,w=class extends t.Plugin{view;maxDistance;positions=[];projected=new Map;listeners=new Set;boundHandler;constructor(e){super(),this.maxDistance=e?.maxDistance??C,this.boundHandler=this.handlePreRender.bind(this)}async init(e,t){this.view=e,e.on(`preRender`,this.boundHandler)}setPositions(e){this.positions=e}onUpdate(e){return this.listeners.add(e),()=>this.listeners.delete(e)}dispose(){this.view&&this.view.off(`preRender`,this.boundHandler),this.listeners.clear(),this.positions=[],this.projected.clear()}worldToScreen(e,n,r){if(!this.view)return null;let i=(0,t.geodeticToVector3)({lng:e,lat:n,height:r}),a={width:this.view.screenSize.x,height:this.view.screenSize.y,pixelRatio:this.view.pixelRatio},o=(0,t.convertWorldToScreen)(a,this.view.camera.raw,i);return o?{x:o.x,y:o.y}:null}handlePreRender(){if(!this.view)return;let e=this.view.camera.positionGeographic,n=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.height}),r=new Map;for(let e of this.positions){let i=(0,t.geodeticToVector3)({lng:e.lng,lat:e.lat,height:e.alt}),a=n.distanceTo(i);if(a>this.maxDistance)continue;let o=this.worldToScreen(e.lng,e.lat,e.alt);o&&r.set(e.id,{...o,distance:a})}this.projected=r;let i={projected:this.projected};for(let e of this.listeners)e(i)}},T=`https://api.cesium.com/v1/assets`,E=class extends t.Plugin{view;config;endpoint;constructor(e){super(),this.config=e}async init(e,t){this.view=e;let{assetId:n,accessToken:r,endpoint:i=T}=this.config,a=`${i}/${encodeURIComponent(String(n))}/endpoint?access_token=${encodeURIComponent(r)}`,o=await fetch(a);if(!o.ok)throw Error(`CesiumIonPlugin: failed to fetch asset ${n} endpoint (${o.status} ${o.statusText})`);let s=await o.json();this.endpoint={url:s.url,accessToken:s.accessToken}}addTerrain(e={}){if(!this.view||!this.endpoint)throw Error(`CesiumIonPlugin: addTerrain() must be called after view.init().`);let{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o,...s}=e,c=this.view.addSource({type:`quantized-mesh`,url:`${this.endpoint.url}{z}/{x}/{y}.terrain`,token:this.endpoint.accessToken,...s}),l=this.view.addLayer({type:`terrain`,source:c,terrain:{castShadow:t,receiveShadow:n,show:r,showBoundingBox:i,skirt:a,skirtExaggeration:o}});return l.once(`deleted`,()=>c.delete()),l}},D=/^3\.\d+\.\d+$/,O=512,k=`mapbox`;function A(e){if(typeof e.tilejson!=`string`||!D.test(e.tilejson))throw Error(`TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);if(!Array.isArray(e.tiles)||e.tiles.length===0)throw Error(`TileJsonPlugin: "tiles" must be a non-empty array of URL templates.`)}function j(e){switch(e){case`mapbox`:return(0,t.MAPBOX_ELEVATION_DECODER)();case`terrarium`:return(0,t.TERRARIUM_ELEVATION_DECODER)();default:throw Error(`TileJsonPlugin: unsupported raster-dem encoding ${JSON.stringify(e)}. Supported encodings: "mapbox", "terrarium".`)}}var M=class extends t.Plugin{view;events=new t.EventHandler;credits=[];async init(e,t){this.view=e}on(e,t){this.events.on(e,t)}once(e,t){this.events.once(e,t)}off(e,t){this.events.off(e,t)}async addSource(e){let t=this.view;if(!t)throw Error(`TileJsonPlugin: addSource() must be called after view.init().`);let n=await this.fetchTileJson(e.url);A(n);let[r,...i]=n.tiles;i.length>0&&console.warn(`TileJsonPlugin: TileJSON at ${e.url} lists ${n.tiles.length} mirror tile endpoints; Navara sources use a single URL, so only the first is used.`);let a=e.minzoom??n.minzoom,o=e.maxzoom??n.maxzoom,s=e.scheme??n.scheme,c=e.id,l={url:r,...c===void 0?{}:{id:c},...o===void 0?{}:{maxZoom:o}},u={...l,...a===void 0?{}:{minZoom:a},...s===`tms`?{tms:!0}:{}},d;return d=e.type===`raster-tile`?t.addSource({type:`raster-tile`,...u}):e.type===`raster-dem`?t.addSource({type:`raster-dem`,...u,tileSize:e.tileSize??n.tileSize??O,elevationDecoder:j(e.encoding??n.encoding??k)}):t.addSource({type:`vector-tile`,...l}),n.attribution&&this.addCredit(n.attribution),this.events.emit(`loaded`,{source:d,tilejson:n,attribution:n.attribution}),d}dispose(){let e=this.view?.attribution;e&&this.credits.length>0&&e.remove(this.credits.map(e=>({attributionHtml:e}))),this.credits.length=0,this.events.clear(`loaded`),this.view=void 0}async fetchTileJson(e){let t=await fetch(e);if(!t.ok)throw Error(`TileJsonPlugin: failed to fetch TileJSON from ${e} (${t.status} ${t.statusText})`);return await t.json()}addCredit(e){this.credits.includes(e)||(this.credits.push(e),this.view?.attribution?.add([{attributionHtml:e}]))}};e.CesiumIonPlugin=E,e.OverlayPlugin=w,e.PersonViewPlugin=x,e.TileJsonPlugin=M,e.moveOverlayElement=S});