@navaramap/three 0.0.8 → 0.1.1
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 +20 -8
- package/dist/assets/fontWorker-C5cHhkWQ.js +2 -0
- package/dist/assets/fontWorker-C5cHhkWQ.js.map +1 -0
- package/dist/assets/{index-DaejBdUz.js → index-VfUncKuS.js} +2 -2
- package/dist/assets/index-VfUncKuS.js.map +1 -0
- package/dist/assets/{navara_wasm_font_worker_bg-CSmoDs_Q.wasm → navara_wasm_font_worker_bg-CKqHBoCR.wasm} +0 -0
- package/dist/assets/{navara_wasm_worker_bg-C1dUd_Nk.wasm → navara_wasm_worker_bg-CT26EH41.wasm} +0 -0
- package/dist/index.d.ts +298 -54
- package/dist/index.js +1569 -1396
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +90 -69
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +12 -12
- package/src/core/BaseDesc.ts +25 -1
- package/src/core/MeshDesc.test.ts +6 -8
- package/src/core/MeshDesc.ts +4 -6
- package/src/evaluations/FeatureEvaluator.ts +17 -10
- package/src/event/feature.ts +15 -2
- package/src/event/features/billboard.test.ts +62 -0
- package/src/event/features/billboard.ts +4 -2
- package/src/index.ts +266 -98
- package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +2 -2
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +2 -2
- package/src/material/macro/tileShader.ts +12 -2
- package/src/mesh/polyline.ts +32 -4
- package/src/mesh/sdfText/batchedSdfText.test.ts +65 -0
- package/src/mesh/sdfText/batchedSdfText.ts +121 -43
- package/src/mesh/sprite/instancedSprite.ts +173 -65
- package/src/pick/pickHelper.test.ts +389 -4
- package/src/pick/pickHelper.ts +212 -37
- package/src/plugins/AttributionPlugin/index.ts +1 -1
- package/src/renderOrder.ts +10 -0
- package/src/terrain/sampleTerrainMostDetailed.test.ts +6 -6
- package/src/terrain/sampleTerrainMostDetailed.ts +7 -5
- package/src/type/index.ts +22 -1
- package/dist/assets/fontWorker-CkLGtrXf.js +0 -2
- package/dist/assets/fontWorker-CkLGtrXf.js.map +0 -1
- package/dist/assets/index-DaejBdUz.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navaramap/three",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"main": "dist/index.umd.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"astronomy-engine": "2.1.19",
|
|
31
31
|
"stats.js": "0.17.0",
|
|
32
32
|
"three-stdlib": "2.36.1",
|
|
33
|
-
"@navaramap/core": "0.
|
|
34
|
-
"@navaramap/engine
|
|
35
|
-
"@navaramap/three-api": "0.
|
|
36
|
-
"@navaramap/
|
|
37
|
-
"@navaramap/
|
|
38
|
-
"@navaramap/
|
|
39
|
-
"@navaramap/
|
|
33
|
+
"@navaramap/core": "0.1.1",
|
|
34
|
+
"@navaramap/engine": "0.1.1",
|
|
35
|
+
"@navaramap/three-api": "0.1.1",
|
|
36
|
+
"@navaramap/three-csm": "0.1.1",
|
|
37
|
+
"@navaramap/worker": "0.1.1",
|
|
38
|
+
"@navaramap/engine-api": "0.1.1",
|
|
39
|
+
"@navaramap/font": "0.1.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@playwright/test": "1.62.1",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"tweakpane": "4.0.5",
|
|
72
72
|
"vite-plugin-glsl": "1.6.0",
|
|
73
73
|
"vite-plugin-static-copy": "4.1.1",
|
|
74
|
-
"@navaramap/three-default-descs": "0.
|
|
75
|
-
"@navaramap/three-default-plugin": "0.
|
|
76
|
-
"@navaramap/three-
|
|
77
|
-
"@navaramap/three-
|
|
74
|
+
"@navaramap/three-default-descs": "0.1.1",
|
|
75
|
+
"@navaramap/three-default-plugin": "0.1.1",
|
|
76
|
+
"@navaramap/three-react": "0.1.1",
|
|
77
|
+
"@navaramap/three-plugins": "0.1.1"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"start": "vite",
|
package/src/core/BaseDesc.ts
CHANGED
|
@@ -76,6 +76,8 @@ export abstract class BaseDesc<
|
|
|
76
76
|
|
|
77
77
|
private _visible?: boolean;
|
|
78
78
|
|
|
79
|
+
private _destroyed = false;
|
|
80
|
+
|
|
79
81
|
constructor(
|
|
80
82
|
view: ThreeView,
|
|
81
83
|
ctx: ViewContext,
|
|
@@ -92,8 +94,20 @@ export abstract class BaseDesc<
|
|
|
92
94
|
/**
|
|
93
95
|
* Called when the this class is added to the scene. Override this to create the Three.js objects.
|
|
94
96
|
* This is where you should initialize `this._instance` and add it to the appropriate scene.
|
|
97
|
+
*
|
|
98
|
+
* May return a promise: a descriptor whose implementation loads lazily
|
|
99
|
+
* (e.g. an `await import(...)` of a heavy renderer dependency) can be
|
|
100
|
+
* `async`, awaiting the module before delegating to `super.onCreate()`
|
|
101
|
+
* (after checking {@link destroyed}). `addMesh`/`addLight`/`addEffect`
|
|
102
|
+
* still return their handle synchronously; the instance appears once the
|
|
103
|
+
* promise settles, and a rejection is reported through the descriptor's
|
|
104
|
+
* `error` event (when it declares one). Handle calls issued while the
|
|
105
|
+
* promise is pending are safe: `update()`/`visible` store their fields
|
|
106
|
+
* without touching the missing instance (the deferred `super.onCreate()`
|
|
107
|
+
* applies them), and per-frame/resize hooks are wired only after creation
|
|
108
|
+
* completes.
|
|
95
109
|
*/
|
|
96
|
-
abstract onCreate(): void
|
|
110
|
+
abstract onCreate(): void | Promise<void>;
|
|
97
111
|
|
|
98
112
|
/**
|
|
99
113
|
* Called when the configuration is updated via `BaseHandle.update()`.
|
|
@@ -122,9 +136,19 @@ export abstract class BaseDesc<
|
|
|
122
136
|
* Override this to clean up resources. Remember to call `super.onDestroy()`.
|
|
123
137
|
*/
|
|
124
138
|
onDestroy(): void {
|
|
139
|
+
this._destroyed = true;
|
|
125
140
|
this._instance = undefined;
|
|
126
141
|
}
|
|
127
142
|
|
|
143
|
+
/**
|
|
144
|
+
* True once {@link onDestroy} has run. An async {@link onCreate} (and the
|
|
145
|
+
* hook wiring that waits on it) checks this after its awaited module
|
|
146
|
+
* resolves, so a descriptor deleted mid-load creates nothing.
|
|
147
|
+
*/
|
|
148
|
+
get destroyed(): boolean {
|
|
149
|
+
return this._destroyed;
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
/**
|
|
129
153
|
* Gets whether this is currently visible.
|
|
130
154
|
*/
|
|
@@ -274,15 +274,15 @@ describe("MeshDesc geodetic placement", () => {
|
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
describe("MeshDesc terrain-referenced placement", () => {
|
|
277
|
-
type
|
|
277
|
+
type LatLngDeg = { lat: number; lng: number };
|
|
278
278
|
|
|
279
279
|
const terrainView = (height: number | undefined) => {
|
|
280
280
|
const listeners: ((h: number) => void)[] = [];
|
|
281
281
|
const unsubscribe = vi.fn();
|
|
282
282
|
const spy = {
|
|
283
|
-
sampleTerrainHeight: vi.fn((_pos:
|
|
283
|
+
sampleTerrainHeight: vi.fn((_pos: LatLngDeg) => height),
|
|
284
284
|
observeTerrainHeightAt: vi.fn(
|
|
285
|
-
(_pos:
|
|
285
|
+
(_pos: LatLngDeg, cb: (h: number) => void) => {
|
|
286
286
|
listeners.push(cb);
|
|
287
287
|
return unsubscribe;
|
|
288
288
|
},
|
|
@@ -297,8 +297,6 @@ describe("MeshDesc terrain-referenced placement", () => {
|
|
|
297
297
|
return desc;
|
|
298
298
|
};
|
|
299
299
|
|
|
300
|
-
const rad = (deg: number) => (deg * Math.PI) / 180;
|
|
301
|
-
|
|
302
300
|
it("seeds from the resident-tile sample and subscribes", () => {
|
|
303
301
|
const { view, spy } = terrainView(300);
|
|
304
302
|
createWith(view, {
|
|
@@ -308,10 +306,10 @@ describe("MeshDesc terrain-referenced placement", () => {
|
|
|
308
306
|
expect(spy.sampleTerrainHeight).toHaveBeenCalled();
|
|
309
307
|
expect(spy.observeTerrainHeightAt).toHaveBeenCalled();
|
|
310
308
|
|
|
311
|
-
// Both
|
|
309
|
+
// Both terrain methods take the same degrees `geodetic` carries.
|
|
312
310
|
const pos = spy.sampleTerrainHeight.mock.calls[0][0];
|
|
313
|
-
expect(pos.lat).toBeCloseTo(
|
|
314
|
-
expect(pos.lng).toBeCloseTo(
|
|
311
|
+
expect(pos.lat).toBeCloseTo(TOKYO.lat, 9);
|
|
312
|
+
expect(pos.lng).toBeCloseTo(TOKYO.lng, 9);
|
|
315
313
|
|
|
316
314
|
// terrain 300 + requested 10
|
|
317
315
|
expect(lastPlacement()?.height).toBeCloseTo(310, 9);
|
package/src/core/MeshDesc.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
headingPitchRollToFixedFrame,
|
|
4
4
|
type GeodeticPlacement,
|
|
5
5
|
} from "@navaramap/three-api";
|
|
6
|
-
import { Euler,
|
|
6
|
+
import { Euler, Matrix4, Object3D, Vector3, type Mesh } from "three";
|
|
7
7
|
import invariant from "tiny-invariant";
|
|
8
8
|
|
|
9
9
|
import type ThreeView from "../index";
|
|
@@ -369,10 +369,7 @@ export abstract class MeshDesc<
|
|
|
369
369
|
return;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
const pos = {
|
|
373
|
-
lat: MathUtils.degToRad(g.lat),
|
|
374
|
-
lng: MathUtils.degToRad(g.lng),
|
|
375
|
-
};
|
|
372
|
+
const pos = { lat: g.lat, lng: g.lng };
|
|
376
373
|
this.terrainHeight = this.view.sampleTerrainHeight(pos) ?? 0;
|
|
377
374
|
this.terrainUnsubscribe = this.view.observeTerrainHeightAt(
|
|
378
375
|
pos,
|
|
@@ -601,7 +598,6 @@ export abstract class MeshDesc<
|
|
|
601
598
|
assertNoTransformConflict(nextGeodetic, nextMatrix, nextMatrixWorld);
|
|
602
599
|
|
|
603
600
|
super.onUpdateConfig(updates);
|
|
604
|
-
invariant(this.raw);
|
|
605
601
|
|
|
606
602
|
const spatialChanged =
|
|
607
603
|
updates.matrix !== undefined ||
|
|
@@ -639,6 +635,8 @@ export abstract class MeshDesc<
|
|
|
639
635
|
this.applyLit();
|
|
640
636
|
}
|
|
641
637
|
|
|
638
|
+
if (!this.raw) return;
|
|
639
|
+
|
|
642
640
|
if (spatialChanged) {
|
|
643
641
|
// With a frame present, the effective transform depends on the
|
|
644
642
|
// combination of all stored fields — recompose the whole thing.
|
|
@@ -99,6 +99,11 @@ export type { GeometryType };
|
|
|
99
99
|
* Information about a feature batch, passed to evaluation callbacks.
|
|
100
100
|
*/
|
|
101
101
|
export type FeatureInfo = {
|
|
102
|
+
/**
|
|
103
|
+
* The feature's canonical global batch id (its first instance's id when a
|
|
104
|
+
* feature spans several instances, e.g. MultiPoint). Pick events report the
|
|
105
|
+
* same canonical id, so picks and evaluations correlate through this field.
|
|
106
|
+
*/
|
|
102
107
|
batchId: number;
|
|
103
108
|
properties: Record<string, unknown> | undefined;
|
|
104
109
|
layerId: LayerId | undefined;
|
|
@@ -431,7 +436,7 @@ export class FeatureEvaluator {
|
|
|
431
436
|
) {
|
|
432
437
|
const evaluate = (info: FeatureInfo, batchIndex: number) => {
|
|
433
438
|
const evaluated = f(info);
|
|
434
|
-
this.applyEvaluatedValues(batchIndex,
|
|
439
|
+
this.applyEvaluatedValues(batchIndex, evaluated);
|
|
435
440
|
};
|
|
436
441
|
|
|
437
442
|
if (options?.filters) {
|
|
@@ -443,30 +448,32 @@ export class FeatureEvaluator {
|
|
|
443
448
|
|
|
444
449
|
private applyEvaluatedValues(
|
|
445
450
|
batchIndex: number,
|
|
446
|
-
batchId: number,
|
|
447
451
|
evaluated: Partial<EvaluatedValue>,
|
|
448
452
|
) {
|
|
449
453
|
const obj = this.obj;
|
|
450
454
|
|
|
455
|
+
// Sprites apply by batch index: a feature can own several instances
|
|
456
|
+
// (MultiPoint, points derived from line/polygon vertices), so a
|
|
457
|
+
// batch-id keyed lookup would style only one of them.
|
|
451
458
|
if (obj instanceof InstancedSpriteMesh) {
|
|
452
459
|
if (evaluated.color != null) {
|
|
453
|
-
obj.
|
|
460
|
+
obj.setFeatureColorByBatchIndex(batchIndex, evaluated.color.raw);
|
|
454
461
|
}
|
|
455
462
|
if (evaluated.show != null) {
|
|
456
|
-
obj.
|
|
463
|
+
obj.setFeatureShowByBatchIndex(batchIndex, evaluated.show);
|
|
457
464
|
}
|
|
458
465
|
if (evaluated.height != null) {
|
|
459
|
-
obj.
|
|
466
|
+
obj.setFeatureHeightByBatchIndex(batchIndex, evaluated.height);
|
|
460
467
|
}
|
|
461
468
|
if (evaluated.size != null) {
|
|
462
|
-
obj.
|
|
469
|
+
obj.setFeatureSizeByBatchIndex(batchIndex, evaluated.size);
|
|
463
470
|
}
|
|
464
471
|
if (evaluated.opacity != null) {
|
|
465
|
-
obj.
|
|
472
|
+
obj.setFeatureOpacityByBatchIndex(batchIndex, evaluated.opacity);
|
|
466
473
|
}
|
|
467
474
|
if (evaluated.declutterPriority != null) {
|
|
468
|
-
obj.
|
|
469
|
-
|
|
475
|
+
obj.setFeatureDeclutterPriorityByBatchIndex(
|
|
476
|
+
batchIndex,
|
|
470
477
|
evaluated.declutterPriority,
|
|
471
478
|
);
|
|
472
479
|
}
|
|
@@ -474,7 +481,7 @@ export class FeatureEvaluator {
|
|
|
474
481
|
// Async by nature (the image may need fetching); the atlas dedupes
|
|
475
482
|
// loads by URL so evaluating many features costs one fetch per image.
|
|
476
483
|
// A nullish url clears the override back to the material default.
|
|
477
|
-
void obj.
|
|
484
|
+
void obj.setFeatureImageByBatchIndex(batchIndex, evaluated.image);
|
|
478
485
|
}
|
|
479
486
|
return;
|
|
480
487
|
}
|
package/src/event/feature.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
PolygonMesh,
|
|
15
15
|
PolylineMesh,
|
|
16
16
|
} from "../mesh";
|
|
17
|
-
import { FEATURE_RENDER_ORDER } from "../renderOrder";
|
|
17
|
+
import { FEATURE_RENDER_ORDER, STROKE_RENDER_ORDER } from "../renderOrder";
|
|
18
18
|
import type { Scenes } from "../scene";
|
|
19
19
|
|
|
20
20
|
import type { EventContext } from "./context";
|
|
@@ -190,7 +190,20 @@ export async function processRenderableFeatureAdded(
|
|
|
190
190
|
setTransform(obj, transform);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
// Strokes draw after fills: sibling meshes are created asynchronously in
|
|
194
|
+
// arbitrary order, and a fill drawn after a coplanar stroke either paints
|
|
195
|
+
// over it (bake scene) or z-fights against its depth (MRT scene), so the
|
|
196
|
+
// stacking must come from renderOrder, not insertion order. Only polylines
|
|
197
|
+
// stroking polygon rings sit on a sibling fill; standalone line strokes
|
|
198
|
+
// keep the base order — elevated, they would deterministically composite
|
|
199
|
+
// over every marker and outline they merely cross (PolylineMesh skips the
|
|
200
|
+
// depth test in the MRT scene).
|
|
201
|
+
const strokesSiblingFills =
|
|
202
|
+
polyline?.material.geometryTypes?.includes("polygon") ?? false;
|
|
203
|
+
obj.renderOrder =
|
|
204
|
+
obj instanceof PolylineMesh && strokesSiblingFills
|
|
205
|
+
? STROKE_RENDER_ORDER
|
|
206
|
+
: FEATURE_RENDER_ORDER;
|
|
194
207
|
|
|
195
208
|
// Add to MRT scene if not draped (draped features render to texturized scene)
|
|
196
209
|
const isDraped =
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BillboardMesh as NavaraBillboardMesh } from "@navaramap/engine";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import type { EventContext } from "../context";
|
|
5
|
+
|
|
6
|
+
import { renderBillboard } from "./billboard";
|
|
7
|
+
|
|
8
|
+
const init = vi.fn().mockResolvedValue(undefined);
|
|
9
|
+
const setActive = vi.fn();
|
|
10
|
+
|
|
11
|
+
vi.mock("../../mesh", () => ({
|
|
12
|
+
InstancedSpriteMesh: class {
|
|
13
|
+
_init = init;
|
|
14
|
+
setActive = setActive;
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe("renderBillboard", () => {
|
|
19
|
+
const ctx = {} as EventContext;
|
|
20
|
+
|
|
21
|
+
// `url` is omitted from the material entirely when not passed, mirroring the
|
|
22
|
+
// engine's `string | undefined` accessor. The engine itself currently always
|
|
23
|
+
// sends a string (`BillboardMaterial::url` is a `String` defaulting to `""`,
|
|
24
|
+
// converted as `Some(url)`), so `""` is the shape a real url-less layer
|
|
25
|
+
// produces — both are covered below.
|
|
26
|
+
const createMesh = (url?: string) =>
|
|
27
|
+
({
|
|
28
|
+
material: url === undefined ? {} : { url },
|
|
29
|
+
active: true,
|
|
30
|
+
}) as unknown as NavaraBillboardMesh;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
init.mockClear();
|
|
34
|
+
setActive.mockClear();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("creates a mesh when the material carries a default image", async () => {
|
|
38
|
+
const obj = await renderBillboard(ctx, createMesh("marker.png"));
|
|
39
|
+
|
|
40
|
+
expect(obj).toBeDefined();
|
|
41
|
+
expect(init).toHaveBeenCalledOnce();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Regression: the mesh used to be skipped when the material had no `url`,
|
|
45
|
+
// so a layer that styles every feature through a FeatureEvaluator
|
|
46
|
+
// (`{ image }`) never got a mesh — and never a `featureCreated` event to
|
|
47
|
+
// attach the evaluator to. Empty atlas rects keep such instances invisible
|
|
48
|
+
// until an image is packed, so the mesh is safe to create eagerly.
|
|
49
|
+
it("creates a mesh when the material's default image is empty", async () => {
|
|
50
|
+
const obj = await renderBillboard(ctx, createMesh(""));
|
|
51
|
+
|
|
52
|
+
expect(obj).toBeDefined();
|
|
53
|
+
expect(init).toHaveBeenCalledOnce();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("creates a mesh when the material has no `url` at all", async () => {
|
|
57
|
+
const obj = await renderBillboard(ctx, createMesh());
|
|
58
|
+
|
|
59
|
+
expect(obj).toBeDefined();
|
|
60
|
+
expect(init).toHaveBeenCalledOnce();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -8,8 +8,10 @@ export async function renderBillboard(
|
|
|
8
8
|
ctx: EventContext,
|
|
9
9
|
m: NavaraBillboardMesh,
|
|
10
10
|
) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// No `url` check: a billboard layer may carry no material-level image and
|
|
12
|
+
// get every image per-feature from a FeatureEvaluator (`{ image }`). The
|
|
13
|
+
// mesh starts with empty atlas rects, which the shader culls, so instances
|
|
14
|
+
// stay invisible until an image is packed for them.
|
|
13
15
|
const mesh = new InstancedSpriteMesh({
|
|
14
16
|
renderOrder: FEATURE_RENDER_ORDER,
|
|
15
17
|
ctx,
|