@navaramap/three 0.0.4 → 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.
Files changed (51) hide show
  1. package/dist/assets/fontWorker-OwvKiQ8o.js +2 -0
  2. package/dist/assets/fontWorker-OwvKiQ8o.js.map +1 -0
  3. package/dist/assets/{index-ywHSGImj.js → index-TKhRxurv.js} +2 -2
  4. package/dist/assets/index-TKhRxurv.js.map +1 -0
  5. package/dist/assets/{navara_wasm_font_worker_bg-Du9nb2Pn.wasm → navara_wasm_font_worker_bg-Kk06ArRB.wasm} +0 -0
  6. package/dist/assets/{navara_wasm_worker_bg-Xadu67fw.wasm → navara_wasm_worker_bg-BZjLKf4i.wasm} +0 -0
  7. package/dist/index.d.ts +349 -282
  8. package/dist/index.js +5394 -5470
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.umd.cjs +287 -235
  11. package/dist/index.umd.cjs.map +1 -1
  12. package/package.json +12 -12
  13. package/src/atmosphere.test.ts +227 -28
  14. package/src/atmosphere.ts +219 -55
  15. package/src/constants/assets.ts +63 -13
  16. package/src/declutter/types.ts +1 -1
  17. package/src/evaluations/FeatureEvaluator.test.ts +152 -0
  18. package/src/evaluations/FeatureEvaluator.ts +73 -2
  19. package/src/event/features/billboard.ts +1 -0
  20. package/src/event/features/point.ts +1 -0
  21. package/src/index.ts +7 -9
  22. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +10 -7
  23. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +74 -81
  24. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +18 -85
  25. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.test.ts +77 -0
  26. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +9 -1
  27. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +15 -19
  28. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +0 -13
  29. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +46 -48
  30. package/src/mesh/constants.ts +22 -0
  31. package/src/mesh/index.ts +1 -1
  32. package/src/mesh/model.ts +6 -0
  33. package/src/mesh/polygon.ts +6 -0
  34. package/src/mesh/polyline.ts +6 -0
  35. package/src/mesh/sdfText/batchedSdfText.ts +1223 -0
  36. package/src/mesh/sdfText/glyphBuffers.ts +222 -0
  37. package/src/mesh/sdfText/glyphSlots.test.ts +151 -0
  38. package/src/mesh/sdfText/glyphSlots.ts +98 -0
  39. package/src/mesh/sdfText/index.ts +10 -0
  40. package/src/mesh/sdfText/labelData.test.ts +178 -0
  41. package/src/mesh/sdfText/labelData.ts +187 -0
  42. package/src/mesh/{sdfText.test.ts → sdfText/layout.test.ts} +1 -1
  43. package/src/mesh/sdfText/layout.ts +306 -0
  44. package/src/mesh/sprite/instancedSprite.ts +32 -0
  45. package/dist/assets/fontWorker-wz61edjR.js +0 -2
  46. package/dist/assets/fontWorker-wz61edjR.js.map +0 -1
  47. package/dist/assets/index-ywHSGImj.js.map +0 -1
  48. package/src/mesh/batchedSdfText.ts +0 -708
  49. package/src/mesh/sdfText.ts +0 -1189
  50. /package/dist/{draco_decoder.wasm → assets/draco_decoder-C32yEggz.wasm} +0 -0
  51. /package/dist/{draco_decoder2.wasm → assets/draco_decoder-Z1_iN-Ht.wasm} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@navaramap/three",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.5",
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.0.4",
34
- "@navaramap/engine": "0.0.4",
35
- "@navaramap/engine-api": "0.0.4",
36
- "@navaramap/three-api": "0.0.4",
37
- "@navaramap/three-csm": "0.0.4",
38
- "@navaramap/font": "0.0.4",
39
- "@navaramap/worker": "0.0.4"
33
+ "@navaramap/core": "0.0.5",
34
+ "@navaramap/engine": "0.0.5",
35
+ "@navaramap/engine-api": "0.0.5",
36
+ "@navaramap/font": "0.0.5",
37
+ "@navaramap/three-api": "0.0.5",
38
+ "@navaramap/three-csm": "0.0.5",
39
+ "@navaramap/worker": "0.0.5"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@playwright/test": "1.61.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.0.4",
75
- "@navaramap/three-plugins": "0.0.4",
76
- "@navaramap/three-default-plugin": "0.0.4",
77
- "@navaramap/three-react": "0.0.4"
74
+ "@navaramap/three-default-descs": "0.0.5",
75
+ "@navaramap/three-plugins": "0.0.5",
76
+ "@navaramap/three-react": "0.0.5",
77
+ "@navaramap/three-default-plugin": "0.0.5"
78
78
  },
