@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 +1 -1
- package/README.md +5 -5
- package/dist/index.d.ts +57 -20
- package/dist/index.js +85 -69
- package/dist/index.umd.cjs +1 -1
- package/package.json +5 -5
- package/src/OverlayPlugin.ts +5 -10
- package/src/PersonViewPlugin.test.ts +144 -17
- package/src/PersonViewPlugin.ts +142 -71
package/LICENSE
CHANGED
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
|
|
6
|
-
- **`OverlayPlugin
|
|
7
|
-
- **`CesiumIonPlugin
|
|
8
|
-
- **`TileJsonPlugin
|
|
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 (
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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(
|
|
405
|
-
/** Current character heading in
|
|
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
|
|
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(
|
|
413
|
-
/** Current downward TPV camera pitch in
|
|
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
|
|
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(
|
|
421
|
-
/** Current downward FPV camera pitch in
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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.
|
|
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:
|
|
139
|
-
lng:
|
|
153
|
+
lat: e,
|
|
154
|
+
lng: t,
|
|
140
155
|
height: n
|
|
141
156
|
});
|
|
142
|
-
if (this.modelHeading = r, this.cameraHeading =
|
|
143
|
-
let { animation: e, modelRotationOffset: t, modelScale: n, castShadow:
|
|
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:
|
|
148
|
-
receiveShadow:
|
|
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,
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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:
|
|
198
|
-
lng:
|
|
199
|
-
height:
|
|
213
|
+
lat: o.lat,
|
|
214
|
+
lng: o.lng,
|
|
215
|
+
height: l
|
|
200
216
|
});
|
|
201
|
-
this.handle.update({ matrixWorld: this.composeCharacterFrame(e,
|
|
217
|
+
this.handle.update({ matrixWorld: this.composeCharacterFrame(e, i) });
|
|
202
218
|
}
|
|
203
|
-
this.modelHeading =
|
|
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:
|
|
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 =
|
|
247
|
-
let
|
|
248
|
-
lat:
|
|
249
|
-
lng:
|
|
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(
|
|
268
|
+
this.handle.update({ matrixWorld: this.composeCharacterFrame(e, i) });
|
|
253
269
|
}
|
|
254
|
-
this.isFreeCamera() || this.placeChaseCamera(t, n, r,
|
|
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:
|
|
337
|
-
if (
|
|
338
|
-
let
|
|
339
|
-
this._offset.set(-Math.sin(r) *
|
|
340
|
-
lat:
|
|
341
|
-
lng:
|
|
342
|
-
height:
|
|
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
|
|
347
|
-
this._offset.set(-Math.sin(r) *
|
|
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:
|
|
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(
|
|
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),
|
|
375
|
-
this._tiltMatrix.makeRotationX(
|
|
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:
|
|
450
|
-
lng:
|
|
465
|
+
lat: e,
|
|
466
|
+
lng: t,
|
|
451
467
|
height: 0
|
|
452
|
-
}),
|
|
453
|
-
this._east.setFromMatrixColumn(
|
|
454
|
-
let
|
|
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
|
|
457
|
-
lng: c
|
|
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:
|
|
519
|
-
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 =
|
|
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:
|
|
601
|
-
lat:
|
|
616
|
+
lng: e,
|
|
617
|
+
lat: t,
|
|
602
618
|
height: n
|
|
603
|
-
}),
|
|
619
|
+
}), a = {
|
|
604
620
|
width: this.view.screenSize.x,
|
|
605
621
|
height: this.view.screenSize.y,
|
|
606
622
|
pixelRatio: this.view.pixelRatio
|
|
607
|
-
},
|
|
608
|
-
return
|
|
609
|
-
x:
|
|
610
|
-
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:
|
|
617
|
-
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:
|
|
623
|
-
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
|
|
628
|
-
|
|
629
|
-
...
|
|
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
|
}
|
package/dist/index.umd.cjs
CHANGED
|
@@ -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});
|