@navaramap/three 0.0.3 → 0.0.5
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/README.md +7 -7
- package/dist/assets/fontWorker-OwvKiQ8o.js +2 -0
- package/dist/assets/fontWorker-OwvKiQ8o.js.map +1 -0
- package/dist/assets/{index-ywHSGImj.js → index-TKhRxurv.js} +2 -2
- package/dist/assets/index-TKhRxurv.js.map +1 -0
- package/dist/assets/{navara_wasm_font_worker_bg-Du9nb2Pn.wasm → navara_wasm_font_worker_bg-Kk06ArRB.wasm} +0 -0
- package/dist/assets/{navara_wasm_worker_bg-Xadu67fw.wasm → navara_wasm_worker_bg-BZjLKf4i.wasm} +0 -0
- package/dist/index.d.ts +351 -284
- package/dist/index.js +5394 -5470
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +287 -235
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +12 -12
- package/src/ShadowMapViewers.ts +1 -1
- package/src/atmosphere.test.ts +227 -28
- package/src/atmosphere.ts +219 -55
- package/src/constants/assets.ts +63 -13
- package/src/declutter/DeclutterManager.ts +1 -1
- package/src/declutter/types.ts +1 -1
- package/src/evaluations/FeatureEvaluator.test.ts +152 -0
- package/src/evaluations/FeatureEvaluator.ts +73 -2
- package/src/event/features/billboard.ts +1 -0
- package/src/event/features/model.ts +1 -1
- package/src/event/features/point.ts +1 -0
- package/src/event/index.ts +1 -1
- package/src/index.iife.ts +3 -3
- package/src/index.ts +10 -12
- package/src/layers/effect/README.md +1 -1
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +2 -2
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +1 -1
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +1 -1
- package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +1 -1
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +10 -7
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +74 -81
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +19 -86
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.test.ts +77 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +9 -1
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +15 -19
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +0 -13
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +46 -48
- package/src/mesh/constants.ts +22 -0
- package/src/mesh/index.ts +1 -1
- package/src/mesh/model.ts +6 -0
- package/src/mesh/polygon.ts +6 -0
- package/src/mesh/polyline.ts +6 -0
- package/src/mesh/rtcRteHelper.ts +1 -1
- package/src/mesh/sdfText/batchedSdfText.ts +1223 -0
- package/src/mesh/sdfText/glyphBuffers.ts +222 -0
- package/src/mesh/sdfText/glyphSlots.test.ts +151 -0
- package/src/mesh/sdfText/glyphSlots.ts +98 -0
- package/src/mesh/sdfText/index.ts +10 -0
- package/src/mesh/sdfText/labelData.test.ts +178 -0
- package/src/mesh/sdfText/labelData.ts +187 -0
- package/src/mesh/{sdfText.test.ts → sdfText/layout.test.ts} +1 -1
- package/src/mesh/sdfText/layout.ts +306 -0
- package/src/mesh/sprite/instancedSprite.ts +33 -1
- package/src/utils/screen.ts +1 -1
- package/dist/assets/fontWorker-wz61edjR.js +0 -2
- package/dist/assets/fontWorker-wz61edjR.js.map +0 -1
- package/dist/assets/index-ywHSGImj.js.map +0 -1
- package/src/mesh/batchedSdfText.ts +0 -708
- package/src/mesh/sdfText.ts +0 -1189
- /package/dist/{draco_decoder.wasm → assets/draco_decoder-C32yEggz.wasm} +0 -0
- /package/dist/{draco_decoder2.wasm → assets/draco_decoder-Z1_iN-Ht.wasm} +0 -0
package/src/atmosphere.ts
CHANGED
|
@@ -3,17 +3,38 @@ import {
|
|
|
3
3
|
getECIToECEFRotationMatrix,
|
|
4
4
|
getMoonDirectionECEF,
|
|
5
5
|
getSunDirectionECEF,
|
|
6
|
-
|
|
6
|
+
IRRADIANCE_TEXTURE_HEIGHT,
|
|
7
|
+
IRRADIANCE_TEXTURE_WIDTH,
|
|
8
|
+
SCATTERING_TEXTURE_DEPTH,
|
|
9
|
+
SCATTERING_TEXTURE_HEIGHT,
|
|
10
|
+
SCATTERING_TEXTURE_WIDTH,
|
|
11
|
+
TRANSMITTANCE_TEXTURE_HEIGHT,
|
|
12
|
+
TRANSMITTANCE_TEXTURE_WIDTH,
|
|
7
13
|
type AtmosphereOverlay,
|
|
8
14
|
type AtmosphereShadow,
|
|
9
15
|
type AtmosphereShadowLength,
|
|
10
16
|
type PrecomputedTextures,
|
|
11
17
|
} from "@takram/three-atmosphere";
|
|
12
|
-
import {
|
|
13
|
-
|
|
18
|
+
import {
|
|
19
|
+
EXR3DTextureLoader,
|
|
20
|
+
EXRTextureLoader,
|
|
21
|
+
Float16Array,
|
|
22
|
+
isFloatLinearSupported,
|
|
23
|
+
reinterpretType,
|
|
24
|
+
} from "@takram/three-geospatial";
|
|
25
|
+
import {
|
|
26
|
+
FloatType,
|
|
27
|
+
HalfFloatType,
|
|
28
|
+
LinearFilter,
|
|
29
|
+
Matrix4,
|
|
30
|
+
Vector3,
|
|
31
|
+
type DataTextureImageData,
|
|
32
|
+
type Texture,
|
|
33
|
+
type WebGLRenderer,
|
|
34
|
+
} from "three";
|
|
14
35
|
|
|
15
36
|
import { type ThreeViewCamera } from "./camera";
|
|
16
|
-
import {
|
|
37
|
+
import { ATMOSPHERE_TEXTURE_URLS, STBN_URL } from "./constants";
|
|
17
38
|
import {
|
|
18
39
|
dateForSolarTime,
|
|
19
40
|
getSolarTime as solarTimeAt,
|
|
@@ -28,7 +49,7 @@ import {
|
|
|
28
49
|
export type AtmosphereEvents = {
|
|
29
50
|
/** Emitted when the atmosphere needs to trigger a re-render. */
|
|
30
51
|
needsUpdate: () => void;
|
|
31
|
-
/** Emitted
|
|
52
|
+
/** Emitted each time a precomputed atmosphere texture finishes loading. */
|
|
32
53
|
textureLoaded: () => void;
|
|
33
54
|
/** Emitted when the atmosphere is disposed. */
|
|
34
55
|
disposed: () => void;
|
|
@@ -36,12 +57,62 @@ export type AtmosphereEvents = {
|
|
|
36
57
|
sunChanged: (sunDirection: Vector3) => void;
|
|
37
58
|
};
|
|
38
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Names of the precomputed atmosphere textures that can be loaded
|
|
62
|
+
* individually.
|
|
63
|
+
*/
|
|
64
|
+
export type AtmosphereTextureName =
|
|
65
|
+
"transmittance" | "scattering" | "irradiance" | "higherOrderScattering";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Which precomputed textures a consumer needs, e.g.
|
|
69
|
+
* `{ transmittance: true }`. Only `true` is allowed as a value so that every
|
|
70
|
+
* listed texture is guaranteed to be loaded.
|
|
71
|
+
*/
|
|
72
|
+
export type AtmosphereTextureNeeds = Partial<
|
|
73
|
+
Record<AtmosphereTextureName, true>
|
|
74
|
+
>;
|
|
75
|
+
|
|
76
|
+
const ATMOSPHERE_TEXTURE_KEYS = {
|
|
77
|
+
transmittance: "transmittanceTexture",
|
|
78
|
+
scattering: "scatteringTexture",
|
|
79
|
+
irradiance: "irradianceTexture",
|
|
80
|
+
higherOrderScattering: "higherOrderScatteringTexture",
|
|
81
|
+
} as const satisfies Record<AtmosphereTextureName, keyof PrecomputedTextures>;
|
|
82
|
+
|
|
83
|
+
const ATMOSPHERE_TEXTURE_FILES: Record<AtmosphereTextureName, string> = {
|
|
84
|
+
transmittance: "transmittance.exr",
|
|
85
|
+
scattering: "scattering.exr",
|
|
86
|
+
irradiance: "irradiance.exr",
|
|
87
|
+
higherOrderScattering: "higher_order_scattering.exr",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const ALL_ATMOSPHERE_TEXTURE_NAMES = Object.keys(
|
|
91
|
+
ATMOSPHERE_TEXTURE_KEYS,
|
|
92
|
+
) as AtmosphereTextureName[];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The textures guaranteed to be present in an {@link Atmosphere.onTexturesReady}
|
|
96
|
+
* callback for a given needs declaration.
|
|
97
|
+
*/
|
|
98
|
+
export type LoadedAtmosphereTextures<N extends AtmosphereTextureNeeds> =
|
|
99
|
+
Required<
|
|
100
|
+
Pick<
|
|
101
|
+
PrecomputedTextures,
|
|
102
|
+
`${Extract<keyof N, AtmosphereTextureName>}Texture`
|
|
103
|
+
>
|
|
104
|
+
>;
|
|
105
|
+
|
|
39
106
|
/**
|
|
40
107
|
* Configuration options for the {@link Atmosphere} class.
|
|
41
108
|
*/
|
|
42
109
|
export type AtmosphereOptions = {
|
|
43
|
-
/**
|
|
44
|
-
|
|
110
|
+
/**
|
|
111
|
+
* URL of a directory to load precomputed atmosphere textures from. The
|
|
112
|
+
* directory must keep the original filenames (`transmittance.exr` etc.).
|
|
113
|
+
* When omitted, the assets bundled with the package are used.
|
|
114
|
+
*/
|
|
115
|
+
atmosphereAssetsUrl?: string | undefined;
|
|
45
116
|
/** URL to load STBN (Spatio-Temporal Blue Noise) textures from. */
|
|
46
117
|
stbnUrl?: string;
|
|
47
118
|
/** Date used for calculating sun/moon positions. Defaults to current date. */
|
|
@@ -51,8 +122,11 @@ export type AtmosphereOptions = {
|
|
|
51
122
|
/**
|
|
52
123
|
* Default values for {@link AtmosphereOptions}.
|
|
53
124
|
*/
|
|
54
|
-
export const DEFAULT_ATMOSPHERE_OPTIONS: Required<
|
|
55
|
-
atmosphereAssetsUrl
|
|
125
|
+
export const DEFAULT_ATMOSPHERE_OPTIONS: Required<
|
|
126
|
+
Omit<AtmosphereOptions, "atmosphereAssetsUrl">
|
|
127
|
+
> &
|
|
128
|
+
Pick<AtmosphereOptions, "atmosphereAssetsUrl"> = {
|
|
129
|
+
atmosphereAssetsUrl: undefined,
|
|
56
130
|
stbnUrl: STBN_URL,
|
|
57
131
|
date: new Date(),
|
|
58
132
|
};
|
|
@@ -85,10 +159,11 @@ export class Atmosphere extends EventHandler<AtmosphereEvents> {
|
|
|
85
159
|
private rotationMatrix = new Matrix4();
|
|
86
160
|
|
|
87
161
|
/**
|
|
88
|
-
* Precomputed atmosphere textures used for rendering.
|
|
89
|
-
*
|
|
162
|
+
* Precomputed atmosphere textures used for rendering. Each texture is
|
|
163
|
+
* loaded lazily when a consumer declares a need for it via
|
|
164
|
+
* {@link onTexturesReady}, so the object may hold any subset at a time.
|
|
90
165
|
*/
|
|
91
|
-
textures
|
|
166
|
+
textures: Partial<PrecomputedTextures> = {};
|
|
92
167
|
|
|
93
168
|
// Variables that come from Clouds.
|
|
94
169
|
overlay = new Observed<AtmosphereOverlay | null>(null);
|
|
@@ -112,11 +187,11 @@ export class Atmosphere extends EventHandler<AtmosphereEvents> {
|
|
|
112
187
|
private _camera: ThreeViewCamera | undefined;
|
|
113
188
|
|
|
114
189
|
/**
|
|
115
|
-
* In-flight texture loading
|
|
116
|
-
*
|
|
190
|
+
* In-flight per-texture loading promises, shared across consumers so each
|
|
191
|
+
* asset is fetched only once.
|
|
117
192
|
* @private
|
|
118
193
|
*/
|
|
119
|
-
private
|
|
194
|
+
private texturePromises = new Map<AtmosphereTextureName, Promise<void>>();
|
|
120
195
|
|
|
121
196
|
/**
|
|
122
197
|
* Creates a new Atmosphere instance.
|
|
@@ -148,67 +223,156 @@ export class Atmosphere extends EventHandler<AtmosphereEvents> {
|
|
|
148
223
|
};
|
|
149
224
|
|
|
150
225
|
/**
|
|
151
|
-
* Loads precomputed atmosphere textures asynchronously.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
226
|
+
* Loads all precomputed atmosphere textures asynchronously.
|
|
227
|
+
*
|
|
228
|
+
* Each texture loads automatically once a consumer declares a need for it
|
|
229
|
+
* via {@link onTexturesReady}; call this manually only to prefetch every
|
|
230
|
+
* texture ahead of time.
|
|
231
|
+
*
|
|
232
|
+
* @returns A promise that resolves when all textures are loaded.
|
|
154
233
|
*/
|
|
155
|
-
// TODO: Add an option to disable loading textures.
|
|
156
234
|
async initTextures() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.setType(this.renderer)
|
|
161
|
-
.loadAsync(
|
|
162
|
-
this.options.atmosphereAssetsUrl ??
|
|
163
|
-
DEFAULT_ATMOSPHERE_OPTIONS.atmosphereAssetsUrl,
|
|
164
|
-
)
|
|
165
|
-
.then((textures) => {
|
|
166
|
-
this.textures = textures;
|
|
167
|
-
this.emit("textureLoaded");
|
|
168
|
-
})
|
|
169
|
-
.finally(() => {
|
|
170
|
-
// Allow retrying after a failed load instead of caching the rejection.
|
|
171
|
-
this.texturesPromise = undefined;
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
await this.texturesPromise;
|
|
235
|
+
await Promise.all(
|
|
236
|
+
ALL_ATMOSPHERE_TEXTURE_NAMES.map((name) => this.loadTexture(name)),
|
|
237
|
+
);
|
|
175
238
|
}
|
|
176
239
|
|
|
177
240
|
/**
|
|
241
|
+
* Loads a single precomputed texture, sharing in-flight loads and caching
|
|
242
|
+
* the result. A failed load is forgotten so the next request retries it.
|
|
178
243
|
* @private
|
|
179
244
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
245
|
+
private loadTexture(name: AtmosphereTextureName): Promise<void> {
|
|
246
|
+
let promise = this.texturePromises.get(name);
|
|
247
|
+
if (promise == null) {
|
|
248
|
+
const assetsUrl = this.options.atmosphereAssetsUrl;
|
|
249
|
+
const file = ATMOSPHERE_TEXTURE_FILES[name];
|
|
250
|
+
const url =
|
|
251
|
+
assetsUrl == null
|
|
252
|
+
? ATMOSPHERE_TEXTURE_URLS[file]
|
|
253
|
+
: `${assetsUrl.replace(/\/+$/, "")}/${file}`;
|
|
254
|
+
promise = this.fetchTexture(name, url)
|
|
255
|
+
.then((texture) => {
|
|
256
|
+
const type = isFloatLinearSupported(this.renderer)
|
|
257
|
+
? FloatType
|
|
258
|
+
: HalfFloatType;
|
|
259
|
+
texture.type = type;
|
|
260
|
+
// EXR data is parsed to Uint16Array, which must be converted to
|
|
261
|
+
// Float32Array when FloatType is used.
|
|
262
|
+
if (type === FloatType) {
|
|
263
|
+
reinterpretType<DataTextureImageData>(texture.image);
|
|
264
|
+
if (texture.image.data != null) {
|
|
265
|
+
texture.image.data = new Float32Array(
|
|
266
|
+
new Float16Array(texture.image.data.buffer),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
texture.minFilter = LinearFilter;
|
|
271
|
+
texture.magFilter = LinearFilter;
|
|
272
|
+
Object.assign(this.textures, {
|
|
273
|
+
[ATMOSPHERE_TEXTURE_KEYS[name]]: texture,
|
|
274
|
+
});
|
|
275
|
+
this.emit("textureLoaded");
|
|
276
|
+
})
|
|
277
|
+
.catch((e: unknown) => {
|
|
278
|
+
// Allow retrying after a failed load instead of caching the
|
|
279
|
+
// rejection.
|
|
280
|
+
this.texturePromises.delete(name);
|
|
281
|
+
throw e;
|
|
282
|
+
});
|
|
283
|
+
this.texturePromises.set(name, promise);
|
|
284
|
+
}
|
|
285
|
+
return promise;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private fetchTexture(
|
|
289
|
+
name: AtmosphereTextureName,
|
|
290
|
+
url: string,
|
|
291
|
+
): Promise<Texture> {
|
|
292
|
+
switch (name) {
|
|
293
|
+
case "transmittance":
|
|
294
|
+
return new EXRTextureLoader({
|
|
295
|
+
width: TRANSMITTANCE_TEXTURE_WIDTH,
|
|
296
|
+
height: TRANSMITTANCE_TEXTURE_HEIGHT,
|
|
297
|
+
}).loadAsync(url);
|
|
298
|
+
case "irradiance":
|
|
299
|
+
return new EXRTextureLoader({
|
|
300
|
+
width: IRRADIANCE_TEXTURE_WIDTH,
|
|
301
|
+
height: IRRADIANCE_TEXTURE_HEIGHT,
|
|
302
|
+
}).loadAsync(url);
|
|
303
|
+
case "scattering":
|
|
304
|
+
case "higherOrderScattering":
|
|
305
|
+
return new EXR3DTextureLoader({
|
|
306
|
+
width: SCATTERING_TEXTURE_WIDTH,
|
|
307
|
+
height: SCATTERING_TEXTURE_HEIGHT,
|
|
308
|
+
depth: SCATTERING_TEXTURE_DEPTH,
|
|
309
|
+
}).loadAsync(url);
|
|
310
|
+
}
|
|
182
311
|
}
|
|
183
312
|
|
|
184
313
|
/**
|
|
185
314
|
* @private
|
|
186
315
|
*/
|
|
187
316
|
_dispose() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.textures.scatteringTexture.dispose();
|
|
191
|
-
this.textures.transmittanceTexture.dispose();
|
|
192
|
-
this.textures.singleMieScatteringTexture?.dispose();
|
|
193
|
-
this.textures.higherOrderScatteringTexture?.dispose();
|
|
194
|
-
this.textures = undefined;
|
|
317
|
+
for (const texture of Object.values(this.textures)) {
|
|
318
|
+
texture?.dispose();
|
|
195
319
|
}
|
|
320
|
+
this.textures = {};
|
|
321
|
+
this.texturePromises.clear();
|
|
196
322
|
this.emit("disposed");
|
|
197
323
|
}
|
|
198
324
|
|
|
199
325
|
/**
|
|
200
|
-
* Invokes the callback
|
|
201
|
-
*
|
|
326
|
+
* Invokes the callback once every needed precomputed texture is loaded —
|
|
327
|
+
* immediately if they already are.
|
|
328
|
+
*
|
|
329
|
+
* Registering also starts loading the needed textures if they aren't loaded
|
|
330
|
+
* yet, so a texture is only fetched once something that needs it exists.
|
|
331
|
+
*
|
|
332
|
+
* @param callback - Receives the textures; the ones listed in `needs` are
|
|
333
|
+
* guaranteed to be present.
|
|
334
|
+
* @param needs - The textures the caller needs, e.g.
|
|
335
|
+
* `{ transmittance: true }`. Omit to load all of them.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* atmosphere.onTexturesReady(
|
|
339
|
+
* (t) => light.setTransmittanceTexture(t.transmittanceTexture),
|
|
340
|
+
* { transmittance: true },
|
|
341
|
+
* );
|
|
202
342
|
*/
|
|
203
|
-
onTexturesReady
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
343
|
+
onTexturesReady<
|
|
344
|
+
N extends AtmosphereTextureNeeds = Record<AtmosphereTextureName, true>,
|
|
345
|
+
>(
|
|
346
|
+
callback: (textures: LoadedAtmosphereTextures<N>) => void,
|
|
347
|
+
needs?: N,
|
|
348
|
+
): void {
|
|
349
|
+
const names =
|
|
350
|
+
needs == null
|
|
351
|
+
? ALL_ATMOSPHERE_TEXTURE_NAMES
|
|
352
|
+
: ALL_ATMOSPHERE_TEXTURE_NAMES.filter((name) => needs[name]);
|
|
353
|
+
for (const name of names) {
|
|
354
|
+
this.loadTexture(name).catch((e: unknown) => {
|
|
355
|
+
console.error("Failed to load atmosphere textures:", e);
|
|
210
356
|
});
|
|
211
357
|
}
|
|
358
|
+
|
|
359
|
+
const isReady = () =>
|
|
360
|
+
names.every(
|
|
361
|
+
(name) => this.textures[ATMOSPHERE_TEXTURE_KEYS[name]] != null,
|
|
362
|
+
);
|
|
363
|
+
const invoke = () => {
|
|
364
|
+
callback(this.textures as LoadedAtmosphereTextures<N>);
|
|
365
|
+
};
|
|
366
|
+
if (isReady()) {
|
|
367
|
+
invoke();
|
|
368
|
+
} else {
|
|
369
|
+
const listener = () => {
|
|
370
|
+
if (!isReady()) return;
|
|
371
|
+
this.off("textureLoaded", listener);
|
|
372
|
+
invoke();
|
|
373
|
+
};
|
|
374
|
+
this.on("textureLoaded", listener);
|
|
375
|
+
}
|
|
212
376
|
}
|
|
213
377
|
|
|
214
378
|
/**
|
package/src/constants/assets.ts
CHANGED
|
@@ -1,30 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Every bundled asset must be referenced through its own static
|
|
2
|
+
// `new URL("<literal>", import.meta.url)` call so that consumers' bundlers
|
|
3
|
+
// (Vite/Rollup/webpack) can detect the file and emit it into their build
|
|
4
|
+
// output. Runtime-concatenated paths are invisible to bundlers and 404 in
|
|
5
|
+
// production builds (#746). The `?no-inline` query opts each file out of
|
|
6
|
+
// Vite's lib-mode base64 inlining so our own dist emits it as a fetchable
|
|
7
|
+
// asset instead.
|
|
8
|
+
|
|
9
|
+
export const ATMOSPHERE_TRANSMITTANCE_URL = new URL(
|
|
10
|
+
"../../assets/atmosphere/transmittance.exr?no-inline",
|
|
11
|
+
import.meta.url,
|
|
12
|
+
).toString();
|
|
13
|
+
|
|
14
|
+
export const ATMOSPHERE_SCATTERING_URL = new URL(
|
|
15
|
+
"../../assets/atmosphere/scattering.exr?no-inline",
|
|
16
|
+
import.meta.url,
|
|
17
|
+
).toString();
|
|
18
|
+
|
|
19
|
+
export const ATMOSPHERE_IRRADIANCE_URL = new URL(
|
|
20
|
+
"../../assets/atmosphere/irradiance.exr?no-inline",
|
|
21
|
+
import.meta.url,
|
|
22
|
+
).toString();
|
|
23
|
+
|
|
24
|
+
export const ATMOSPHERE_HIGHER_ORDER_SCATTERING_URL = new URL(
|
|
25
|
+
"../../assets/atmosphere/higher_order_scattering.exr?no-inline",
|
|
3
26
|
import.meta.url,
|
|
4
27
|
).toString();
|
|
5
28
|
|
|
6
|
-
export const
|
|
7
|
-
|
|
29
|
+
export const ATMOSPHERE_SINGLE_MIE_SCATTERING_URL = new URL(
|
|
30
|
+
"../../assets/atmosphere/single_mie_scattering.exr?no-inline",
|
|
8
31
|
import.meta.url,
|
|
9
32
|
).toString();
|
|
10
33
|
|
|
11
|
-
export const
|
|
12
|
-
|
|
34
|
+
export const STARS_ASSETS_URL = new URL(
|
|
35
|
+
"../../assets/atmosphere/stars.bin?no-inline",
|
|
13
36
|
import.meta.url,
|
|
14
37
|
).toString();
|
|
15
38
|
|
|
16
|
-
export const
|
|
17
|
-
|
|
39
|
+
export const CLOUD_LOCAL_WEATHER_URL = new URL(
|
|
40
|
+
"../../assets/cloud/local_weather.png?no-inline",
|
|
18
41
|
import.meta.url,
|
|
19
42
|
).toString();
|
|
20
43
|
|
|
21
|
-
export const
|
|
22
|
-
|
|
44
|
+
export const CLOUD_SHAPE_URL = new URL(
|
|
45
|
+
"../../assets/cloud/shape.bin?no-inline",
|
|
23
46
|
import.meta.url,
|
|
24
47
|
).toString();
|
|
25
48
|
|
|
26
|
-
export const
|
|
49
|
+
export const CLOUD_SHAPE_DETAIL_URL = new URL(
|
|
50
|
+
"../../assets/cloud/shape_detail.bin?no-inline",
|
|
51
|
+
import.meta.url,
|
|
52
|
+
).toString();
|
|
53
|
+
|
|
54
|
+
export const CLOUD_TURBULENCE_URL = new URL(
|
|
55
|
+
"../../assets/cloud/turbulence.png?no-inline",
|
|
56
|
+
import.meta.url,
|
|
57
|
+
).toString();
|
|
27
58
|
|
|
28
|
-
export const STBN_URL =
|
|
59
|
+
export const STBN_URL = new URL(
|
|
60
|
+
"../../assets/noise/stbn.bin?no-inline",
|
|
61
|
+
import.meta.url,
|
|
62
|
+
).toString();
|
|
63
|
+
|
|
64
|
+
export const WATER_NORMAL_URL = new URL(
|
|
65
|
+
"../../assets/water/waternormals.jpg?no-inline",
|
|
66
|
+
import.meta.url,
|
|
67
|
+
).toString();
|
|
29
68
|
|
|
30
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Filenames requested by `PrecomputedTexturesLoader` mapped to their
|
|
71
|
+
* statically-resolved URLs. Used to remap directory-relative requests when
|
|
72
|
+
* loading the bundled default atmosphere assets.
|
|
73
|
+
*/
|
|
74
|
+
export const ATMOSPHERE_TEXTURE_URLS: Record<string, string> = {
|
|
75
|
+
"transmittance.exr": ATMOSPHERE_TRANSMITTANCE_URL,
|
|
76
|
+
"scattering.exr": ATMOSPHERE_SCATTERING_URL,
|
|
77
|
+
"irradiance.exr": ATMOSPHERE_IRRADIANCE_URL,
|
|
78
|
+
"higher_order_scattering.exr": ATMOSPHERE_HIGHER_ORDER_SCATTERING_URL,
|
|
79
|
+
"single_mie_scattering.exr": ATMOSPHERE_SINGLE_MIE_SCATTERING_URL,
|
|
80
|
+
};
|
|
@@ -203,7 +203,7 @@ export class DeclutterManager {
|
|
|
203
203
|
camera.near,
|
|
204
204
|
widthPx,
|
|
205
205
|
heightPx,
|
|
206
|
-
// Plain math instead of @
|
|
206
|
+
// Plain math instead of @navaramap/three-api's degreeToRadian — that one
|
|
207
207
|
// is a WASM call whose result we'd only marshal straight back in.
|
|
208
208
|
(camera.fov * Math.PI) / 180.0,
|
|
209
209
|
DeclutterManager.PADDING_PX,
|
package/src/declutter/types.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type DeclutterCandidate = {
|
|
|
31
31
|
isShown: boolean;
|
|
32
32
|
/** Participant that owns this candidate; receives the placement result. */
|
|
33
33
|
owner: DeclutterParticipant;
|
|
34
|
-
/** Participant-defined id (
|
|
34
|
+
/** Participant-defined id (label slot for text batches, instance index for
|
|
35
35
|
* sprites) passed back to `applyDeclutter`. */
|
|
36
36
|
handle: number;
|
|
37
37
|
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Object3D } from "three";
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
|
|
4
|
+
import type { FeatureHandler } from "../event/context";
|
|
5
|
+
|
|
6
|
+
import { FeatureEvaluator, type GeometryType } from "./FeatureEvaluator";
|
|
7
|
+
|
|
8
|
+
// Mock worker module to avoid os.cpus() error in test environment
|
|
9
|
+
vi.mock("@navaramap/worker", () => ({}));
|
|
10
|
+
|
|
11
|
+
describe("FeatureEvaluator", () => {
|
|
12
|
+
let mockHandler: FeatureHandler;
|
|
13
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Create mock FeatureHandler with minimal implementation
|
|
17
|
+
mockHandler = {
|
|
18
|
+
readAllBatchedProperties: vi.fn((_, callback) => {
|
|
19
|
+
// Simulate a single batch with empty properties object
|
|
20
|
+
callback(0, 0, {});
|
|
21
|
+
}),
|
|
22
|
+
readFilteredBatchedProperties: vi.fn((_, __, callback) => {
|
|
23
|
+
// Simulate a single batch with empty properties object (not array)
|
|
24
|
+
// Production code expects Record<string, unknown>, not an array
|
|
25
|
+
callback(0, 0, {});
|
|
26
|
+
}),
|
|
27
|
+
} as unknown as FeatureHandler;
|
|
28
|
+
|
|
29
|
+
// Spy on console.warn to check for warnings
|
|
30
|
+
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
consoleWarnSpy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("determineGeometryType", () => {
|
|
38
|
+
it("should pass through valid geometry type literals", () => {
|
|
39
|
+
// Test representative types: billboard and text (critical for symbol layers)
|
|
40
|
+
const mockMesh = new Object3D();
|
|
41
|
+
Object.defineProperty(mockMesh, "geometryType", {
|
|
42
|
+
get: () => "billboard",
|
|
43
|
+
configurable: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const evaluator = new FeatureEvaluator(
|
|
47
|
+
mockHandler,
|
|
48
|
+
"test-layer",
|
|
49
|
+
0n,
|
|
50
|
+
mockMesh,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let capturedGeometryType: GeometryType | undefined;
|
|
54
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
55
|
+
capturedGeometryType = gt;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(capturedGeometryType).toBe("billboard");
|
|
59
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should return undefined and warn once for invalid values", () => {
|
|
63
|
+
const mockMesh = new Object3D();
|
|
64
|
+
Object.defineProperty(mockMesh, "geometryType", {
|
|
65
|
+
get: () => "invalid-type",
|
|
66
|
+
configurable: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const evaluator = new FeatureEvaluator(
|
|
70
|
+
mockHandler,
|
|
71
|
+
"test-layer",
|
|
72
|
+
0n,
|
|
73
|
+
mockMesh,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// First evaluation - should warn
|
|
77
|
+
let capturedGeometryType: GeometryType | undefined;
|
|
78
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
79
|
+
capturedGeometryType = gt;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(capturedGeometryType).toBeUndefined();
|
|
83
|
+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
85
|
+
expect.stringContaining(
|
|
86
|
+
'Invalid geometry type returned from geometryType getter: "invalid-type"',
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Second evaluation - should NOT warn again (same object)
|
|
91
|
+
consoleWarnSpy.mockClear();
|
|
92
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
93
|
+
capturedGeometryType = gt;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(capturedGeometryType).toBeUndefined();
|
|
97
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled(); // No additional warnings
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should return undefined without warning when geometryType is not present", () => {
|
|
101
|
+
const mockMesh = new Object3D();
|
|
102
|
+
// No geometryType getter
|
|
103
|
+
|
|
104
|
+
const evaluator = new FeatureEvaluator(
|
|
105
|
+
mockHandler,
|
|
106
|
+
"test-layer",
|
|
107
|
+
0n,
|
|
108
|
+
mockMesh,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
let capturedGeometryType: GeometryType | undefined;
|
|
112
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
113
|
+
capturedGeometryType = gt;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(capturedGeometryType).toBeUndefined();
|
|
117
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should read geometryType dynamically on each evaluation (critical for symbol layers)", () => {
|
|
121
|
+
const mockMesh = new Object3D();
|
|
122
|
+
let currentType: GeometryType = "billboard";
|
|
123
|
+
Object.defineProperty(mockMesh, "geometryType", {
|
|
124
|
+
get: () => currentType,
|
|
125
|
+
configurable: true,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const evaluator = new FeatureEvaluator(
|
|
129
|
+
mockHandler,
|
|
130
|
+
"test-layer",
|
|
131
|
+
0n,
|
|
132
|
+
mockMesh,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// First read - should be "billboard"
|
|
136
|
+
let capturedGeometryType: GeometryType | undefined;
|
|
137
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
138
|
+
capturedGeometryType = gt;
|
|
139
|
+
});
|
|
140
|
+
expect(capturedGeometryType).toBe("billboard");
|
|
141
|
+
|
|
142
|
+
// Change the geometry type (simulates symbol layer switching from billboard to text)
|
|
143
|
+
currentType = "text";
|
|
144
|
+
|
|
145
|
+
// Second read - should be "text" (not cached)
|
|
146
|
+
evaluator.readFeatureProperties(({ meshGeomType: gt }) => {
|
|
147
|
+
capturedGeometryType = gt;
|
|
148
|
+
});
|
|
149
|
+
expect(capturedGeometryType).toBe("text");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|