@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/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type FontWorkerMemoryStats,
|
|
24
24
|
} from "@navaramap/font";
|
|
25
25
|
import FontWorkerURL from "@navaramap/font/fontWorker?worker&url";
|
|
26
|
-
import { initNavaraApi } from "@navaramap/three-api";
|
|
26
|
+
import { initNavaraApi, degreeToRadian } from "@navaramap/three-api";
|
|
27
27
|
import {
|
|
28
28
|
initializeWorkerPool,
|
|
29
29
|
probeWorkerPoolHeap,
|
|
@@ -118,7 +118,12 @@ import {
|
|
|
118
118
|
} from "./material";
|
|
119
119
|
import type { TileMesh } from "./mesh/tile";
|
|
120
120
|
import { RenderPassOrchestrator } from "./orchestrators/RenderPassOrchestrator";
|
|
121
|
-
import {
|
|
121
|
+
import {
|
|
122
|
+
CLICK_PIXEL_TOLERANCE,
|
|
123
|
+
isClickGesture,
|
|
124
|
+
PickHelper,
|
|
125
|
+
TAP_PIXEL_TOLERANCE,
|
|
126
|
+
} from "./pick/pickHelper";
|
|
122
127
|
import { TerrainPicker } from "./pick/pickTerrain";
|
|
123
128
|
import { AttributionPlugin, type AttributionPluginOptions } from "./plugins";
|
|
124
129
|
import { TexturizedSceneByTileCoordinates, type Scenes } from "./scene";
|
|
@@ -219,6 +224,12 @@ export * from "./layers";
|
|
|
219
224
|
export * from "./passes";
|
|
220
225
|
export * from "./evaluations";
|
|
221
226
|
export { SKY_RENDER_ORDER, STARS_RENDER_ORDER } from "./renderOrder";
|
|
227
|
+
export {
|
|
228
|
+
CLICK_PIXEL_TOLERANCE,
|
|
229
|
+
TAP_PIXEL_TOLERANCE,
|
|
230
|
+
PICK_RADIUS,
|
|
231
|
+
TOUCH_PICK_RADIUS,
|
|
232
|
+
} from "./pick/pickHelper";
|
|
222
233
|
export * from "@navaramap/three-api";
|
|
223
234
|
export * from "./Color";
|
|
224
235
|
export { type BlendMode, blendFunction, createReplacer } from "./utils";
|
|
@@ -309,7 +320,11 @@ export type Options = {
|
|
|
309
320
|
atmosphere?: AtmosphereOptions;
|
|
310
321
|
/** Background color of the scene. Defaults to dark color (0x0a0a0f). */
|
|
311
322
|
backgroundColor?: CoreColor;
|
|
312
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* Feature picking configuration (default: true). Enables the `featureClick`
|
|
325
|
+
* event (click or touch tap) and the `featureHover` / `featureEnter` /
|
|
326
|
+
* `featureLeave` events (pointer hover).
|
|
327
|
+
*/
|
|
313
328
|
picking?: boolean;
|
|
314
329
|
/** When true, renders every frame. When false, renders only on changes or when forceUpdate() is called. */
|
|
315
330
|
animation?: boolean;
|
|
@@ -400,12 +415,13 @@ export type Options = {
|
|
|
400
415
|
} & GlobeOptions;
|
|
401
416
|
|
|
402
417
|
/**
|
|
403
|
-
*
|
|
418
|
+
* Pointer event extended with map coordinates at the event location.
|
|
419
|
+
* Fired for every input type; `pointerType` tells them apart.
|
|
404
420
|
*/
|
|
405
|
-
export type
|
|
406
|
-
/** World coordinates (ECEF) at the
|
|
421
|
+
export type MapPointerEvent = {
|
|
422
|
+
/** World coordinates (ECEF) at the pointer position on the globe surface. */
|
|
407
423
|
map: XYZ;
|
|
408
|
-
} &
|
|
424
|
+
} & PointerEvent;
|
|
409
425
|
|
|
410
426
|
/**
|
|
411
427
|
* Event types emitted by ThreeView. Subscribe using `view.on(eventName, callback)`.
|
|
@@ -413,8 +429,24 @@ export type MapMouseEvent = {
|
|
|
413
429
|
export type ViewEvents = {
|
|
414
430
|
/** Emitted when the view is resized. Receives width and height in pixels. */
|
|
415
431
|
resize: (w: number, h: number) => void;
|
|
416
|
-
/**
|
|
417
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Emitted when a feature is clicked or tapped (click-gesture GPU pick).
|
|
434
|
+
* Receives the clicked feature, or null when empty space was clicked. For
|
|
435
|
+
* raw click coordinates use the `click` event instead.
|
|
436
|
+
*/
|
|
437
|
+
featureClick: (info: Nullable<PickedFeature>) => void;
|
|
438
|
+
/**
|
|
439
|
+
* Emitted when the hovered feature changes (hover picking). Receives
|
|
440
|
+
* the newly hovered feature, or null when the pointer leaves all pickable
|
|
441
|
+
* features. Hover picking runs a GPU pick per frame while the pointer
|
|
442
|
+
* moves, and is active only while at least one `featureHover` /
|
|
443
|
+
* `featureEnter` / `featureLeave` listener is registered.
|
|
444
|
+
*/
|
|
445
|
+
featureHover: (info: Nullable<PickedFeature>) => void;
|
|
446
|
+
/** Emitted when the pointer starts hovering a pickable feature. See `featureHover` for activation. */
|
|
447
|
+
featureEnter: (info: PickedFeature) => void;
|
|
448
|
+
/** Emitted when the pointer stops hovering the previously hovered feature. See `featureHover` for activation. */
|
|
449
|
+
featureLeave: (info: PickedFeature) => void;
|
|
418
450
|
/** Emitted when a layer event occurs. Receives event type, layer ID, and event arguments. */
|
|
419
451
|
layer: <K extends keyof LayerEvent>(
|
|
420
452
|
k: K,
|
|
@@ -431,18 +463,26 @@ export type ViewEvents = {
|
|
|
431
463
|
postRender: (t: number) => void;
|
|
432
464
|
/** @private Emitted when terrain height sampling completes. */
|
|
433
465
|
_sample_terrain_height_received: (ev: TerrainHeightUpdatedEvent) => void;
|
|
434
|
-
/** Emitted on
|
|
435
|
-
|
|
436
|
-
/** Emitted when
|
|
437
|
-
|
|
438
|
-
/** Emitted when
|
|
439
|
-
|
|
440
|
-
/** Emitted on
|
|
441
|
-
|
|
442
|
-
/** Emitted on
|
|
443
|
-
|
|
444
|
-
/** Emitted
|
|
445
|
-
|
|
466
|
+
/** Emitted on pointerdown with map coordinates, for every input type. */
|
|
467
|
+
pointerdown: (event: MapPointerEvent) => void;
|
|
468
|
+
/** Emitted when a pointer enters the canvas with map coordinates, for every input type. */
|
|
469
|
+
pointerenter: (event: MapPointerEvent) => void;
|
|
470
|
+
/** Emitted when a pointer leaves the canvas with map coordinates, for every input type. */
|
|
471
|
+
pointerleave: (event: MapPointerEvent) => void;
|
|
472
|
+
/** Emitted on pointermove with map coordinates, for every input type. */
|
|
473
|
+
pointermove: (event: MapPointerEvent) => void;
|
|
474
|
+
/** Emitted on pointerup with map coordinates, for every input type. */
|
|
475
|
+
pointerup: (event: MapPointerEvent) => void;
|
|
476
|
+
/** Emitted when the browser cancels an active pointer (e.g. a system gesture takes over). */
|
|
477
|
+
pointercancel: (event: PointerEvent) => void;
|
|
478
|
+
/**
|
|
479
|
+
* Emitted on a click or touch tap with map coordinates: a primary-pointer
|
|
480
|
+
* press and release within the travel tolerance
|
|
481
|
+
* ({@link CLICK_PIXEL_TOLERANCE}, or {@link TAP_PIXEL_TOLERANCE} for
|
|
482
|
+
* touch). Camera drags and pinches never fire it. `pointerType` tells the
|
|
483
|
+
* input kinds apart.
|
|
484
|
+
*/
|
|
485
|
+
click: (event: MapPointerEvent) => void;
|
|
446
486
|
/** Emitted when the engine becomes idle (no Rust-side events for `idleThreshold` ms). */
|
|
447
487
|
idle: () => void;
|
|
448
488
|
};
|
|
@@ -503,6 +543,9 @@ export default class ThreeView<
|
|
|
503
543
|
* `camera_flight_ended` events from the engine. */
|
|
504
544
|
private _pendingFlights = new Map<number, (completed: boolean) => void>();
|
|
505
545
|
|
|
546
|
+
/** Currently hovered feature, used to diff-synthesize hover events. */
|
|
547
|
+
private _hoveredFeature: Nullable<PickedFeature> = null;
|
|
548
|
+
|
|
506
549
|
private _meshes: MeshCache = new Map();
|
|
507
550
|
private _abortControllers: AbortControllers = new Map();
|
|
508
551
|
private _workerPoolPromises: WorkerPoolPromises = new Map();
|
|
@@ -1150,9 +1193,9 @@ export default class ThreeView<
|
|
|
1150
1193
|
}
|
|
1151
1194
|
|
|
1152
1195
|
/**
|
|
1153
|
-
*
|
|
1196
|
+
* Extend a pointer event with map coordinates at its location
|
|
1154
1197
|
*/
|
|
1155
|
-
private
|
|
1198
|
+
private convertToMapEvent(event: PointerEvent): MapPointerEvent | null {
|
|
1156
1199
|
const rect = this._renderer.domElement.getBoundingClientRect();
|
|
1157
1200
|
const x = event.clientX - rect.left;
|
|
1158
1201
|
const y = event.clientY - rect.top;
|
|
@@ -1165,7 +1208,7 @@ export default class ThreeView<
|
|
|
1165
1208
|
y: xyz.y,
|
|
1166
1209
|
z: xyz.z,
|
|
1167
1210
|
},
|
|
1168
|
-
})
|
|
1211
|
+
});
|
|
1169
1212
|
}
|
|
1170
1213
|
|
|
1171
1214
|
return null;
|
|
@@ -1562,6 +1605,17 @@ export default class ThreeView<
|
|
|
1562
1605
|
this._camera.raw,
|
|
1563
1606
|
this._meshes,
|
|
1564
1607
|
this.onPick.bind(this),
|
|
1608
|
+
this.onHoverPick.bind(this),
|
|
1609
|
+
// Picks cost a GPU render (plus a re-render to restore draped
|
|
1610
|
+
// atlases), so both gestures stay off until someone actually
|
|
1611
|
+
// listens for the result.
|
|
1612
|
+
() => !!this.size("featureClick"),
|
|
1613
|
+
() =>
|
|
1614
|
+
!!(
|
|
1615
|
+
this.size("featureHover") ||
|
|
1616
|
+
this.size("featureEnter") ||
|
|
1617
|
+
this.size("featureLeave")
|
|
1618
|
+
),
|
|
1565
1619
|
// {
|
|
1566
1620
|
// debug: true,
|
|
1567
1621
|
// },
|
|
@@ -1590,47 +1644,75 @@ export default class ThreeView<
|
|
|
1590
1644
|
|
|
1591
1645
|
this._camera.core = this._core;
|
|
1592
1646
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1647
|
+
// The map events are fed from pointer events; touch produces no
|
|
1648
|
+
// compatibility mouse events because the camera-input handlers call
|
|
1649
|
+
// preventDefault() on touch events. `click` is a gesture, not the DOM
|
|
1650
|
+
// event: a primary-pointer press and release within the travel
|
|
1651
|
+
// tolerance, with no pinch and no cancellation. Camera drags never
|
|
1652
|
+
// fire it.
|
|
1653
|
+
let clickStart: Vector2 | undefined;
|
|
1654
|
+
|
|
1655
|
+
this._renderer.domElement.addEventListener("pointerdown", (event) => {
|
|
1656
|
+
// A second finger turns the gesture into a pinch, not a tap.
|
|
1657
|
+
clickStart =
|
|
1658
|
+
event.isPrimary && event.button === 0
|
|
1659
|
+
? new Vector2(event.clientX, event.clientY)
|
|
1660
|
+
: undefined;
|
|
1661
|
+
const mapEvent = this.convertToMapEvent(event);
|
|
1595
1662
|
if (mapEvent) {
|
|
1596
|
-
this.emit("
|
|
1663
|
+
this.emit("pointerdown", mapEvent);
|
|
1597
1664
|
}
|
|
1598
1665
|
});
|
|
1599
1666
|
|
|
1600
|
-
this._renderer.domElement.addEventListener("
|
|
1601
|
-
const mapEvent = this.
|
|
1667
|
+
this._renderer.domElement.addEventListener("pointerenter", (event) => {
|
|
1668
|
+
const mapEvent = this.convertToMapEvent(event);
|
|
1602
1669
|
if (mapEvent) {
|
|
1603
|
-
this.emit("
|
|
1670
|
+
this.emit("pointerenter", mapEvent);
|
|
1604
1671
|
}
|
|
1605
1672
|
});
|
|
1606
1673
|
|
|
1607
|
-
this._renderer.domElement.addEventListener("
|
|
1608
|
-
const mapEvent = this.
|
|
1674
|
+
this._renderer.domElement.addEventListener("pointerleave", (event) => {
|
|
1675
|
+
const mapEvent = this.convertToMapEvent(event);
|
|
1609
1676
|
if (mapEvent) {
|
|
1610
|
-
this.emit("
|
|
1677
|
+
this.emit("pointerleave", mapEvent);
|
|
1611
1678
|
}
|
|
1612
1679
|
});
|
|
1613
1680
|
|
|
1614
|
-
this._renderer.domElement.addEventListener("
|
|
1615
|
-
const mapEvent = this.
|
|
1681
|
+
this._renderer.domElement.addEventListener("pointermove", (event) => {
|
|
1682
|
+
const mapEvent = this.convertToMapEvent(event);
|
|
1616
1683
|
if (mapEvent) {
|
|
1617
|
-
this.emit("
|
|
1684
|
+
this.emit("pointermove", mapEvent);
|
|
1618
1685
|
}
|
|
1619
1686
|
});
|
|
1620
1687
|
|
|
1621
|
-
this._renderer.domElement.addEventListener("
|
|
1622
|
-
const mapEvent = this.
|
|
1688
|
+
this._renderer.domElement.addEventListener("pointerup", (event) => {
|
|
1689
|
+
const mapEvent = this.convertToMapEvent(event);
|
|
1623
1690
|
if (mapEvent) {
|
|
1624
|
-
this.emit("
|
|
1691
|
+
this.emit("pointerup", mapEvent);
|
|
1625
1692
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1693
|
+
if (!event.isPrimary || event.button !== 0) return;
|
|
1694
|
+
const start = clickStart;
|
|
1695
|
+
clickStart = undefined;
|
|
1696
|
+
if (!start || !mapEvent) return;
|
|
1697
|
+
const tolerance =
|
|
1698
|
+
event.pointerType === "touch"
|
|
1699
|
+
? TAP_PIXEL_TOLERANCE
|
|
1700
|
+
: CLICK_PIXEL_TOLERANCE;
|
|
1701
|
+
if (
|
|
1702
|
+
isClickGesture(
|
|
1703
|
+
start,
|
|
1704
|
+
new Vector2(event.clientX, event.clientY),
|
|
1705
|
+
tolerance,
|
|
1706
|
+
)
|
|
1707
|
+
) {
|
|
1631
1708
|
this.emit("click", mapEvent);
|
|
1632
1709
|
}
|
|
1633
1710
|
});
|
|
1711
|
+
|
|
1712
|
+
this._renderer.domElement.addEventListener("pointercancel", (event) => {
|
|
1713
|
+
clickStart = undefined;
|
|
1714
|
+
this.emit("pointercancel", event);
|
|
1715
|
+
});
|
|
1634
1716
|
}
|
|
1635
1717
|
|
|
1636
1718
|
/**
|
|
@@ -2040,6 +2122,33 @@ export default class ThreeView<
|
|
|
2040
2122
|
return new Source(sourceId, s.type, this._core);
|
|
2041
2123
|
}
|
|
2042
2124
|
|
|
2125
|
+
/**
|
|
2126
|
+
* Tracks a possibly-async `onCreate()` (see `BaseDesc.onCreate`): the
|
|
2127
|
+
* `add*` methods still return their handle synchronously, the descriptor's
|
|
2128
|
+
* instance appears when the promise settles, and a rejection is logged and
|
|
2129
|
+
* reported through the descriptor's `error` event (when it declares one).
|
|
2130
|
+
*
|
|
2131
|
+
* `wire` binds the descriptor's lifecycle hooks (per-frame `update`,
|
|
2132
|
+
* `onResize`, `needsUpdate`). It runs immediately for a synchronous
|
|
2133
|
+
* creation, and only after the promise resolves for an async one — the
|
|
2134
|
+
* hooks must not fire while the descriptor has no instance yet.
|
|
2135
|
+
*/
|
|
2136
|
+
private _trackAsyncCreate(
|
|
2137
|
+
kind: "mesh" | "light" | "effect",
|
|
2138
|
+
desc: { emit: (event: never, ...args: never[]) => void },
|
|
2139
|
+
result: void | Promise<void>,
|
|
2140
|
+
wire: () => void,
|
|
2141
|
+
): void {
|
|
2142
|
+
if (!(result instanceof Promise)) {
|
|
2143
|
+
wire();
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
result.then(wire, (error: unknown) => {
|
|
2147
|
+
console.error(`Failed to create ${kind} descriptor:`, error);
|
|
2148
|
+
(desc.emit as (event: string, payload: unknown) => void)("error", error);
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2043
2152
|
/**
|
|
2044
2153
|
* Adds a 3D mesh to the scene.
|
|
2045
2154
|
* The mesh kind is determined by the nested key (e.g., `{ box: { width: 200 } }`).
|
|
@@ -2058,28 +2167,35 @@ export default class ThreeView<
|
|
|
2058
2167
|
// Create mesh descriptor instance
|
|
2059
2168
|
const meshDesc = this.registries.mesh.create(meshType, config);
|
|
2060
2169
|
|
|
2061
|
-
// Initialize the mesh
|
|
2062
|
-
meshDesc.onCreate()
|
|
2170
|
+
// Initialize the mesh; hooks are wired once the instance exists.
|
|
2171
|
+
this._trackAsyncCreate("mesh", meshDesc, meshDesc.onCreate(), () => {
|
|
2172
|
+
// If deleted while an async creation was loading, nothing to wire.
|
|
2173
|
+
if (meshDesc.destroyed) return;
|
|
2063
2174
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2175
|
+
// Set up update listener
|
|
2176
|
+
if (meshDesc.update) {
|
|
2177
|
+
this.on("preRender", meshDesc.update.bind(meshDesc));
|
|
2178
|
+
}
|
|
2068
2179
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2180
|
+
if (meshDesc.onResize) {
|
|
2181
|
+
this.on("resize", meshDesc.onResize.bind(meshDesc));
|
|
2071
2182
|
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2183
|
+
const canvasSize = this._getCanvasSize();
|
|
2184
|
+
if (canvasSize) {
|
|
2185
|
+
meshDesc.onResize(canvasSize.width, canvasSize.height);
|
|
2186
|
+
}
|
|
2075
2187
|
}
|
|
2076
|
-
}
|
|
2077
2188
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2189
|
+
// Trigger re-render
|
|
2190
|
+
meshDesc.on("needsUpdate", this.forceUpdate);
|
|
2191
|
+
|
|
2192
|
+
this.forceUpdate();
|
|
2193
|
+
});
|
|
2080
2194
|
|
|
2081
2195
|
const l = new MeshHandle(meshDesc);
|
|
2082
2196
|
|
|
2197
|
+
l.on("deleted", this.forceUpdate);
|
|
2198
|
+
|
|
2083
2199
|
// Store the mesh descriptor
|
|
2084
2200
|
this.layersManager.add(l);
|
|
2085
2201
|
|
|
@@ -2105,19 +2221,26 @@ export default class ThreeView<
|
|
|
2105
2221
|
// Create light descriptor instance
|
|
2106
2222
|
const lightDesc = this.registries.light.create(lightType, config);
|
|
2107
2223
|
|
|
2108
|
-
// Initialize the light
|
|
2109
|
-
lightDesc.onCreate()
|
|
2224
|
+
// Initialize the light; hooks are wired once the instance exists.
|
|
2225
|
+
this._trackAsyncCreate("light", lightDesc, lightDesc.onCreate(), () => {
|
|
2226
|
+
// Deleted while an async creation was loading — nothing to wire.
|
|
2227
|
+
if (lightDesc.destroyed) return;
|
|
2110
2228
|
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2229
|
+
// Set up update listener if the descriptor has an update method
|
|
2230
|
+
if (lightDesc.update) {
|
|
2231
|
+
this.on("preRender", lightDesc.update.bind(lightDesc));
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// Trigger re-render
|
|
2235
|
+
lightDesc.on("needsUpdate", this.forceUpdate);
|
|
2115
2236
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2237
|
+
this.forceUpdate();
|
|
2238
|
+
});
|
|
2118
2239
|
|
|
2119
2240
|
const l = new LightHandle(lightDesc);
|
|
2120
2241
|
|
|
2242
|
+
l.on("deleted", this.forceUpdate);
|
|
2243
|
+
|
|
2121
2244
|
// Store the light descriptor
|
|
2122
2245
|
this.layersManager.add(l);
|
|
2123
2246
|
|
|
@@ -2160,18 +2283,26 @@ export default class ThreeView<
|
|
|
2160
2283
|
// and recompiles shaders when the derived configuration actually changes,
|
|
2161
2284
|
// so tune effects via `update()` rather than re-adding them.
|
|
2162
2285
|
this._syncGBuffers();
|
|
2163
|
-
l.on("deleted",
|
|
2286
|
+
l.on("deleted", () => {
|
|
2287
|
+
this._syncGBuffers();
|
|
2288
|
+
this.forceUpdate();
|
|
2289
|
+
});
|
|
2164
2290
|
|
|
2165
|
-
// Initialize the effect
|
|
2166
|
-
effectDesc.onCreate()
|
|
2291
|
+
// Initialize the effect; hooks are wired once the instance exists.
|
|
2292
|
+
this._trackAsyncCreate("effect", effectDesc, effectDesc.onCreate(), () => {
|
|
2293
|
+
// Deleted while an async creation was loading — nothing to wire.
|
|
2294
|
+
if (effectDesc.destroyed) return;
|
|
2167
2295
|
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2296
|
+
// Set up update listener if the descriptor has an update method
|
|
2297
|
+
if (effectDesc.update) {
|
|
2298
|
+
this.on("preRender", effectDesc.update.bind(effectDesc));
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// Trigger re-render
|
|
2302
|
+
effectDesc.on("needsUpdate", this.forceUpdate);
|
|
2172
2303
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2304
|
+
this.forceUpdate();
|
|
2305
|
+
});
|
|
2175
2306
|
|
|
2176
2307
|
// Return handle for imperative access
|
|
2177
2308
|
return l as EffectHandle<L>;
|
|
@@ -2621,11 +2752,11 @@ export default class ThreeView<
|
|
|
2621
2752
|
|
|
2622
2753
|
/**
|
|
2623
2754
|
* Samples the terrain height at a given geodetic position synchronously.
|
|
2624
|
-
* @param pos - Geodetic position (lat in
|
|
2755
|
+
* @param pos - Geodetic position (lat in degrees, lng in degrees)
|
|
2625
2756
|
* @returns Terrain height in meters, or undefined if terrain data not loaded
|
|
2626
2757
|
*/
|
|
2627
2758
|
sampleTerrainHeight(pos: LatLng): number | undefined {
|
|
2628
|
-
const lle = new LLE(pos.lat, pos.lng, 0);
|
|
2759
|
+
const lle = new LLE(degreeToRadian(pos.lat), degreeToRadian(pos.lng), 0);
|
|
2629
2760
|
return this._core?.sampleTerrainHeight(lle);
|
|
2630
2761
|
}
|
|
2631
2762
|
|
|
@@ -2643,7 +2774,7 @@ export default class ThreeView<
|
|
|
2643
2774
|
* sampled.
|
|
2644
2775
|
*
|
|
2645
2776
|
* @param source - A registered terrain source (handle or id)
|
|
2646
|
-
* @param positions - Geodetic positions (lat/lng in
|
|
2777
|
+
* @param positions - Geodetic positions (lat/lng in degrees)
|
|
2647
2778
|
* @param options - Optional abort signal and fixed-level sampling
|
|
2648
2779
|
* @returns One result per input position, in the same order
|
|
2649
2780
|
*/
|
|
@@ -2669,7 +2800,7 @@ export default class ThreeView<
|
|
|
2669
2800
|
|
|
2670
2801
|
/**
|
|
2671
2802
|
* Observes terrain height changes at a position. Callback is invoked each time terrain data updates.
|
|
2672
|
-
* @param pos - Geodetic position to observe (lat in
|
|
2803
|
+
* @param pos - Geodetic position to observe (lat in degrees, lng in degrees)
|
|
2673
2804
|
* @param cb - Callback function receiving the terrain height in meters
|
|
2674
2805
|
* @returns Cleanup function to stop observing
|
|
2675
2806
|
*/
|
|
@@ -2681,7 +2812,7 @@ export default class ThreeView<
|
|
|
2681
2812
|
return () => {};
|
|
2682
2813
|
}
|
|
2683
2814
|
|
|
2684
|
-
const lle = new LLE(pos.lat, pos.lng, 0);
|
|
2815
|
+
const lle = new LLE(degreeToRadian(pos.lat), degreeToRadian(pos.lng), 0);
|
|
2685
2816
|
const entityBits = this._core.registerSampleTerrainHeightEvent(lle);
|
|
2686
2817
|
|
|
2687
2818
|
const callFunc = (ev: TerrainHeightUpdatedEvent) => {
|
|
@@ -2778,6 +2909,29 @@ export default class ThreeView<
|
|
|
2778
2909
|
this.resize(width, height, pixelRatio);
|
|
2779
2910
|
};
|
|
2780
2911
|
|
|
2912
|
+
/**
|
|
2913
|
+
* Resolves a picked batch ID into feature information via the engine's
|
|
2914
|
+
* property store.
|
|
2915
|
+
*/
|
|
2916
|
+
private resolvePickedFeature(batchId: number): PickedFeature {
|
|
2917
|
+
const prop = this._core?.readPropertyByGlobalBatchId(batchId);
|
|
2918
|
+
if (prop) {
|
|
2919
|
+
return {
|
|
2920
|
+
properties: prop.properties,
|
|
2921
|
+
// The GPU pick returns the clicked instance's id; report the
|
|
2922
|
+
// feature's canonical batch id instead so it matches the id the
|
|
2923
|
+
// evaluator exposes (a MultiPoint feature spans many instances).
|
|
2924
|
+
batchId: prop.batchId ?? batchId,
|
|
2925
|
+
layerId: prop.layerId,
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
return {
|
|
2929
|
+
properties: {},
|
|
2930
|
+
batchId,
|
|
2931
|
+
layerId: undefined,
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2781
2935
|
/**
|
|
2782
2936
|
* Handles pick events and emits the picked feature information.
|
|
2783
2937
|
* @param pickArr - Array of picked batch IDs
|
|
@@ -2786,28 +2940,34 @@ export default class ThreeView<
|
|
|
2786
2940
|
this._renderFlag.forceUpdate = true;
|
|
2787
2941
|
|
|
2788
2942
|
if (pickArr.length > 0) {
|
|
2789
|
-
|
|
2790
|
-
const prop = this._core?.readPropertyByGlobalBatchId(batchId);
|
|
2791
|
-
if (prop) {
|
|
2792
|
-
const pickedFeature: PickedFeature = {
|
|
2793
|
-
properties: prop.properties,
|
|
2794
|
-
batchId,
|
|
2795
|
-
layerId: prop.layerId,
|
|
2796
|
-
};
|
|
2797
|
-
this.emit("pick", pickedFeature);
|
|
2798
|
-
} else {
|
|
2799
|
-
const emptyFeature: PickedFeature = {
|
|
2800
|
-
properties: {},
|
|
2801
|
-
batchId,
|
|
2802
|
-
layerId: undefined,
|
|
2803
|
-
};
|
|
2804
|
-
this.emit("pick", emptyFeature);
|
|
2805
|
-
}
|
|
2943
|
+
this.emit("featureClick", this.resolvePickedFeature(pickArr[0]));
|
|
2806
2944
|
} else {
|
|
2807
|
-
this.emit("
|
|
2945
|
+
this.emit("featureClick", null);
|
|
2808
2946
|
}
|
|
2809
2947
|
}
|
|
2810
2948
|
|
|
2949
|
+
/**
|
|
2950
|
+
* Handles hover pick results and synthesizes `featureHover` / `featureEnter` /
|
|
2951
|
+
* `featureLeave` events by diffing against the previously hovered feature.
|
|
2952
|
+
* @param pickArr - Array of picked batch IDs
|
|
2953
|
+
*/
|
|
2954
|
+
private onHoverPick(pickArr: number[]) {
|
|
2955
|
+
// The pick render re-bakes draped vector atlases in pick mode; a main
|
|
2956
|
+
// render is needed to restore their normal colors.
|
|
2957
|
+
this._renderFlag.forceUpdate = true;
|
|
2958
|
+
|
|
2959
|
+
const batchId = pickArr.length > 0 ? pickArr[0] : 0;
|
|
2960
|
+
const prev = this._hoveredFeature;
|
|
2961
|
+
if ((prev?.batchId ?? 0) === batchId) return;
|
|
2962
|
+
|
|
2963
|
+
const feature = batchId > 0 ? this.resolvePickedFeature(batchId) : null;
|
|
2964
|
+
this._hoveredFeature = feature;
|
|
2965
|
+
|
|
2966
|
+
if (prev) this.emit("featureLeave", prev);
|
|
2967
|
+
if (feature) this.emit("featureEnter", feature);
|
|
2968
|
+
this.emit("featureHover", feature);
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2811
2971
|
/**
|
|
2812
2972
|
* Gets whether continuous animation mode is enabled.
|
|
2813
2973
|
*/
|
|
@@ -2821,6 +2981,14 @@ export default class ThreeView<
|
|
|
2821
2981
|
this._renderFlag.animation = v;
|
|
2822
2982
|
}
|
|
2823
2983
|
|
|
2984
|
+
/**
|
|
2985
|
+
* Gets the canvas element the view renders into. Useful for styling,
|
|
2986
|
+
* e.g. switching the cursor from a `featureHover` event listener.
|
|
2987
|
+
*/
|
|
2988
|
+
get canvas(): HTMLCanvasElement {
|
|
2989
|
+
return this._renderer.domElement;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2824
2992
|
/**
|
|
2825
2993
|
* Gets the current screen size in pixels.
|
|
2826
2994
|
*/
|
|
@@ -52,7 +52,7 @@ export const transformShader = (
|
|
|
52
52
|
#include <packing>
|
|
53
53
|
|
|
54
54
|
in float batchId;
|
|
55
|
-
out float nvr_vBatchId;
|
|
55
|
+
flat out float nvr_vBatchId;
|
|
56
56
|
out vec3 vPosition;
|
|
57
57
|
|
|
58
58
|
${ShowParsVertex}
|
|
@@ -93,7 +93,7 @@ uniform float nvr_uPickable;
|
|
|
93
93
|
// uEffectIdsMask is declared by overrideMaterialsForMRT (#ifdef USE_SELECTIVE_EFFECT block)
|
|
94
94
|
${MODEL_BASE_SHADER_MARKERS.fragment.UNIFORM_END}
|
|
95
95
|
|
|
96
|
-
in float nvr_vBatchId;
|
|
96
|
+
flat in float nvr_vBatchId;
|
|
97
97
|
|
|
98
98
|
${ShowParsFragment}
|
|
99
99
|
|
|
@@ -68,7 +68,7 @@ in float attrBatchId;
|
|
|
68
68
|
in vec4 scaleNormalAndCap;
|
|
69
69
|
|
|
70
70
|
uniform vec2 uMinMaxHeight;
|
|
71
|
-
out float nvr_vBatchId;
|
|
71
|
+
flat out float nvr_vBatchId;
|
|
72
72
|
|
|
73
73
|
${useRTE ? RteParsVertex : ""}
|
|
74
74
|
${ShowParsVertex}
|
|
@@ -162,7 +162,7 @@ uniform float nvr_uPickable;
|
|
|
162
162
|
uniform bool uIsTexturized;
|
|
163
163
|
${POLYGON_BASE_SHADER_MARKERS.fragment.UNIFORM_END}
|
|
164
164
|
|
|
165
|
-
in float nvr_vBatchId;
|
|
165
|
+
flat in float nvr_vBatchId;
|
|
166
166
|
|
|
167
167
|
${ShowParsFragment}
|
|
168
168
|
|
|
@@ -97,8 +97,18 @@ export function generateTileMapFragment(
|
|
|
97
97
|
}`
|
|
98
98
|
: "";
|
|
99
99
|
return `
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// The atlases are linear-filtered for display, but the pick pass reads
|
|
101
|
+
// encoded batch ids out of atlasColor: interpolating id colors at feature
|
|
102
|
+
// edges decodes to batch ids that don't exist (a thin draped polyline is
|
|
103
|
+
// almost all edge pixels). Snap to the texel center (nearest sampling)
|
|
104
|
+
// while picking.
|
|
105
|
+
vec2 nvr_atlasUv = vOrigUv;
|
|
106
|
+
if (uPickable > 0.) {
|
|
107
|
+
vec2 nvr_atlasSize = vec2(textureSize(uColorAtlas, 0));
|
|
108
|
+
nvr_atlasUv = (floor(vOrigUv * nvr_atlasSize) + 0.5) / nvr_atlasSize;
|
|
109
|
+
}
|
|
110
|
+
vec4 atlasColor = texture2D(uColorAtlas, nvr_atlasUv);
|
|
111
|
+
vec4 atlasAttr = texture2D(uAttrAtlas, nvr_atlasUv);
|
|
102
112
|
|
|
103
113
|
// attr.a encodes (winningSlot + 1) / 255 — 0 means no contributing slot.
|
|
104
114
|
int winIdx = int(round(atlasAttr.a * 255.0)) - 1;
|