79
79
  "scripts": {
80
80
  "start": "vite",
@@ -1,76 +1,275 @@
1
- import type { WebGLRenderer } from "three";
1
+ import { type WebGLRenderer } from "three";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
- import { Atmosphere } from "./atmosphere";
4
+ import { Atmosphere, type AtmosphereOptions } from "./atmosphere";
5
5
 
6
6
  const { loadAsyncMock } = vi.hoisted(() => ({
7
7
  loadAsyncMock: vi.fn(),
8
8
  }));
9
9
 
10
10
  vi.mock("@takram/three-atmosphere", () => ({
11
- PrecomputedTexturesLoader: class {
12
- setType() {
13
- return this;
14
- }
15
- loadAsync = loadAsyncMock;
16
- },
17
11
  getSunDirectionECEF: vi.fn(),
18
12
  getMoonDirectionECEF: vi.fn(),
19
13
  getECIToECEFRotationMatrix: vi.fn(),
14
+ TRANSMITTANCE_TEXTURE_WIDTH: 256,
15
+ TRANSMITTANCE_TEXTURE_HEIGHT: 64,
16
+ IRRADIANCE_TEXTURE_WIDTH: 64,
17
+ IRRADIANCE_TEXTURE_HEIGHT: 16,
18
+ SCATTERING_TEXTURE_WIDTH: 256,
19
+ SCATTERING_TEXTURE_HEIGHT: 128,
20
+ SCATTERING_TEXTURE_DEPTH: 32,
20
21
  }));
21
22
 
22
- const createAtmosphere = () =>
23
- new Atmosphere({} as unknown as WebGLRenderer, { date: new Date(0) });
23
+ vi.mock("@takram/three-geospatial", () => {
24
+ class MockLoader {
25
+ loadAsync = loadAsyncMock;
26
+ }
27
+ return {
28
+ EXRTextureLoader: MockLoader,
29
+ EXR3DTextureLoader: MockLoader,
30
+ isFloatLinearSupported: () => false,
31
+ reinterpretType: () => {},
32
+ };
33
+ });
34
+
35
+ const fakeTexture = () => ({ image: {}, dispose: vi.fn() });
36
+
37
+ /** Resolves every load immediately with a fake texture, recording the URL. */
38
+ const autoResolveLoads = () => {
39
+ loadAsyncMock.mockImplementation(() => Promise.resolve(fakeTexture()));
40
+ };
41
+
42
+ const requestedFiles = () =>
43
+ loadAsyncMock.mock.calls.map((call) => {
44
+ const url = call[0] as string;
45
+ return url.slice(url.lastIndexOf("/") + 1).replace(/\?.*$/, "");
46
+ });
47
+
48
+ const createAtmosphere = (options?: Partial<AtmosphereOptions>) =>
49
+ new Atmosphere({} as unknown as WebGLRenderer, {
50
+ date: new Date(0),
51
+ ...options,
52
+ });
24
53
 
25
- const fakeTextures = () => ({}) as never;
54
+ const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
26
55
 
27
56
  describe("Atmosphere.initTextures", () => {
28
57
  beforeEach(() => {
29
58
  loadAsyncMock.mockReset();
30
59
  });
31
60
 
32
- it("shares a single load across concurrent calls", async () => {
33
- loadAsyncMock.mockResolvedValue(fakeTextures());
61
+ it("loads all four textures", async () => {
62
+ autoResolveLoads();
63
+ const atmosphere = createAtmosphere();
64
+
65
+ await atmosphere.initTextures();
66
+
67
+ expect(requestedFiles().sort()).toEqual([
68
+ "higher_order_scattering.exr",
69
+ "irradiance.exr",
70
+ "scattering.exr",
71
+ "transmittance.exr",
72
+ ]);
73
+ expect(atmosphere.textures.transmittanceTexture).toBeDefined();
74
+ expect(atmosphere.textures.scatteringTexture).toBeDefined();
75
+ expect(atmosphere.textures.irradianceTexture).toBeDefined();
76
+ expect(atmosphere.textures.higherOrderScatteringTexture).toBeDefined();
77
+ });
78
+
79
+ it("shares in-flight loads across concurrent calls", async () => {
80
+ autoResolveLoads();
34
81
  const atmosphere = createAtmosphere();
35
82
 
36
83
  await Promise.all([atmosphere.initTextures(), atmosphere.initTextures()]);
37
84
 
38
- expect(loadAsyncMock).toHaveBeenCalledTimes(1);
39
- expect(atmosphere.textures).toBeDefined();
85
+ expect(loadAsyncMock).toHaveBeenCalledTimes(4);
40
86
  });
41
87
 
42
- it("returns immediately once textures are loaded", async () => {
43
- loadAsyncMock.mockResolvedValue(fakeTextures());
88
+ it("does not reload once textures are loaded", async () => {
89
+ autoResolveLoads();
44
90
  const atmosphere = createAtmosphere();
45
91
 
46
92
  await atmosphere.initTextures();
47
93
  await atmosphere.initTextures();
48
94
 
49
- expect(loadAsyncMock).toHaveBeenCalledTimes(1);
95
+ expect(loadAsyncMock).toHaveBeenCalledTimes(4);
50
96
  });
51
97
 
52
- it("emits textureLoaded exactly once", async () => {
53
- loadAsyncMock.mockResolvedValue(fakeTextures());
98
+ it("emits textureLoaded once per texture", async () => {
99
+ autoResolveLoads();
54
100
  const atmosphere = createAtmosphere();
55
101
  const onLoaded = vi.fn();
56
102
  atmosphere.on("textureLoaded", onLoaded);
57
103
 
58
- await Promise.all([atmosphere.initTextures(), atmosphere.initTextures()]);
104
+ await atmosphere.initTextures();
59
105
 
60
- expect(onLoaded).toHaveBeenCalledTimes(1);
106
+ expect(onLoaded).toHaveBeenCalledTimes(4);
61
107
  });
62
108
 
63
- it("allows retrying after a failed load", async () => {
64
- loadAsyncMock.mockRejectedValueOnce(new Error("network error"));
65
- loadAsyncMock.mockResolvedValueOnce(fakeTextures());
109
+ it("retries only the texture that failed", async () => {
110
+ let failTransmittance = true;
111
+ loadAsyncMock.mockImplementation((url: string) => {
112
+ if (failTransmittance && url.includes("transmittance.exr")) {
113
+ return Promise.reject(new Error("network error"));
114
+ }
115
+ return Promise.resolve(fakeTexture());
116
+ });
66
117
  const atmosphere = createAtmosphere();
67
118
 
68
119
  await expect(atmosphere.initTextures()).rejects.toThrow("network error");
69
- expect(atmosphere.textures).toBeUndefined();
120
+ expect(atmosphere.textures.transmittanceTexture).toBeUndefined();
121
+ expect(atmosphere.textures.scatteringTexture).toBeDefined();
70
122
 
123
+ failTransmittance = false;
71
124
  await atmosphere.initTextures();
72
125
 
73
- expect(loadAsyncMock).toHaveBeenCalledTimes(2);
74
- expect(atmosphere.textures).toBeDefined();
126
+ expect(atmosphere.textures.transmittanceTexture).toBeDefined();
127
+ expect(
128
+ requestedFiles().filter((f) => f === "transmittance.exr"),
129
+ ).toHaveLength(2);
130
+ expect(requestedFiles().filter((f) => f === "scattering.exr")).toHaveLength(
131
+ 1,
132
+ );
133
+ });
134
+
135
+ it("loads from atmosphereAssetsUrl when provided, normalizing slashes", async () => {
136
+ autoResolveLoads();
137
+ const atmosphere = createAtmosphere({
138
+ atmosphereAssetsUrl: "https://example.com/assets/",
139
+ });
140
+
141
+ await atmosphere.initTextures();
142
+
143
+ for (const [url] of loadAsyncMock.mock.calls as [string][]) {
144
+ expect(url).toMatch(/^https:\/\/example\.com\/assets\/[a-z_]+\.exr$/);
145
+ }
146
+ });
147
+ });
148
+
149
+ describe("Atmosphere.onTexturesReady", () => {
150
+ beforeEach(() => {
151
+ loadAsyncMock.mockReset();
152
+ });
153
+
154
+ it("does not load anything until a consumer registers", () => {
155
+ autoResolveLoads();
156
+ createAtmosphere();
157
+
158
+ expect(loadAsyncMock).not.toHaveBeenCalled();
159
+ });
160
+
161
+ it("loads only the needed textures", async () => {
162
+ autoResolveLoads();
163
+ const atmosphere = createAtmosphere();
164
+ const callback = vi.fn();
165
+
166
+ atmosphere.onTexturesReady(callback, { transmittance: true });
167
+ await flush();
168
+
169
+ expect(requestedFiles()).toEqual(["transmittance.exr"]);
170
+ expect(callback).toHaveBeenCalledTimes(1);
171
+ expect(callback.mock.calls[0][0].transmittanceTexture).toBeDefined();
172
+ });
173
+
174
+ it("shares a fetch between consumers needing the same texture", async () => {
175
+ autoResolveLoads();
176
+ const atmosphere = createAtmosphere();
177
+ const first = vi.fn();
178
+ const second = vi.fn();
179
+
180
+ atmosphere.onTexturesReady(first, { transmittance: true });
181
+ atmosphere.onTexturesReady(second, { transmittance: true });
182
+ await flush();
183
+
184
+ expect(loadAsyncMock).toHaveBeenCalledTimes(1);
185
+ expect(first).toHaveBeenCalledTimes(1);
186
+ expect(second).toHaveBeenCalledTimes(1);
187
+ });
188
+
189
+ it("fetches only the missing textures for wider needs", async () => {
190
+ autoResolveLoads();
191
+ const atmosphere = createAtmosphere();
192
+
193
+ atmosphere.onTexturesReady(vi.fn(), { transmittance: true });
194
+ await flush();
195
+ const callback = vi.fn();
196
+ atmosphere.onTexturesReady(callback, {
197
+ transmittance: true,
198
+ scattering: true,
199
+ });
200
+ await flush();
201
+
202
+ expect(requestedFiles()).toEqual(["transmittance.exr", "scattering.exr"]);
203
+ expect(callback).toHaveBeenCalledTimes(1);
204
+ });
205
+
206
+ it("loads all textures and fires once when needs are omitted", async () => {
207
+ autoResolveLoads();
208
+ const atmosphere = createAtmosphere();
209
+ const callback = vi.fn();
210
+
211
+ atmosphere.onTexturesReady(callback);
212
+ await flush();
213
+
214
+ expect(loadAsyncMock).toHaveBeenCalledTimes(4);
215
+ expect(callback).toHaveBeenCalledTimes(1);
216
+ });
217
+
218
+ it("waits until every needed texture is ready", async () => {
219
+ const pending = new Map<string, (t: unknown) => void>();
220
+ loadAsyncMock.mockImplementation(
221
+ (url: string) =>
222
+ new Promise((resolve) => {
223
+ pending.set(
224
+ url.slice(url.lastIndexOf("/") + 1).replace(/\?.*$/, ""),
225
+ resolve,
226
+ );
227
+ }),
228
+ );
229
+ const atmosphere = createAtmosphere();
230
+ const callback = vi.fn();
231
+
232
+ atmosphere.onTexturesReady(callback, {
233
+ transmittance: true,
234
+ scattering: true,
235
+ });
236
+ pending.get("transmittance.exr")?.(fakeTexture());
237
+ await flush();
238
+ expect(callback).not.toHaveBeenCalled();
239
+
240
+ pending.get("scattering.exr")?.(fakeTexture());
241
+ await flush();
242
+ expect(callback).toHaveBeenCalledTimes(1);
243
+ });
244
+
245
+ it("invokes the callback immediately once loaded", async () => {
246
+ autoResolveLoads();
247
+ const atmosphere = createAtmosphere();
248
+ await atmosphere.initTextures();
249
+ const callback = vi.fn();
250
+
251
+ atmosphere.onTexturesReady(callback, { irradiance: true });
252
+
253
+ expect(callback).toHaveBeenCalledTimes(1);
254
+ expect(loadAsyncMock).toHaveBeenCalledTimes(4);
255
+ });
256
+
257
+ it("logs instead of rejecting when a lazy load fails", async () => {
258
+ const error = new Error("network error");
259
+ loadAsyncMock.mockRejectedValue(error);
260
+ const consoleError = vi
261
+ .spyOn(console, "error")
262
+ .mockImplementation(() => {});
263
+ const atmosphere = createAtmosphere();
264
+
265
+ atmosphere.onTexturesReady(vi.fn(), { transmittance: true });
266
+
267
+ await vi.waitFor(() => {
268
+ expect(consoleError).toHaveBeenCalledWith(
269
+ "Failed to load atmosphere textures:",
270
+ error,
271
+ );
272
+ });
273
+ consoleError.mockRestore();
75
274
  });
76
275
  });