@navaramap/three 0.0.0 → 0.0.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/dist/assets/fontWorker-CCWL5pbx.js +2 -0
- package/dist/assets/fontWorker-CCWL5pbx.js.map +1 -0
- package/dist/assets/index-BdtHOOT9.js +12 -0
- package/dist/assets/index-BdtHOOT9.js.map +1 -0
- package/dist/assets/{navara_wasm_font_worker_bg-665OLxjy.wasm → navara_wasm_font_worker_bg-Bnck8OGf.wasm} +0 -0
- package/dist/assets/{navara_wasm_worker_bg-BXhKC5bE.wasm → navara_wasm_worker_bg-DG9ORAmj.wasm} +0 -0
- package/dist/index.d.ts +142 -51
- package/dist/index.js +4772 -4510
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +226 -218
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +13 -13
- package/src/event/context.ts +5 -0
- package/src/index.ts +10 -2
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/mutates.ts +6 -1
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/shader.ts +9 -2
- package/src/mesh/tile/drapeResolver.ts +76 -0
- package/src/mesh/{tile.test.ts → tile/index.test.ts} +16 -6
- package/src/mesh/{tile.ts → tile/index.ts} +124 -392
- package/src/mesh/tile/rasterDrapeResolver.ts +423 -0
- package/src/mesh/tile/vectorDrapeResolver.ts +404 -0
- package/src/tileTexture/TileTextureCache.test.ts +2 -0
- package/src/tileTexture/TileTextureCache.ts +1 -0
- package/src/tileTexture/TileTextureCompositor.test.ts +105 -0
- package/src/tileTexture/TileTextureCompositor.ts +241 -31
- package/src/tileTexture/__snapshots__/CompositeShader.snapshot.test.ts.snap +36 -16
- package/src/tileTexture/types.ts +7 -0
- package/src/utils/demNoDataColor.test.ts +47 -0
- package/src/utils/demNoDataColor.ts +48 -0
- package/dist/assets/fontWorker-CEJEfRpb.js +0 -2
- package/dist/assets/fontWorker-CEJEfRpb.js.map +0 -1
- package/dist/assets/index-BUB-ijMO.js +0 -12
- package/dist/assets/index-BUB-ijMO.js.map +0 -1
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
Mesh,
|
|
21
21
|
MeshLambertMaterial,
|
|
22
22
|
RedFormat,
|
|
23
|
-
RGBAFormat,
|
|
24
23
|
SRGBColorSpace,
|
|
25
24
|
Texture,
|
|
26
25
|
UnsignedByteType,
|
|
@@ -29,7 +28,6 @@ import {
|
|
|
29
28
|
Vector4,
|
|
30
29
|
AddOperation,
|
|
31
30
|
Object3D,
|
|
32
|
-
WebGLRenderTarget,
|
|
33
31
|
type MagnificationTextureFilter,
|
|
34
32
|
type MinificationTextureFilter,
|
|
35
33
|
ShaderChunk,
|
|
@@ -41,9 +39,8 @@ import {
|
|
|
41
39
|
LinearFilter,
|
|
42
40
|
} from "three";
|
|
43
41
|
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import type { EventContext, TileHandler } from "../event/context";
|
|
42
|
+
import { setTransform } from "../../event";
|
|
43
|
+
import type { EventContext, TileHandler } from "../../event/context";
|
|
47
44
|
import {
|
|
48
45
|
generateTileCommonInjection,
|
|
49
46
|
generateTileMapFragment,
|
|
@@ -52,61 +49,37 @@ import {
|
|
|
52
49
|
TILE_NORMAL_BUFFER_REPLACEMENT,
|
|
53
50
|
TILE_PICK_FRAGMENT_OVERRIDE,
|
|
54
51
|
TILE_VERTEX_INJECTIONS,
|
|
55
|
-
} from "
|
|
56
|
-
import { deriveCompositeFeatures } from "
|
|
57
|
-
import type { CustomObject3DEventMap } from "
|
|
58
|
-
import type {
|
|
59
|
-
import type { TextureOptions } from "../textures";
|
|
52
|
+
} from "../../material";
|
|
53
|
+
import { deriveCompositeFeatures } from "../../material/enhancer/tileComposite";
|
|
54
|
+
import type { CustomObject3DEventMap } from "../../object3DEvent";
|
|
55
|
+
import type { TextureOptions } from "../../textures";
|
|
60
56
|
import {
|
|
61
57
|
planSlots,
|
|
62
58
|
type CompositeFeatures,
|
|
63
59
|
type CompositeGlobals,
|
|
64
60
|
type CompositeLayer,
|
|
65
61
|
type TileTextureCompositor,
|
|
66
|
-
} from "
|
|
67
|
-
import type { TileMapByHandle } from "
|
|
68
|
-
import type { CommonUniforms } from "
|
|
69
|
-
import { createReplacer } from "
|
|
62
|
+
} from "../../tileTexture";
|
|
63
|
+
import type { TileMapByHandle } from "../../type";
|
|
64
|
+
import type { CommonUniforms } from "../../uniforms";
|
|
65
|
+
import { createReplacer } from "../../utils";
|
|
70
66
|
import {
|
|
71
67
|
type TextureSlot,
|
|
72
68
|
updateTextureFragmentIndex,
|
|
73
69
|
removeTextureFragmentIndex,
|
|
74
|
-
} from "
|
|
70
|
+
} from "../../utils/textureFragmentIndex";
|
|
71
|
+
import type { PickableMesh } from "../pickableMesh";
|
|
72
|
+
import { releaseGeometryArraysAfterUpload } from "../releaseGeometryArrays";
|
|
75
73
|
|
|
76
|
-
import type {
|
|
77
|
-
import {
|
|
74
|
+
import type { DrapeResolver } from "./drapeResolver";
|
|
75
|
+
import {
|
|
76
|
+
BakedRasterDrapeResolver,
|
|
77
|
+
DirectRasterDrapeResolver,
|
|
78
|
+
} from "./rasterDrapeResolver";
|
|
79
|
+
import { VectorDrapeResolver } from "./vectorDrapeResolver";
|
|
78
80
|
|
|
79
81
|
export type TileMaterial = MeshBasicMaterial | MeshLambertMaterial;
|
|
80
82
|
|
|
81
|
-
/**
|
|
82
|
-
* One WebMercator vector source tile baked into a layer's render target.
|
|
83
|
-
* `tileHandle` keys the cached offscreen scene; `uvOffset`/`uvScale` are the
|
|
84
|
-
* **mercator** affine framing the terrain tile's sub-region of that (possibly
|
|
85
|
-
* coarser, ancestor-resolved) source tile, driving the bake camera (identity for
|
|
86
|
-
* an exact same-tile drape).
|
|
87
|
-
*/
|
|
88
|
-
type VectorSource = {
|
|
89
|
-
tileHandle: TileHandle;
|
|
90
|
-
uvOffset: [number, number];
|
|
91
|
-
uvScale: [number, number];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* One clamp-to-ground vector layer draped on this terrain tile, resolved by Rust.
|
|
96
|
-
* A layer is backed by one source on WebMercator terrain, but by several when the
|
|
97
|
-
* terrain is Geographic (N:M): every overlapping WM vector tile is baked into the
|
|
98
|
-
* layer's single render target, each framed by its own `uvOffset`/`uvScale`, so
|
|
99
|
-
* the RT ends up spanning the terrain tile's extent. `reproject` carries the
|
|
100
|
-
* terrain `[south, north]` band on Geographic terrain (undefined on WM) so the
|
|
101
|
-
* composite paste can remap the latitude axis WebMercator→Geographic, exactly
|
|
102
|
-
* like a raster slot.
|
|
103
|
-
*/
|
|
104
|
-
type VectorSlot = {
|
|
105
|
-
layerId: string;
|
|
106
|
-
sources: VectorSource[];
|
|
107
|
-
reproject?: [number, number];
|
|
108
|
-
};
|
|
109
|
-
|
|
110
83
|
/** The MRT composite atlas has three RGBA attachments (color, attr, normal). */
|
|
111
84
|
const ATLAS_ATTACHMENTS = 3;
|
|
112
85
|
/** GPU bytes for one drape render target at side length `size`: size² RGBA. */
|
|
@@ -126,32 +99,6 @@ function compositeAtlasBytes(size: number): number {
|
|
|
126
99
|
return size * size * 4 * ATLAS_ATTACHMENTS;
|
|
127
100
|
}
|
|
128
101
|
|
|
129
|
-
/** Shared 1×1 transparent texture bound to texturized sampler slots that have
|
|
130
|
-
* no drape render target yet (they are allocated lazily). WebGL requires every
|
|
131
|
-
* sampler to reference a valid texture; the shader gates sampling with `shows`,
|
|
132
|
-
* so this placeholder's content is never visible. */
|
|
133
|
-
const EMPTY_TEXTURIZED_TEXTURE = new DataTexture(
|
|
134
|
-
new Uint8Array([0, 0, 0, 0]),
|
|
135
|
-
1,
|
|
136
|
-
1,
|
|
137
|
-
RGBAFormat,
|
|
138
|
-
);
|
|
139
|
-
EMPTY_TEXTURIZED_TEXTURE.needsUpdate = true;
|
|
140
|
-
|
|
141
|
-
/** Apply the shared texturized-slot sampler settings to a drape render target's
|
|
142
|
-
* texture. Returns the texture for call-site chaining. */
|
|
143
|
-
function configureTexturizedTexture(
|
|
144
|
-
tex: Texture,
|
|
145
|
-
textureOptions: TextureOptions,
|
|
146
|
-
): Texture {
|
|
147
|
-
tex.minFilter = textureOptions.minFilter as MinificationTextureFilter;
|
|
148
|
-
tex.magFilter = textureOptions.magFilter as MagnificationTextureFilter;
|
|
149
|
-
tex.anisotropy = textureOptions.maxAnisotropy;
|
|
150
|
-
tex.generateMipmaps = textureOptions.useMipmaps;
|
|
151
|
-
tex.needsUpdate = true;
|
|
152
|
-
return tex;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
102
|
export class TileMesh
|
|
156
103
|
extends Mesh<BufferGeometry, TileMaterial, CustomObject3DEventMap>
|
|
157
104
|
implements PickableMesh
|
|
@@ -162,24 +109,19 @@ export class TileMesh
|
|
|
162
109
|
texturizedSceneIndexFrom: number;
|
|
163
110
|
numTexturizedVector: number;
|
|
164
111
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
private
|
|
171
|
-
|
|
172
|
-
// is skipped while the global revision is unchanged (the resolve result can only change
|
|
173
|
-
// when a traverse runs or a scene becomes ready). `-1` forces a fetch on the first frame.
|
|
174
|
-
private _lastVectorRevision = -1;
|
|
112
|
+
// The tile's two drape resolvers (see `drapeResolver.ts`): the vector one is
|
|
113
|
+
// always the baked path (WebMercator is its degenerate identity case), the
|
|
114
|
+
// raster one is chosen at construction from the globe tiling scheme —
|
|
115
|
+
// direct 1:1 material slots on WebMercator terrain, baked per-layer render
|
|
116
|
+
// targets (with Mercator latitude reprojection) on Geographic terrain.
|
|
117
|
+
private readonly vectorDrape: DrapeResolver;
|
|
118
|
+
private readonly rasterDrape: DrapeResolver;
|
|
175
119
|
|
|
176
120
|
// Separate mesh for shadow casting (uses terrain-only geometry without skirt)
|
|
177
121
|
private shadowMesh?: Mesh<BufferGeometry, TileMaterial>;
|
|
178
122
|
|
|
179
|
-
private texturizedSceneByTileCoordinates: TexturizedSceneByTileCoordinates;
|
|
180
123
|
private compositor: TileTextureCompositor;
|
|
181
124
|
|
|
182
|
-
texturizedSceneRenderTargets: WebGLRenderTarget[] = [];
|
|
183
125
|
// Drape RT side length + atlas byte cost, read from the compositor (the
|
|
184
126
|
// single source of truth shared with the Rust cost hints) in the constructor.
|
|
185
127
|
// Per-instance rather than module constants because the compositor size is a
|
|
@@ -193,8 +135,6 @@ export class TileMesh
|
|
|
193
135
|
// never reports. Assigned in the constructor once `atlasBytes` is known.
|
|
194
136
|
private reportedDrapeGpuBytes: number;
|
|
195
137
|
|
|
196
|
-
private warnedExceededTextures = false;
|
|
197
|
-
|
|
198
138
|
readonly ctx: EventContext;
|
|
199
139
|
|
|
200
140
|
constructor(ctx: EventContext, mesh: MeshAdded) {
|
|
@@ -212,7 +152,6 @@ export class TileMesh
|
|
|
212
152
|
const handle = mesh.tile_handle;
|
|
213
153
|
this.handle = handle;
|
|
214
154
|
|
|
215
|
-
this.texturizedSceneByTileCoordinates = texturizedSceneByTileCoordinates;
|
|
216
155
|
this.compositor = tileTextureCompositor;
|
|
217
156
|
|
|
218
157
|
// Device-dependent atlas / drape RT sizing, sourced from the compositor so
|
|
@@ -248,113 +187,61 @@ export class TileMesh
|
|
|
248
187
|
|
|
249
188
|
this.tileHandler = tileHandler;
|
|
250
189
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
190
|
+
// Vector layers always resolve through the baked drape path (WebMercator
|
|
191
|
+
// terrain is its degenerate identity case).
|
|
192
|
+
this.vectorDrape = new VectorDrapeResolver({
|
|
193
|
+
handle,
|
|
194
|
+
tileHandler,
|
|
195
|
+
compositor: this.compositor,
|
|
196
|
+
texturizedScenes: texturizedSceneByTileCoordinates,
|
|
197
|
+
textureOptions,
|
|
198
|
+
drapeRtSize: this.drapeRtSize,
|
|
199
|
+
numTexturizedVector: this.numTexturizedVector,
|
|
200
|
+
texturizedSceneIndexFrom: this.texturizedSceneIndexFrom,
|
|
201
|
+
material: () => this.material,
|
|
202
|
+
reportDrapeGpuBytes: () => this.reportDrapeGpuBytesIfChanged(),
|
|
203
|
+
});
|
|
255
204
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (!slot) {
|
|
277
|
-
// Rust returns a state for every vector layer with a rendered tile, including
|
|
278
|
-
// non-draped layers (those render in the MRT scene and never create a texturized
|
|
279
|
-
// scene). Only a layer that actually has a draped scene may consume a bake slot;
|
|
280
|
-
// otherwise non-draped layers would occupy the limited slot budget and crowd out
|
|
281
|
-
// genuinely-draped layers past the cap. A draped layer whose scene isn't ready yet
|
|
282
|
-
// simply gets no slot this pass and is picked up once its scene exists.
|
|
283
|
-
const hasScene =
|
|
284
|
-
this.texturizedSceneByTileCoordinates.findSceneByLayerId(
|
|
285
|
-
state.tile_handle,
|
|
286
|
-
layerId,
|
|
287
|
-
) != null;
|
|
288
|
-
if (!hasScene) continue;
|
|
289
|
-
// Cap the layer fan-out to the GPU slot budget; extra layers are dropped.
|
|
290
|
-
if (byLayer.size >= this.numTexturizedVector) {
|
|
291
|
-
droppedLayers.add(layerId);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
const reproject = state.reproject_terrain_lat;
|
|
295
|
-
slot = {
|
|
296
|
-
layerId,
|
|
297
|
-
sources: [],
|
|
298
|
-
reproject:
|
|
299
|
-
reproject.length >= 2 ? [reproject[0], reproject[1]] : undefined,
|
|
300
|
-
};
|
|
301
|
-
byLayer.set(layerId, slot);
|
|
302
|
-
}
|
|
303
|
-
slot.sources.push({
|
|
304
|
-
tileHandle: state.tile_handle,
|
|
305
|
-
uvOffset: [state.uv_offset[0] ?? 0, state.uv_offset[1] ?? 0],
|
|
306
|
-
uvScale: [state.uv_scale[0] ?? 1, state.uv_scale[1] ?? 1],
|
|
307
|
-
});
|
|
308
|
-
}
|
|
205
|
+
// The globe tiling scheme is fixed for this mesh's lifetime (a runtime
|
|
206
|
+
// scheme flip drains and rebuilds every tile), so the raster drape
|
|
207
|
+
// resolver is chosen once here. `mesh.globe` clones a wasm object;
|
|
208
|
+
// release it.
|
|
209
|
+
const globe = mesh.globe;
|
|
210
|
+
const isGeographicTiling = globe.isGeographicTiling;
|
|
211
|
+
globe.free();
|
|
212
|
+
this.rasterDrape = isGeographicTiling
|
|
213
|
+
? new BakedRasterDrapeResolver({
|
|
214
|
+
handle,
|
|
215
|
+
tileHandler,
|
|
216
|
+
compositor: this.compositor,
|
|
217
|
+
loadedTexs: ctx.loadedTexs,
|
|
218
|
+
textureOptions,
|
|
219
|
+
drapeRtSize: this.drapeRtSize,
|
|
220
|
+
texturizedSceneIndexFrom: this.texturizedSceneIndexFrom,
|
|
221
|
+
material: () => this.material,
|
|
222
|
+
reportDrapeGpuBytes: () => this.reportDrapeGpuBytesIfChanged(),
|
|
223
|
+
})
|
|
224
|
+
: new DirectRasterDrapeResolver();
|
|
309
225
|
|
|
310
|
-
|
|
311
|
-
if (!this.warnedExceededTextures) {
|
|
312
|
-
this.warnedExceededTextures = true;
|
|
313
|
-
console.warn(
|
|
314
|
-
`[TileMesh] Exceeded maximum MVT texture slots: ${byLayer.size + droppedLayers.size} layers requested, ` +
|
|
315
|
-
`but only ${this.numTexturizedVector} slots available. ` +
|
|
316
|
-
`Some MVT layers will not be rendered.`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
this.warnedExceededTextures = false;
|
|
321
|
-
}
|
|
226
|
+
tileMapByHandle.set(handle, this);
|
|
322
227
|
|
|
323
|
-
this.
|
|
324
|
-
this.syncDrapeRenderTargets();
|
|
228
|
+
this.onBeforeRender = this._onBeforeRender;
|
|
325
229
|
}
|
|
326
230
|
|
|
327
231
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
232
|
+
* Report the tile's drape GPU footprint (composite atlas + vector and baked-raster
|
|
233
|
+
* drape render targets) to the memory ledger when it changed. The atlas is acquired
|
|
234
|
+
* in the constructor and held for the tile's lifetime regardless of draping, so it
|
|
235
|
+
* is always part of the report — otherwise a tile whose last draped layer vanished
|
|
236
|
+
* would report 0 and wipe the atlas cost Rust seeds at mesh-attach time. The render
|
|
237
|
+
* targets on top of it scale with terrain subdivision past the layers' maxZoom — a
|
|
238
|
+
* cost per-source-tile accounting cannot see.
|
|
334
239
|
*/
|
|
335
|
-
private
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
format: RGBAFormat,
|
|
341
|
-
});
|
|
342
|
-
// Match the sampler settings setupTextures applies to eager RTs, so the
|
|
343
|
-
// lazily-grown one binds identically when bindVectorSlots picks it up.
|
|
344
|
-
configureTexturizedTexture(rt.texture, this.ctx.textureOptions);
|
|
345
|
-
rts.push(rt);
|
|
346
|
-
}
|
|
347
|
-
while (rts.length > target) {
|
|
348
|
-
rts.pop()?.dispose();
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// The composite atlas is acquired in the constructor and held for the
|
|
352
|
-
// tile's lifetime regardless of draping, so it is always part of the
|
|
353
|
-
// report — otherwise a tile whose last vector layer vanished would report
|
|
354
|
-
// 0 and wipe the atlas cost Rust seeds at mesh-attach time. The render
|
|
355
|
-
// targets on top of it scale with terrain subdivision past the vector
|
|
356
|
-
// maxZoom — a cost per-vector-tile accounting cannot see.
|
|
357
|
-
const bytes = this.atlasBytes + rts.length * drapeRtBytes(this.drapeRtSize);
|
|
240
|
+
private reportDrapeGpuBytesIfChanged() {
|
|
241
|
+
const rtCount =
|
|
242
|
+
this.vectorDrape.liveRenderTargetCount() +
|
|
243
|
+
this.rasterDrape.liveRenderTargetCount();
|
|
244
|
+
const bytes = this.atlasBytes + rtCount * drapeRtBytes(this.drapeRtSize);
|
|
358
245
|
if (bytes === this.reportedDrapeGpuBytes) return;
|
|
359
246
|
// Only the mesh currently registered for this position-stable handle may
|
|
360
247
|
// report. During a terrain mesh replacement the outgoing mesh briefly
|
|
@@ -369,54 +256,14 @@ export class TileMesh
|
|
|
369
256
|
this.tileHandler.reportDrapeGpuBytes(this.handle, bytes);
|
|
370
257
|
}
|
|
371
258
|
|
|
372
|
-
/**
|
|
373
|
-
* Identity of the current slot set + the backing scenes' content. A change means
|
|
374
|
-
* the offscreen scenes must be re-baked and the per-slot material state re-bound.
|
|
375
|
-
*/
|
|
376
|
-
private vectorSignature(): string {
|
|
377
|
-
return this.vectorSlots
|
|
378
|
-
.map((slot) => {
|
|
379
|
-
const sourceSig = slot.sources
|
|
380
|
-
.map((s) => {
|
|
381
|
-
const scene =
|
|
382
|
-
this.texturizedSceneByTileCoordinates.findSceneByLayerId(
|
|
383
|
-
s.tileHandle,
|
|
384
|
-
slot.layerId,
|
|
385
|
-
);
|
|
386
|
-
return `${s.tileHandle}@${s.uvOffset[0]},${s.uvOffset[1]},${s.uvScale[0]},${s.uvScale[1]}#${scene?.revision ?? -1}:${scene?.children.length ?? 0}`;
|
|
387
|
-
})
|
|
388
|
-
.join(",");
|
|
389
|
-
return `${slot.layerId}[${sourceSig}]`;
|
|
390
|
-
})
|
|
391
|
-
.join("|");
|
|
392
|
-
}
|
|
393
|
-
|
|
394
259
|
private _onBeforeRender = () => {
|
|
395
260
|
if (!this.visible) return;
|
|
396
261
|
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const revision = this.tileHandler.vectorRevision();
|
|
403
|
-
if (revision !== this._lastVectorRevision) {
|
|
404
|
-
this._lastVectorRevision = revision;
|
|
405
|
-
this.refreshVectorSlots();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Re-bake the offscreen vector scenes only when the resolved slots or their
|
|
409
|
-
// backing scenes changed; otherwise the existing render targets stay valid.
|
|
410
|
-
const signature = this.vectorSignature();
|
|
411
|
-
if (signature !== this.prevVectorSignature) {
|
|
412
|
-
this.prevVectorSignature = signature;
|
|
413
|
-
this.compositor.renderVectorScenes(
|
|
414
|
-
this.vectorSlots,
|
|
415
|
-
this.texturizedSceneRenderTargets,
|
|
416
|
-
);
|
|
417
|
-
this.bindVectorSlots();
|
|
418
|
-
this.compositor.markDirty(this.handle, "vector-revision");
|
|
419
|
-
}
|
|
262
|
+
// Drape resolvers (see `drapeResolver.ts`): each refreshes its Rust-resolved
|
|
263
|
+
// slots on a revision bump and re-bakes on a slot/content signature change.
|
|
264
|
+
// The raster one is a no-op on WebMercator terrain (direct 1:1 slots).
|
|
265
|
+
this.vectorDrape.update();
|
|
266
|
+
this.rasterDrape.update();
|
|
420
267
|
|
|
421
268
|
// MRT composite pass: bakes N source textures into the per-tile atlas.
|
|
422
269
|
// Skip building the layer snapshot entirely when nothing is dirty.
|
|
@@ -435,93 +282,6 @@ export class TileMesh
|
|
|
435
282
|
);
|
|
436
283
|
};
|
|
437
284
|
|
|
438
|
-
/**
|
|
439
|
-
* Drive each vector slot's main-shader state per frame: texture, per-slot UV
|
|
440
|
-
* transform, visibility and the draped mesh's material attributes. One slot per
|
|
441
|
-
* layer, sourced from the WM vector tile the Rust resolve picked. The resolve
|
|
442
|
-
* walks up to the nearest rendered tile (readiness derived from ECS activation
|
|
443
|
-
* / the Rust resolve, not a JS-side scene_ready flag), so the slot's scene is
|
|
444
|
-
* always bakeable (its own render target was framed by the bake) and the UV is
|
|
445
|
-
* identity — the ancestor LOD fallback lives entirely in Rust. Replaces the old
|
|
446
|
-
* scene-observer `updateTexturizedSceneTextureVisibility`.
|
|
447
|
-
*/
|
|
448
|
-
private bindVectorSlots() {
|
|
449
|
-
const m = this.material;
|
|
450
|
-
if (!m || !m.userData || !m.userData.textures?.value) return;
|
|
451
|
-
const textures = m.userData.textures.value;
|
|
452
|
-
const uvOffsets: Vector2[] = m.userData.layerUvOffset?.value ?? [];
|
|
453
|
-
const uvScales: Vector2[] = m.userData.layerUvScale?.value ?? [];
|
|
454
|
-
|
|
455
|
-
for (let i = 0; i < this.numTexturizedVector; i++) {
|
|
456
|
-
const lastIdx = this.texturizedSceneIndexFrom + i;
|
|
457
|
-
const ownRt = this.texturizedSceneRenderTargets[i];
|
|
458
|
-
// Slots past the live pool (lazily sized to the draped-layer count) have
|
|
459
|
-
// no render target: hide the slot and rebind the shared empty texture so
|
|
460
|
-
// the shader never samples a stale / disposed texture from a layer that
|
|
461
|
-
// is no longer draped here.
|
|
462
|
-
if (!ownRt) {
|
|
463
|
-
m.userData.shows.value[lastIdx] = 0;
|
|
464
|
-
textures[lastIdx] = EMPTY_TEXTURIZED_TEXTURE;
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
textures[lastIdx] = ownRt.texture;
|
|
469
|
-
// The bake framed every source into this RT so it spans the terrain tile's
|
|
470
|
-
// extent: the paste samples it 1:1 in longitude (identity UV here); the
|
|
471
|
-
// latitude axis is reprojected WebMercator→Geographic in the composite
|
|
472
|
-
// shader via the slot's reproject band (see buildCompositeLayers).
|
|
473
|
-
uvOffsets[lastIdx]?.set(0, 0);
|
|
474
|
-
uvScales[lastIdx]?.set(1, 1);
|
|
475
|
-
|
|
476
|
-
const slot = this.vectorSlots[i];
|
|
477
|
-
const mesh = slot ? this.representativeVectorMesh(slot) : undefined;
|
|
478
|
-
m.userData.shows.value[lastIdx] = mesh ? 1 : 0;
|
|
479
|
-
if (mesh) this.copyVectorMeshAttrs(lastIdx, mesh);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* First available draped mesh backing any of a layer's sources. A clamp-to-ground
|
|
485
|
-
* layer's material attributes (water/specular/emissive/…) are uniform across its
|
|
486
|
-
* tiles, so any source's mesh is a faithful representative for the slot.
|
|
487
|
-
*/
|
|
488
|
-
private representativeVectorMesh(slot: VectorSlot): Object3D | undefined {
|
|
489
|
-
for (const source of slot.sources) {
|
|
490
|
-
const scene = this.texturizedSceneByTileCoordinates.findSceneByLayerId(
|
|
491
|
-
source.tileHandle,
|
|
492
|
-
slot.layerId,
|
|
493
|
-
);
|
|
494
|
-
if (scene && !scene.removed && scene.children.length) {
|
|
495
|
-
return scene.children[0];
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return undefined;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/** Copy a draped mesh's material attributes into the main shader's slot `lastIdx`. */
|
|
502
|
-
private copyVectorMeshAttrs(lastIdx: number, mesh: Object3D) {
|
|
503
|
-
const m = this.material;
|
|
504
|
-
if (mesh instanceof PolygonMesh) {
|
|
505
|
-
// Use PolygonMesh getters that expose material enhancer state
|
|
506
|
-
m.userData.reflectivities.value[lastIdx] = mesh.reflectivity;
|
|
507
|
-
m.userData.roughnesses.value[lastIdx] = mesh.roughness;
|
|
508
|
-
m.userData.waters.value[lastIdx] = mesh.water;
|
|
509
|
-
m.userData.waterScaleNormals.value[lastIdx] = mesh.waterScaleNormal;
|
|
510
|
-
m.userData.waterSpeeds.value[lastIdx] = mesh.waterSpeed;
|
|
511
|
-
m.userData.shininesses.value[lastIdx] = mesh.shininess;
|
|
512
|
-
m.userData.specularStrengths.value[lastIdx] = mesh.specularStrength;
|
|
513
|
-
m.userData.applyWaterNormals.value[lastIdx] = mesh.applyWaterNormal;
|
|
514
|
-
m.userData.speculars.value[lastIdx] = mesh.specular;
|
|
515
|
-
m.userData.emissiveIntensities.value[lastIdx] = mesh.emissiveIntensity;
|
|
516
|
-
m.userData.emissiveColors.value[lastIdx].set(mesh.emissiveColor);
|
|
517
|
-
m.userData.effectIdsMasks.value[lastIdx] = mesh.effectIdsMask;
|
|
518
|
-
} else if (mesh instanceof PolylineMesh) {
|
|
519
|
-
m.userData.emissiveIntensities.value[lastIdx] = mesh.emissiveIntensity;
|
|
520
|
-
m.userData.emissiveColors.value[lastIdx].set(mesh.emissiveColor);
|
|
521
|
-
m.userData.effectIdsMasks.value[lastIdx] = mesh.effectIdsMask;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
285
|
/**
|
|
526
286
|
* Feature flags for the **main** TileMesh shader (atlas-sampling side): drives
|
|
527
287
|
* its program cache key and which normal/specular branches compile. Sourced
|
|
@@ -569,13 +329,6 @@ export class TileMesh
|
|
|
569
329
|
const isHillshades: boolean[] = ud.isHillshades?.value ?? [];
|
|
570
330
|
const isElevationHeatmaps: boolean[] = ud.isElevationHeatmaps?.value ?? [];
|
|
571
331
|
const waters: boolean[] = ud.waters?.value ?? [];
|
|
572
|
-
// Per-slot Mercator reprojection flag + the shared terrain latitude range.
|
|
573
|
-
const layerReproject: number[] = ud.layerReproject ?? [];
|
|
574
|
-
const terrainLatRange: number[] = ud.terrainLatRange ?? [];
|
|
575
|
-
const reproject: [number, number] | undefined =
|
|
576
|
-
terrainLatRange.length === 2
|
|
577
|
-
? [terrainLatRange[0], terrainLatRange[1]]
|
|
578
|
-
: undefined;
|
|
579
332
|
const boundary = this.texturizedSceneIndexFrom;
|
|
580
333
|
|
|
581
334
|
const layers: CompositeLayer[] = [];
|
|
@@ -599,39 +352,37 @@ export class TileMesh
|
|
|
599
352
|
uvOffset,
|
|
600
353
|
uvScale,
|
|
601
354
|
});
|
|
602
|
-
} else if (isElevationHeatmaps[absSlot]) {
|
|
603
|
-
layers.push({
|
|
604
|
-
kind: "elevationHeatmap",
|
|
605
|
-
region,
|
|
606
|
-
absSlot,
|
|
607
|
-
texture,
|
|
608
|
-
uvOffset,
|
|
609
|
-
uvScale,
|
|
610
|
-
opacity: opacities[absSlot] ?? 1,
|
|
611
|
-
});
|
|
612
355
|
} else {
|
|
613
|
-
// WebMercator-on-Geographic latitude reprojection
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
:
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
356
|
+
// WebMercator-on-Geographic latitude reprojection, resolved by the
|
|
357
|
+
// slot's drape resolver (always undefined on WM terrain).
|
|
358
|
+
const slotReproject = (
|
|
359
|
+
region === "vector" ? this.vectorDrape : this.rasterDrape
|
|
360
|
+
).slotReproject(absSlot);
|
|
361
|
+
if (isElevationHeatmaps[absSlot]) {
|
|
362
|
+
layers.push({
|
|
363
|
+
kind: "elevationHeatmap",
|
|
364
|
+
region,
|
|
365
|
+
absSlot,
|
|
366
|
+
texture,
|
|
367
|
+
uvOffset,
|
|
368
|
+
uvScale,
|
|
369
|
+
opacity: opacities[absSlot] ?? 1,
|
|
370
|
+
reproject: slotReproject,
|
|
371
|
+
});
|
|
372
|
+
} else {
|
|
373
|
+
layers.push({
|
|
374
|
+
kind: "raster",
|
|
375
|
+
region,
|
|
376
|
+
absSlot,
|
|
377
|
+
texture,
|
|
378
|
+
uvOffset,
|
|
379
|
+
uvScale,
|
|
380
|
+
color: colors[absSlot] ?? new Color(),
|
|
381
|
+
opacity: opacities[absSlot] ?? 1,
|
|
382
|
+
water: waters[absSlot] ?? false,
|
|
383
|
+
reproject: slotReproject,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
635
386
|
}
|
|
636
387
|
}
|
|
637
388
|
|
|
@@ -1148,7 +899,7 @@ ${generateTileCommonInjection(maxTextures)}
|
|
|
1148
899
|
|
|
1149
900
|
// Material / texture rebind → force the vector slots to re-bind their
|
|
1150
901
|
// per-slot uniform state on the next frame, and repaint the composite atlas.
|
|
1151
|
-
this.
|
|
902
|
+
this.vectorDrape.invalidate();
|
|
1152
903
|
this.compositor.markDirty(this.handle, "material");
|
|
1153
904
|
this.compositor.markDirty(this.handle, "texture-binding");
|
|
1154
905
|
}
|
|
@@ -1516,13 +1267,12 @@ ${generateTileCommonInjection(maxTextures)}
|
|
|
1516
1267
|
const textureFragments = m.userData.textureFragments?.value;
|
|
1517
1268
|
const textureFragmentsLen = textureFragments?.length ?? 0;
|
|
1518
1269
|
|
|
1519
|
-
const numTexturizedVector = this.numTexturizedVector;
|
|
1520
|
-
|
|
1521
1270
|
// Raster textures fill slots [0, texturizedSceneIndexFrom); texturized vector
|
|
1522
1271
|
// scenes occupy the rest. Exactly `texturizedSceneIndexFrom` raster layers fit
|
|
1523
|
-
// (indices 0..from-1), so only a strictly larger count overflows.
|
|
1524
|
-
//
|
|
1525
|
-
// this guard is the final safety net
|
|
1272
|
+
// (indices 0..from-1), so only a strictly larger count overflows. Each layer
|
|
1273
|
+
// costs one slot (a baked layer's overlap is mosaicked into a single render
|
|
1274
|
+
// target), so this guard is the final safety net for an oversized layer
|
|
1275
|
+
// count and the extra fragments are dropped below.
|
|
1526
1276
|
if (textureFragmentsLen > this.texturizedSceneIndexFrom) {
|
|
1527
1277
|
console.error(
|
|
1528
1278
|
`Exceeded maximum textures: ${textureFragmentsLen} layers are provided. Maximum the number of textures is ${this.texturizedSceneIndexFrom}.`,
|
|
@@ -1647,27 +1397,11 @@ ${generateTileCommonInjection(maxTextures)}
|
|
|
1647
1397
|
textures[i] = t;
|
|
1648
1398
|
}
|
|
1649
1399
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
const lastIndex = this.texturizedSceneIndexFrom + i;
|
|
1656
|
-
const rt = this.texturizedSceneRenderTargets[i];
|
|
1657
|
-
const texturizedSceneTexture = rt
|
|
1658
|
-
? configureTexturizedTexture(rt.texture, textureOptions)
|
|
1659
|
-
: EMPTY_TEXTURIZED_TEXTURE;
|
|
1660
|
-
|
|
1661
|
-
textures[lastIndex] = texturizedSceneTexture;
|
|
1662
|
-
|
|
1663
|
-
// Per-slot visibility + material attributes are bound by bindVectorSlots once
|
|
1664
|
-
// the Rust-resolved slots are known; default to hidden here.
|
|
1665
|
-
m.userData.shows.value[lastIndex] = 0;
|
|
1666
|
-
m.userData.colors.value[lastIndex] = new Color(0xffffff);
|
|
1667
|
-
m.userData.opacities.value[lastIndex] = 1.0;
|
|
1668
|
-
}
|
|
1669
|
-
// Re-bind vector slots on the next frame now that render targets were rebound.
|
|
1670
|
-
this.prevVectorSignature = "";
|
|
1400
|
+
// Re-derive each drape resolver's slot pairing / bindings from the rebuilt
|
|
1401
|
+
// material (the raster call is a no-op on WebMercator terrain, where every
|
|
1402
|
+
// raster slot carries its own fragment and drapes directly).
|
|
1403
|
+
this.rasterDrape.syncMaterialSlots(textureFragments);
|
|
1404
|
+
this.vectorDrape.syncMaterialSlots(textureFragments);
|
|
1671
1405
|
}
|
|
1672
1406
|
|
|
1673
1407
|
/**
|
|
@@ -1704,13 +1438,13 @@ ${generateTileCommonInjection(maxTextures)}
|
|
|
1704
1438
|
this.material.userData.uPickable.value = 1;
|
|
1705
1439
|
|
|
1706
1440
|
// Force the vector scenes to re-bake for the picking pass.
|
|
1707
|
-
this.
|
|
1441
|
+
this.vectorDrape.invalidate();
|
|
1708
1442
|
this.compositor.markDirty(this.handle, "vector-revision");
|
|
1709
1443
|
}
|
|
1710
1444
|
|
|
1711
1445
|
onAfterPicking(): void {
|
|
1712
1446
|
this.material.userData.uPickable.value = 0;
|
|
1713
|
-
this.
|
|
1447
|
+
this.vectorDrape.invalidate();
|
|
1714
1448
|
this.compositor.markDirty(this.handle, "vector-revision");
|
|
1715
1449
|
}
|
|
1716
1450
|
|
|
@@ -1755,11 +1489,9 @@ ${generateTileCommonInjection(maxTextures)}
|
|
|
1755
1489
|
this.shadowMesh = undefined;
|
|
1756
1490
|
}
|
|
1757
1491
|
|
|
1758
|
-
// Dispose WebGLRenderTargets to free GPU memory
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}
|
|
1762
|
-
this.texturizedSceneRenderTargets.length = 0;
|
|
1492
|
+
// Dispose the drape resolvers' WebGLRenderTargets to free GPU memory
|
|
1493
|
+
this.vectorDrape.dispose();
|
|
1494
|
+
this.rasterDrape.dispose();
|
|
1763
1495
|
// Do NOT report a zero drape cost here. The Rust `TerrainTileGpuCost` /
|
|
1764
1496
|
// `TileCost` live on the mesh entity, whose despawn already subtracts the
|
|
1765
1497
|
// full cost from the ledger via the component `on_replace` hook — so the
|