@navaramap/three-plugins 0.0.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/dist/index.js ADDED
@@ -0,0 +1,553 @@
1
+ import { EventHandler as e, Plugin as t, convertWorldToScreen as n, degreeToRadian as r, eastNorthUpToFixedFrame as i, geodeticToVector3 as a, radianToDegree as o, vector3ToGeodetic as s } from "@navaramap/three";
2
+ import { Matrix4 as c, Vector3 as l } from "three";
3
+ //#region src/PersonViewPlugin.ts
4
+ var u = {
5
+ x: 0,
6
+ y: 0,
7
+ z: 0
8
+ }, d = {
9
+ forward: ["KeyW"],
10
+ backward: ["KeyS"],
11
+ turnLeft: ["KeyA"],
12
+ turnRight: ["KeyD"],
13
+ ascend: ["ArrowUp", "Space"],
14
+ descend: [
15
+ "ArrowDown",
16
+ "ControlLeft",
17
+ "ControlRight"
18
+ ],
19
+ dash: ["ShiftLeft", "ShiftRight"],
20
+ orbitCamera: ["AltLeft", "AltRight"],
21
+ toggleView: ["KeyV"]
22
+ }, f = {
23
+ allowCameraControl: !1,
24
+ initialView: "tpv",
25
+ moveSpeed: 50,
26
+ rotationSpeed: 3,
27
+ altSpeed: 30,
28
+ minAlt: 50,
29
+ maxAlt: 5e3,
30
+ cameraDistance: 50,
31
+ cameraPitch: 0,
32
+ cameraLerpSpeed: 3,
33
+ fpvForwardOffset: 0,
34
+ fpvHeightOffset: 1,
35
+ fpvPitch: 0,
36
+ startLat: 35.6812,
37
+ startLng: 139.7671,
38
+ startHeight: 500,
39
+ startHeading: Math.PI * 1.3
40
+ }, p = 3, m = 1e3, h = class extends t {
41
+ view;
42
+ config;
43
+ character;
44
+ keys;
45
+ keyToAction;
46
+ handle = null;
47
+ modelRef = null;
48
+ animId = null;
49
+ lastTime = 0;
50
+ state;
51
+ listeners = /* @__PURE__ */ new Set();
52
+ heldActions = /* @__PURE__ */ new Set();
53
+ dashMultiplier = 1;
54
+ orbitKeyHeld = !1;
55
+ orbitLatched = !1;
56
+ currentAnimState;
57
+ modelHeading;
58
+ cameraHeading;
59
+ viewMode;
60
+ movementSuppressed = !1;
61
+ boundKeyDown;
62
+ boundKeyUp;
63
+ _curPos = new l();
64
+ _east = new l();
65
+ _north = new l();
66
+ _worldForward = new l();
67
+ _offset = new l();
68
+ _headingMatrix = new c();
69
+ _characterFrame = new c();
70
+ constructor(e = {}) {
71
+ super();
72
+ let { character: t, keys: n, ...r } = e;
73
+ this.config = {
74
+ ...f,
75
+ ...r
76
+ }, this.character = t ? this.resolveCharacter(t) : null, this.keys = {
77
+ ...d,
78
+ ...n
79
+ }, 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);
80
+ }
81
+ async init(e, t) {
82
+ this.view = e, this.state = {
83
+ lng: this.config.startLng,
84
+ lat: this.config.startLat,
85
+ alt: this.config.startHeight,
86
+ heading: this.config.startHeading,
87
+ speed: 0,
88
+ animationState: this.currentAnimState,
89
+ mode: this.viewMode
90
+ };
91
+ }
92
+ start() {
93
+ if (!this.view || this.animId != null) return;
94
+ this.animId = -1;
95
+ let { startLat: e, startLng: t, startHeight: n, startHeading: i } = this.config, o = a({
96
+ lat: r(e),
97
+ lng: r(t),
98
+ height: n
99
+ });
100
+ if (this.modelHeading = i, this.cameraHeading = i, this.character) {
101
+ let { animation: e, modelRotationOffset: t, modelScale: n, castShadow: r, receiveShadow: a } = this.character;
102
+ this.handle = this.view.addMesh({
103
+ gltfModel: {
104
+ url: this.character.modelUrl,
105
+ castShadow: r,
106
+ receiveShadow: a,
107
+ animationEnabled: !0,
108
+ animationAutoPlay: !0,
109
+ animationActiveClip: e.idleClip,
110
+ animationSpeed: e.speed,
111
+ animationLoop: !0,
112
+ animationCrossfadeDuration: e.crossfadeDuration
113
+ },
114
+ matrixWorld: this.composeCharacterFrame(o, i),
115
+ rotation: {
116
+ x: t.x,
117
+ y: t.y,
118
+ z: t.z
119
+ },
120
+ scale: {
121
+ x: n,
122
+ y: n,
123
+ z: n
124
+ }
125
+ }), this.modelRef = this.handle.ref, this.modelRef.on("load", () => {
126
+ this.view && (this.lastTime = performance.now(), this.animId = requestAnimationFrame(this.tick));
127
+ });
128
+ } else this.lastTime = performance.now(), this.animId = requestAnimationFrame(this.tick);
129
+ this.applyInitialCamera(), this.applyModelVisibility(), document.addEventListener("keydown", this.boundKeyDown), document.addEventListener("keyup", this.boundKeyUp);
130
+ }
131
+ teleport(e) {
132
+ if (!this.view) return;
133
+ let { lng: t, lat: n, alt: i } = e, o = e.heading == null ? this.cameraHeading : e.heading;
134
+ if (this.handle && this.character) {
135
+ let e = a({
136
+ lat: r(n),
137
+ lng: r(t),
138
+ height: i
139
+ });
140
+ this.handle.update({ matrixWorld: this.composeCharacterFrame(e, o) });
141
+ }
142
+ this.modelHeading = o, this.cameraHeading = o, this.placeChaseCamera(n, t, i, o), this.state = {
143
+ lng: t,
144
+ lat: n,
145
+ alt: i,
146
+ heading: o,
147
+ speed: 0,
148
+ animationState: this.currentAnimState,
149
+ mode: this.viewMode
150
+ }, this.notify();
151
+ }
152
+ getState() {
153
+ return this.state;
154
+ }
155
+ onStateChange(e) {
156
+ return this.listeners.add(e), () => this.listeners.delete(e);
157
+ }
158
+ setViewMode(e) {
159
+ this.viewMode !== e && (this.viewMode = e, this.state = {
160
+ ...this.state,
161
+ mode: e
162
+ }, this.applyModelVisibility(), this.placeChaseCamera(this.state.lat, this.state.lng, this.state.alt, this.cameraHeading), this.notify());
163
+ }
164
+ toggleViewMode() {
165
+ this.setViewMode(this.viewMode === "tpv" ? "fpv" : "tpv");
166
+ }
167
+ setAllowCameraControl(e) {
168
+ this.config.allowCameraControl = e;
169
+ }
170
+ setHeading(e) {
171
+ if (!this.view) return;
172
+ let { lat: t, lng: n, alt: i } = this.state;
173
+ if (this.modelHeading = e, this.cameraHeading = e, this.handle && this.character) {
174
+ let o = a({
175
+ lat: r(t),
176
+ lng: r(n),
177
+ height: i
178
+ });
179
+ this.handle.update({ matrixWorld: this.composeCharacterFrame(o, e) });
180
+ }
181
+ this.isFreeCamera() || this.placeChaseCamera(t, n, i, e), this.state = {
182
+ ...this.state,
183
+ heading: e
184
+ }, this.notify();
185
+ }
186
+ getHeading() {
187
+ return this.state.heading;
188
+ }
189
+ setCameraPitch(e) {
190
+ this.config.cameraPitch = e, this.isFreeCamera() || this.placeChaseCamera(this.state.lat, this.state.lng, this.state.alt, this.cameraHeading);
191
+ }
192
+ getCameraPitch() {
193
+ return this.config.cameraPitch;
194
+ }
195
+ setFpvPitch(e) {
196
+ this.config.fpvPitch = e, this.isFreeCamera() || this.placeChaseCamera(this.state.lat, this.state.lng, this.state.alt, this.cameraHeading);
197
+ }
198
+ getFpvPitch() {
199
+ return this.config.fpvPitch;
200
+ }
201
+ setFpvHeightOffset(e) {
202
+ this.config.fpvHeightOffset = e, this.isFreeCamera() || this.placeChaseCamera(this.state.lat, this.state.lng, this.state.alt, this.cameraHeading);
203
+ }
204
+ getFpvHeightOffset() {
205
+ return this.config.fpvHeightOffset;
206
+ }
207
+ dispose() {
208
+ this.animId != null && cancelAnimationFrame(this.animId), this.animId = null, document.removeEventListener("keydown", this.boundKeyDown), document.removeEventListener("keyup", this.boundKeyUp), this.handle &&= (this.handle.delete(), null), this.modelRef = null, this.heldActions.clear(), this.listeners.clear(), this.orbitKeyHeld = !1, this.orbitLatched = !1, this.dashMultiplier = 1, this.view = void 0;
209
+ }
210
+ resolveCharacter(e) {
211
+ return {
212
+ modelUrl: e.modelUrl,
213
+ animation: e.animation,
214
+ modelRotationOffset: e.modelRotationOffset ? {
215
+ ...u,
216
+ ...e.modelRotationOffset
217
+ } : u,
218
+ modelScale: e.modelScale ?? p,
219
+ hideModelInFpv: e.hideModelInFpv ?? !0,
220
+ castShadow: e.castShadow ?? !1,
221
+ receiveShadow: e.receiveShadow ?? !1
222
+ };
223
+ }
224
+ buildKeyMap(e) {
225
+ let t = /* @__PURE__ */ new Map(), n = (e, n) => {
226
+ for (let r of e) t.set(r, n);
227
+ };
228
+ 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;
229
+ }
230
+ isFreeCamera() {
231
+ return this.config.allowCameraControl || this.orbitKeyHeld || this.orbitLatched;
232
+ }
233
+ isMovementAction(e) {
234
+ return e === "forward" || e === "backward" || e === "turnLeft" || e === "turnRight" || e === "ascend" || e === "descend";
235
+ }
236
+ applyModelVisibility() {
237
+ if (!this.handle || !this.character) return;
238
+ let e = this.viewMode === "fpv" && this.character.hideModelInFpv;
239
+ this.handle.visible = !e;
240
+ }
241
+ applyInitialCamera() {
242
+ let { startLat: e, startLng: t, startHeight: n, startHeading: r } = this.config;
243
+ this.placeChaseCamera(e, t, n, r);
244
+ }
245
+ placeChaseCamera(e, t, n, r) {
246
+ if (!this.view) return;
247
+ let { cameraDistance: i, cameraPitch: a, fpvPitch: o, fpvHeightOffset: s, fpvForwardOffset: c } = this.config, l = this.viewMode === "fpv", u = n + s;
248
+ if (l) {
249
+ let n = m, i = c + m, a = Math.tan(o) * n, s = this.advanceLatLng(e, t, r, i);
250
+ this._offset.set(-Math.sin(r) * n, -Math.cos(r) * n, a), this.view.cameraFollow(!0, {
251
+ lat: s.lat,
252
+ lng: s.lng,
253
+ height: u - a
254
+ }, this._offset);
255
+ return;
256
+ }
257
+ let d = i * Math.cos(a), f = i * Math.sin(a);
258
+ this._offset.set(-Math.sin(r) * d, -Math.cos(r) * d, f), this.view.cameraFollow(!0, {
259
+ lat: e,
260
+ lng: t,
261
+ height: u
262
+ }, this._offset);
263
+ }
264
+ composeCharacterFrame(e, t) {
265
+ let n = i(e);
266
+ return this._headingMatrix.makeRotationZ(-t), this._characterFrame.multiplyMatrices(n, this._headingMatrix);
267
+ }
268
+ advanceLatLng(e, t, n, c) {
269
+ let l = a({
270
+ lat: r(e),
271
+ lng: r(t),
272
+ height: 0
273
+ }), u = i(l);
274
+ 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)), l.addScaledVector(this._worldForward, c);
275
+ let d = s(l);
276
+ return {
277
+ lat: o(d.lat),
278
+ lng: o(d.lng)
279
+ };
280
+ }
281
+ notify() {
282
+ for (let e of this.listeners) e(this.state);
283
+ }
284
+ onKeyDown(e) {
285
+ let t = e.target;
286
+ if (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable) return;
287
+ let n = this.keyToAction.get(e.code);
288
+ if (n !== void 0) {
289
+ if (n === "toggleView") {
290
+ if (e.repeat) return;
291
+ this.toggleViewMode();
292
+ return;
293
+ }
294
+ if (n === "orbitCamera") {
295
+ this.orbitKeyHeld = !0, this.orbitLatched = !0;
296
+ return;
297
+ }
298
+ if (!this.movementSuppressed) {
299
+ if (n === "dash") {
300
+ this.dashMultiplier = 2.5;
301
+ return;
302
+ }
303
+ this.isMovementAction(n) && (this.orbitLatched = !1), this.heldActions.add(n);
304
+ }
305
+ }
306
+ }
307
+ onKeyUp(e) {
308
+ let t = this.keyToAction.get(e.code);
309
+ if (t !== void 0 && t !== "toggleView") {
310
+ if (t === "orbitCamera") {
311
+ this.orbitKeyHeld = !1;
312
+ return;
313
+ }
314
+ if (t === "dash") {
315
+ this.dashMultiplier = 1;
316
+ return;
317
+ }
318
+ this.heldActions.delete(t);
319
+ }
320
+ }
321
+ tick = (e) => {
322
+ if (!this.view) return;
323
+ this.animId = requestAnimationFrame(this.tick);
324
+ let t = Math.min((e - this.lastTime) / 1e3, .1);
325
+ this.lastTime = e;
326
+ let n = 0, c = 0, l = 0;
327
+ this.heldActions.has("forward") && (c += 1), this.heldActions.has("backward") && --c, this.heldActions.has("turnLeft") && --n, this.heldActions.has("turnRight") && (n += 1), this.heldActions.has("ascend") && (l += 1), this.heldActions.has("descend") && --l;
328
+ let { moveSpeed: u, rotationSpeed: d, altSpeed: f, minAlt: p, maxAlt: m, cameraLerpSpeed: h } = this.config;
329
+ n !== 0 && (this.modelHeading += r(d * n));
330
+ let g = this._curPos;
331
+ if (this.handle && this.modelRef) {
332
+ let e = this.modelRef.getWorldPosition();
333
+ if (!e) return;
334
+ g.copy(e);
335
+ } else g.copy(a({
336
+ lat: r(this.state.lat),
337
+ lng: r(this.state.lng),
338
+ height: this.state.alt
339
+ }));
340
+ let _ = i(g);
341
+ this._east.setFromMatrixColumn(_, 0).normalize(), this._north.setFromMatrixColumn(_, 1).normalize();
342
+ let v = s(g);
343
+ this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north, Math.cos(this.modelHeading)), c !== 0 && g.addScaledVector(this._worldForward, u * this.dashMultiplier * t * c);
344
+ let y = s(g), b = v.height + l * f * t, x = Math.max(p, Math.min(m, b)), S = o(y.lat), C = o(y.lng), w = x;
345
+ if (this.handle && this.character) {
346
+ let e = a({
347
+ lat: y.lat,
348
+ lng: y.lng,
349
+ height: x
350
+ });
351
+ this.handle.update({ matrixWorld: this.composeCharacterFrame(e, this.modelHeading) });
352
+ }
353
+ let T = this.modelHeading - this.cameraHeading;
354
+ if (T -= Math.round(T / (Math.PI * 2)) * (Math.PI * 2), this.cameraHeading += T * Math.min(t * h, 1), this.isFreeCamera()) if (this.viewMode === "fpv") {
355
+ let e = this.advanceLatLng(S, C, this.cameraHeading, this.config.fpvForwardOffset);
356
+ this.view.cameraFreeLook(!0, {
357
+ lat: e.lat,
358
+ lng: e.lng,
359
+ height: w + this.config.fpvHeightOffset
360
+ });
361
+ } else this.view.cameraFollow(!0, {
362
+ lat: S,
363
+ lng: C,
364
+ height: w + this.config.fpvHeightOffset
365
+ });
366
+ else this.placeChaseCamera(S, C, w, this.cameraHeading);
367
+ let E = c !== 0 || n !== 0 || l !== 0, D = E && this.dashMultiplier > 1, O = this.currentAnimState;
368
+ if (this.character && this.modelRef) {
369
+ let { animation: e } = this.character, t = e.walkClip ?? e.idleClip, n;
370
+ n = D ? e.dashClip : E ? t : e.idleClip, n !== this.currentAnimState && (this.modelRef.crossFadeAnimation(this.currentAnimState ?? "", n, e.crossfadeDuration), this.currentAnimState = n), O = n;
371
+ }
372
+ let k = {
373
+ lng: C,
374
+ lat: S,
375
+ alt: w,
376
+ heading: this.modelHeading,
377
+ speed: u * this.dashMultiplier,
378
+ animationState: O,
379
+ mode: this.viewMode
380
+ };
381
+ this.hasStateChanged(this.state, k) && (this.state = k, this.notify());
382
+ };
383
+ hasStateChanged(e, t) {
384
+ 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;
385
+ }
386
+ };
387
+ //#endregion
388
+ //#region src/OverlayPlugin.ts
389
+ function g(e, t, n) {
390
+ e.style.transform = `translate(${t}px, ${n}px)`, e.style.willChange = "transform";
391
+ }
392
+ var _ = 1e5, v = class extends t {
393
+ view;
394
+ maxDistance;
395
+ positions = [];
396
+ projected = /* @__PURE__ */ new Map();
397
+ listeners = /* @__PURE__ */ new Set();
398
+ boundHandler;
399
+ constructor(e) {
400
+ super(), this.maxDistance = e?.maxDistance ?? _, this.boundHandler = this.handlePreRender.bind(this);
401
+ }
402
+ async init(e, t) {
403
+ this.view = e, e.on("preRender", this.boundHandler);
404
+ }
405
+ setPositions(e) {
406
+ this.positions = e;
407
+ }
408
+ onUpdate(e) {
409
+ return this.listeners.add(e), () => this.listeners.delete(e);
410
+ }
411
+ dispose() {
412
+ this.view && this.view.off("preRender", this.boundHandler), this.listeners.clear(), this.positions = [], this.projected.clear();
413
+ }
414
+ worldToScreen(e, t, i) {
415
+ if (!this.view) return null;
416
+ let o = a({
417
+ lng: r(e),
418
+ lat: r(t),
419
+ height: i
420
+ }), s = n({
421
+ width: this.view.screenSize.x,
422
+ height: this.view.screenSize.y,
423
+ pixelRatio: this.view.pixelRatio
424
+ }, this.view.camera.raw, o);
425
+ return s ? {
426
+ x: s.x,
427
+ y: s.y
428
+ } : null;
429
+ }
430
+ handlePreRender() {
431
+ if (!this.view) return;
432
+ let e = this.view.camera.positionGeographic, t = a({
433
+ lng: r(e.lng),
434
+ lat: r(e.lat),
435
+ height: e.height
436
+ }), n = /* @__PURE__ */ new Map();
437
+ for (let e of this.positions) {
438
+ let i = a({
439
+ lng: r(e.lng),
440
+ lat: r(e.lat),
441
+ height: e.alt
442
+ }), o = t.distanceTo(i);
443
+ if (o > this.maxDistance) continue;
444
+ let s = this.worldToScreen(e.lng, e.lat, e.alt);
445
+ s && n.set(e.id, {
446
+ ...s,
447
+ distance: o
448
+ });
449
+ }
450
+ this.projected = n;
451
+ let i = { projected: this.projected };
452
+ for (let e of this.listeners) e(i);
453
+ }
454
+ }, y = "https://api.cesium.com/v1/assets", b = class extends t {
455
+ view;
456
+ config;
457
+ endpoint;
458
+ constructor(e) {
459
+ super(), this.config = e;
460
+ }
461
+ async init(e, t) {
462
+ this.view = e;
463
+ let { assetId: n, accessToken: r, endpoint: i = y } = this.config, a = `${i}/${encodeURIComponent(String(n))}/endpoint?access_token=${encodeURIComponent(r)}`, o = await fetch(a);
464
+ if (!o.ok) throw Error(`CesiumIonPlugin: failed to fetch asset ${n} endpoint (${o.status} ${o.statusText})`);
465
+ let s = await o.json();
466
+ this.endpoint = {
467
+ url: s.url,
468
+ accessToken: s.accessToken
469
+ };
470
+ }
471
+ addTerrain(e = {}) {
472
+ if (!this.view || !this.endpoint) throw Error("CesiumIonPlugin: addTerrain() must be called after view.init().");
473
+ let { castShadow: t, receiveShadow: n, show: r, showBoundingBox: i, skirt: a, skirtExaggeration: o, ...s } = e, c = this.view.addSource({
474
+ type: "quantized-mesh",
475
+ url: `${this.endpoint.url}{z}/{x}/{y}.terrain`,
476
+ token: this.endpoint.accessToken,
477
+ ...s
478
+ }), l = this.view.addLayer({
479
+ type: "terrain",
480
+ source: c,
481
+ terrain: {
482
+ castShadow: t,
483
+ receiveShadow: n,
484
+ show: r,
485
+ showBoundingBox: i,
486
+ skirt: a,
487
+ skirtExaggeration: o
488
+ }
489
+ });
490
+ return l.once("deleted", () => c.delete()), l;
491
+ }
492
+ }, x = /^3\.\d+\.\d+$/;
493
+ function S(e) {
494
+ if (typeof e.tilejson != "string" || !x.test(e.tilejson)) throw Error(`TileJsonPlugin: invalid or missing "tilejson" version field (got ${JSON.stringify(e.tilejson)}).`);
495
+ if (!Array.isArray(e.tiles) || e.tiles.length === 0) throw Error("TileJsonPlugin: \"tiles\" must be a non-empty array of URL templates.");
496
+ }
497
+ var C = class extends t {
498
+ view;
499
+ events = new e();
500
+ credits = [];
501
+ async init(e, t) {
502
+ this.view = e;
503
+ }
504
+ on(e, t) {
505
+ this.events.on(e, t);
506
+ }
507
+ once(e, t) {
508
+ this.events.once(e, t);
509
+ }
510
+ off(e, t) {
511
+ this.events.off(e, t);
512
+ }
513
+ async addSource(e) {
514
+ let t = this.view;
515
+ if (!t) throw Error("TileJsonPlugin: addSource() must be called after view.init().");
516
+ let n = await this.fetchTileJson(e.url);
517
+ S(n);
518
+ let [r, ...i] = n.tiles;
519
+ 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.`);
520
+ let { minzoom: a, maxzoom: o, scheme: s } = n, c = e.id, l = e.type === "raster-tile" ? t.addSource({
521
+ type: "raster-tile",
522
+ url: r,
523
+ ...c === void 0 ? {} : { id: c },
524
+ ...a === void 0 ? {} : { minZoom: a },
525
+ ...o === void 0 ? {} : { maxZoom: o },
526
+ ...s === "tms" ? { tms: !0 } : {}
527
+ }) : t.addSource({
528
+ type: "vector-tile",
529
+ url: r,
530
+ ...c === void 0 ? {} : { id: c },
531
+ ...o === void 0 ? {} : { maxZoom: o }
532
+ });
533
+ return n.attribution && this.addCredit(n.attribution), this.events.emit("loaded", {
534
+ source: l,
535
+ tilejson: n,
536
+ attribution: n.attribution
537
+ }), l;
538
+ }
539
+ dispose() {
540
+ let e = this.view?.attribution;
541
+ 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;
542
+ }
543
+ async fetchTileJson(e) {
544
+ let t = await fetch(e);
545
+ if (!t.ok) throw Error(`TileJsonPlugin: failed to fetch TileJSON from ${e} (${t.status} ${t.statusText})`);
546
+ return await t.json();
547
+ }
548
+ addCredit(e) {
549
+ this.credits.includes(e) || (this.credits.push(e), this.view?.attribution?.add([{ attributionHtml: e }]));
550
+ }
551
+ };
552
+ //#endregion
553
+ export { b as CesiumIonPlugin, v as OverlayPlugin, h as PersonViewPlugin, C as TileJsonPlugin, g as moveOverlayElement };
@@ -0,0 +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?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={allowCameraControl:!1,initialView:`tpv`,moveSpeed:50,rotationSpeed:3,altSpeed:30,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},p=3,m=1e3,h=class extends t.Plugin{view;config;character;keys;keyToAction;handle=null;modelRef=null;animId=null;lastTime=0;state;listeners=new Set;heldActions=new Set;dashMultiplier=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;_headingMatrix=new n.Matrix4;_characterFrame=new n.Matrix4;constructor(e={}){super();let{character:t,keys:n,...r}=e;this.config={...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;this.animId=-1;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:e.speed,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.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick))})}else this.lastTime=performance.now(),this.animId=requestAnimationFrame(this.tick);this.applyInitialCamera(),this.applyModelVisibility(),document.addEventListener(`keydown`,this.boundKeyDown),document.addEventListener(`keyup`,this.boundKeyUp)}teleport(e){if(!this.view)return;let{lng:n,lat:r,alt:i}=e,a=e.heading==null?this.cameraHeading:e.heading;if(this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(r),lng:(0,t.degreeToRadian)(n),height:i});this.handle.update({matrixWorld:this.composeCharacterFrame(e,a)})}this.modelHeading=a,this.cameraHeading=a,this.placeChaseCamera(r,n,i,a),this.state={lng:n,lat:r,alt:i,heading:a,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)}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}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}dispose(){this.animId!=null&&cancelAnimationFrame(this.animId),this.animId=null,document.removeEventListener(`keydown`,this.boundKeyDown),document.removeEventListener(`keyup`,this.boundKeyUp),this.handle&&=(this.handle.delete(),null),this.modelRef=null,this.heldActions.clear(),this.listeners.clear(),this.orbitKeyHeld=!1,this.orbitLatched=!1,this.dashMultiplier=1,this.view=void 0}resolveCharacter(e){return{modelUrl:e.modelUrl,animation:e.animation,modelRotationOffset:e.modelRotationOffset?{...u,...e.modelRotationOffset}:u,modelScale:e.modelScale??p,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){if(!this.view)return;let{cameraDistance:i,cameraPitch:a,fpvPitch:o,fpvHeightOffset:s,fpvForwardOffset:c}=this.config,l=this.viewMode===`fpv`,u=n+s;if(l){let n=m,i=c+m,a=Math.tan(o)*n,s=this.advanceLatLng(e,t,r,i);this._offset.set(-Math.sin(r)*n,-Math.cos(r)*n,a),this.view.cameraFollow(!0,{lat:s.lat,lng:s.lng,height:u-a},this._offset);return}let d=i*Math.cos(a),f=i*Math.sin(a);this._offset.set(-Math.sin(r)*d,-Math.cos(r)*d,f),this.view.cameraFollow(!0,{lat:e,lng:t,height:u},this._offset)}composeCharacterFrame(e,n){let r=(0,t.eastNorthUpToFixedFrame)(e);return this._headingMatrix.makeRotationZ(-n),this._characterFrame.multiplyMatrices(r,this._headingMatrix)}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)}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(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.dashMultiplier=2.5;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.dashMultiplier=1;return}this.heldActions.delete(t)}}tick=e=>{if(!this.view)return;this.animId=requestAnimationFrame(this.tick);let n=Math.min((e-this.lastTime)/1e3,.1);this.lastTime=e;let r=0,i=0,a=0;this.heldActions.has(`forward`)&&(i+=1),this.heldActions.has(`backward`)&&--i,this.heldActions.has(`turnLeft`)&&--r,this.heldActions.has(`turnRight`)&&(r+=1),this.heldActions.has(`ascend`)&&(a+=1),this.heldActions.has(`descend`)&&--a;let{moveSpeed:o,rotationSpeed:s,altSpeed:c,minAlt:l,maxAlt:u,cameraLerpSpeed:d}=this.config;r!==0&&(this.modelHeading+=(0,t.degreeToRadian)(s*r));let f=this._curPos;if(this.handle&&this.modelRef){let e=this.modelRef.getWorldPosition();if(!e)return;f.copy(e)}else f.copy((0,t.geodeticToVector3)({lat:(0,t.degreeToRadian)(this.state.lat),lng:(0,t.degreeToRadian)(this.state.lng),height:this.state.alt}));let p=(0,t.eastNorthUpToFixedFrame)(f);this._east.setFromMatrixColumn(p,0).normalize(),this._north.setFromMatrixColumn(p,1).normalize();let m=(0,t.vector3ToGeodetic)(f);this._worldForward.copy(this._east).multiplyScalar(Math.sin(this.modelHeading)).addScaledVector(this._north,Math.cos(this.modelHeading)),i!==0&&f.addScaledVector(this._worldForward,o*this.dashMultiplier*n*i);let h=(0,t.vector3ToGeodetic)(f),g=m.height+a*c*n,_=Math.max(l,Math.min(u,g)),v=(0,t.radianToDegree)(h.lat),y=(0,t.radianToDegree)(h.lng),b=_;if(this.handle&&this.character){let e=(0,t.geodeticToVector3)({lat:h.lat,lng:h.lng,height:_});this.handle.update({matrixWorld:this.composeCharacterFrame(e,this.modelHeading)})}let x=this.modelHeading-this.cameraHeading;if(x-=Math.round(x/(Math.PI*2))*(Math.PI*2),this.cameraHeading+=x*Math.min(n*d,1),this.isFreeCamera())if(this.viewMode===`fpv`){let e=this.advanceLatLng(v,y,this.cameraHeading,this.config.fpvForwardOffset);this.view.cameraFreeLook(!0,{lat:e.lat,lng:e.lng,height:b+this.config.fpvHeightOffset})}else this.view.cameraFollow(!0,{lat:v,lng:y,height:b+this.config.fpvHeightOffset});else this.placeChaseCamera(v,y,b,this.cameraHeading);let S=i!==0||r!==0||a!==0,C=S&&this.dashMultiplier>1,w=this.currentAnimState;if(this.character&&this.modelRef){let{animation:e}=this.character,t=e.walkClip??e.idleClip,n;n=C?e.dashClip:S?t:e.idleClip,n!==this.currentAnimState&&(this.modelRef.crossFadeAnimation(this.currentAnimState??``,n,e.crossfadeDuration),this.currentAnimState=n),w=n}let T={lng:y,lat:v,alt:b,heading:this.modelHeading,speed:o*this.dashMultiplier,animationState:w,mode:this.viewMode};this.hasStateChanged(this.state,T)&&(this.state=T,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 g(e,t,n){e.style.transform=`translate(${t}px, ${n}px)`,e.style.willChange=`transform`}var _=1e5,v=class extends t.Plugin{view;maxDistance;positions=[];projected=new Map;listeners=new Set;boundHandler;constructor(e){super(),this.maxDistance=e?.maxDistance??_,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=(0,t.convertWorldToScreen)({width:this.view.screenSize.x,height:this.view.screenSize.y,pixelRatio:this.view.pixelRatio},this.view.camera.raw,i);return a?{x:a.x,y:a.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)}},y=`https://api.cesium.com/v1/assets`,b=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=y}=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}},x=/^3\.\d+\.\d+$/;function S(e){if(typeof e.tilejson!=`string`||!x.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.`)}var C=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);S(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{minzoom:a,maxzoom:o,scheme:s}=n,c=e.id,l=e.type===`raster-tile`?t.addSource({type:`raster-tile`,url:r,...c===void 0?{}:{id:c},...a===void 0?{}:{minZoom:a},...o===void 0?{}:{maxZoom:o},...s===`tms`?{tms:!0}:{}}):t.addSource({type:`vector-tile`,url:r,...c===void 0?{}:{id:c},...o===void 0?{}:{maxZoom:o}});return n.attribution&&this.addCredit(n.attribution),this.events.emit(`loaded`,{source:l,tilejson:n,attribution:n.attribution}),l}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=b,e.OverlayPlugin=v,e.PersonViewPlugin=h,e.TileJsonPlugin=C,e.moveOverlayElement=g});
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@navaramap/three-plugins",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "main": "dist/index.umd.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "dist"
11
+ ],
12
+ "license": "MIT OR Apache-2.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/reearth/navara.git",
16
+ "directory": "web/navara_three_plugins"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "peerDependencies": {
22
+ "three": ">=0.183.0",
23
+ "@navaramap/three": "0.0.0",
24
+ "@navaramap/three-default-plugin": "0.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/three": "0.185.1",
28
+ "three": "0.185.1",
29
+ "@navaramap/three-default-plugin": "0.0.0",
30
+ "@navaramap/three": "0.0.0"
31
+ },
32
+ "scripts": {
33
+ "type-check": "tsc && tsc -p tsconfig.node.json",
34
+ "build": "pnpm type-check && vite build --watch=false",
35
+ "build:dev": "vite build --mode development",
36
+ "build:dev:watch": "pnpm build:dev --watch",
37
+ "test": "vitest --passWithNoTests --watch=false",
38
+ "lint": "eslint . --fix",
39
+ "lint:check": "eslint .",
40
+ "format": "prettier --write --ignore-path ../../.gitignore .",
41
+ "format:check": "prettier --check --ignore-path ../../.gitignore ."
42
+ }
43
+ }