@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.
Files changed (64) hide show
  1. package/README.md +7 -7
  2. package/dist/assets/fontWorker-OwvKiQ8o.js +2 -0
  3. package/dist/assets/fontWorker-OwvKiQ8o.js.map +1 -0
  4. package/dist/assets/{index-ywHSGImj.js → index-TKhRxurv.js} +2 -2
  5. package/dist/assets/index-TKhRxurv.js.map +1 -0
  6. package/dist/assets/{navara_wasm_font_worker_bg-Du9nb2Pn.wasm → navara_wasm_font_worker_bg-Kk06ArRB.wasm} +0 -0
  7. package/dist/assets/{navara_wasm_worker_bg-Xadu67fw.wasm → navara_wasm_worker_bg-BZjLKf4i.wasm} +0 -0
  8. package/dist/index.d.ts +351 -284
  9. package/dist/index.js +5394 -5470
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.umd.cjs +287 -235
  12. package/dist/index.umd.cjs.map +1 -1
  13. package/package.json +12 -12
  14. package/src/ShadowMapViewers.ts +1 -1
  15. package/src/atmosphere.test.ts +227 -28
  16. package/src/atmosphere.ts +219 -55
  17. package/src/constants/assets.ts +63 -13
  18. package/src/declutter/DeclutterManager.ts +1 -1
  19. package/src/declutter/types.ts +1 -1
  20. package/src/evaluations/FeatureEvaluator.test.ts +152 -0
  21. package/src/evaluations/FeatureEvaluator.ts +73 -2
  22. package/src/event/features/billboard.ts +1 -0
  23. package/src/event/features/model.ts +1 -1
  24. package/src/event/features/point.ts +1 -0
  25. package/src/event/index.ts +1 -1
  26. package/src/index.iife.ts +3 -3
  27. package/src/index.ts +10 -12
  28. package/src/layers/effect/README.md +1 -1
  29. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +2 -2
  30. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +1 -1
  31. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +1 -1
  32. package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +1 -1
  33. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +10 -7
  34. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +74 -81
  35. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +19 -86
  36. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.test.ts +77 -0
  37. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +9 -1
  38. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +15 -19
  39. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +0 -13
  40. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +46 -48
  41. package/src/mesh/constants.ts +22 -0
  42. package/src/mesh/index.ts +1 -1
  43. package/src/mesh/model.ts +6 -0
  44. package/src/mesh/polygon.ts +6 -0
  45. package/src/mesh/polyline.ts +6 -0
  46. package/src/mesh/rtcRteHelper.ts +1 -1
  47. package/src/mesh/sdfText/batchedSdfText.ts +1223 -0
  48. package/src/mesh/sdfText/glyphBuffers.ts +222 -0
  49. package/src/mesh/sdfText/glyphSlots.test.ts +151 -0
  50. package/src/mesh/sdfText/glyphSlots.ts +98 -0
  51. package/src/mesh/sdfText/index.ts +10 -0
  52. package/src/mesh/sdfText/labelData.test.ts +178 -0
  53. package/src/mesh/sdfText/labelData.ts +187 -0
  54. package/src/mesh/{sdfText.test.ts → sdfText/layout.test.ts} +1 -1
  55. package/src/mesh/sdfText/layout.ts +306 -0
  56. package/src/mesh/sprite/instancedSprite.ts +33 -1
  57. package/src/utils/screen.ts +1 -1
  58. package/dist/assets/fontWorker-wz61edjR.js +0 -2
  59. package/dist/assets/fontWorker-wz61edjR.js.map +0 -1
  60. package/dist/assets/index-ywHSGImj.js.map +0 -1
  61. package/src/mesh/batchedSdfText.ts +0 -708
  62. package/src/mesh/sdfText.ts +0 -1189
  63. /package/dist/{draco_decoder.wasm → assets/draco_decoder-C32yEggz.wasm} +0 -0
  64. /package/dist/{draco_decoder2.wasm → assets/draco_decoder-Z1_iN-Ht.wasm} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { encodePosition } from "@navaramap/engine-api";
2
- import { RTE_ONE_UNIFORM } from "@navaramap/three_api";
2
+ import { RTE_ONE_UNIFORM } from "@navaramap/three-api";
3
3
  import { Color, DataTexture, type Matrix4, Vector2, Vector3 } from "three";
4
4
 
5
5
  import type { UniformValue } from "../../../types";
@@ -14,22 +14,17 @@ import type {
14
14
  * Create mutation functions for the sdfText base enhancer.
15
15
  * Refs are created internally and captured via closure.
16
16
  *
17
- * @param useRTE - Whether RTE positioning is enabled
17
+ * Every ref here is batch-wide — one material draws all of a tile-layer's
18
+ * labels. Per-label values are read by the shader from `uLabelData`.
19
+ *
18
20
  * @param rtcCenter - The RTC center position [x, y, z]
19
- * @param batchId - Batch ID for picking
20
21
  */
21
22
  export const createBaseMutates = (
22
- useRTE: boolean,
23
23
  rtcCenter?: [number, number, number],
24
- batchId?: number,
25
24
  ): SdfTextBaseMutates => {
26
25
  const refs: SdfTextBaseRefs = {
27
- uColor: { value: new Color(1, 1, 1) },
28
- uOpacity: { value: 1.0 },
29
- uFontSize: { value: 16.0 },
30
26
  uCenter: { value: new Vector2(0.5, 0.0) },
31
27
  uSizeInMeters: { value: true },
32
- uAddHeight: { value: 0.0 },
33
28
  uOffsetDepth: { value: true },
34
29
  uSdfThreshold: { value: 0.5 },
35
30
  uOutlineWidth: { value: 0.0 },
@@ -42,10 +37,6 @@ export const createBaseMutates = (
42
37
  uFovRad: { value: 1.0 },
43
38
  uScreenHeightPx: { value: 1080.0 },
44
39
  uFarPlane: { value: 1000.0 },
45
- uTextWidth: { value: 0.0 },
46
- uTextHeight: { value: 0.0 },
47
- uBgYBounds: { value: new Vector2(0.0, 1.0) },
48
- uDeclutterHide: { value: 0.0 },
49
40
  uRTCCenter: {
50
41
  value: new Vector3(
51
42
  rtcCenter?.[0] ?? 0,
@@ -56,7 +47,6 @@ export const createBaseMutates = (
56
47
  uRTCCenterView: { value: new Vector3(0, 0, 0) },
57
48
  uEyeRTELow: { value: new Vector3(0, 0, 0) },
58
49
  uEyeRTEHigh: { value: new Vector3(0, 0, 0) },
59
- nvr_uBatchId: { value: batchId ?? 0 },
60
50
  nvr_uPickable: { value: 0.0 },
61
51
  uAtlas: { value: null },
62
52
  uColorAtlas: { value: null },
@@ -64,24 +54,16 @@ export const createBaseMutates = (
64
54
  // is bound; UVs are unused until glyph quads exist anyway.
65
55
  uSdfAtlasSize: { value: new Vector2(1, 1) },
66
56
  uColorAtlasSize: { value: new Vector2(1, 1) },
57
+ uLabelData: { value: null },
58
+ // Non-zero so the shader's `i % width` / `i / width` never divide by zero
59
+ // before the mesh binds its label texture.
60
+ uLabelTexSize: { value: new Vector2(1, 1) },
67
61
  };
68
62
 
69
- // Conditional position uniforms based on RTE mode
70
- if (useRTE) {
71
- refs.uRTEPositionLOW = { value: new Vector3(0, 0, 0) };
72
- refs.uRTEPositionHIGH = { value: new Vector3(0, 0, 0) };
73
- } else {
74
- refs.uRTCPosition = { value: new Vector3(0, 0, 0) };
75
- }
76
-
77
63
  return {
78
64
  update: (state: SdfTextBaseState) => {
79
- refs.uColor.value.set(state.color);
80
- refs.uOpacity.value = state.opacity;
81
- refs.uFontSize.value = state.fontSize;
82
65
  refs.uCenter.value.set(state.center[0], state.center[1]);
83
66
  refs.uSizeInMeters.value = state.sizeInMeters;
84
- refs.uAddHeight.value = state.addHeight;
85
67
  refs.uOffsetDepth.value = state.offsetDepth;
86
68
  refs.uOutlineWidth.value = state.outlineWidth;
87
69
  refs.uOutlineColor.value.set(state.outlineColor);
@@ -94,12 +76,8 @@ export const createBaseMutates = (
94
76
  },
95
77
 
96
78
  updateUniforms: (uniforms) => {
97
- uniforms.uColor = refs.uColor;
98
- uniforms.uOpacity = refs.uOpacity;
99
- uniforms.uFontSize = refs.uFontSize;
100
79
  uniforms.uCenter = refs.uCenter;
101
80
  uniforms.uSizeInMeters = refs.uSizeInMeters;
102
- uniforms.uAddHeight = refs.uAddHeight;
103
81
  uniforms.uOffsetDepth = refs.uOffsetDepth;
104
82
  uniforms.uSdfThreshold = refs.uSdfThreshold;
105
83
  uniforms.uOutlineWidth = refs.uOutlineWidth;
@@ -112,31 +90,18 @@ export const createBaseMutates = (
112
90
  uniforms.uFovRad = refs.uFovRad;
113
91
  uniforms.uScreenHeightPx = refs.uScreenHeightPx;
114
92
  uniforms.uFarPlane = refs.uFarPlane;
115
- uniforms.uTextWidth = refs.uTextWidth;
116
- uniforms.uTextHeight = refs.uTextHeight;
117
- uniforms.uBgYBounds = refs.uBgYBounds;
118
- uniforms.uDeclutterHide = refs.uDeclutterHide;
119
93
  uniforms.uRTCCenter = refs.uRTCCenter;
120
94
  uniforms.uRTCCenterView = refs.uRTCCenterView;
121
95
  uniforms.uEyeRTELow = refs.uEyeRTELow;
122
96
  uniforms.uEyeRTEHigh = refs.uEyeRTEHigh;
123
97
  uniforms.u_rteOne = RTE_ONE_UNIFORM;
124
- uniforms.nvr_uBatchId = refs.nvr_uBatchId;
125
98
  uniforms.nvr_uPickable = refs.nvr_uPickable;
126
99
  uniforms.uAtlas = refs.uAtlas;
127
100
  uniforms.uColorAtlas = refs.uColorAtlas;
128
101
  uniforms.uSdfAtlasSize = refs.uSdfAtlasSize;
129
102
  uniforms.uColorAtlasSize = refs.uColorAtlasSize;
130
-
131
- if (refs.uRTEPositionLOW) {
132
- uniforms.uRTEPositionLOW = refs.uRTEPositionLOW;
133
- }
134
- if (refs.uRTEPositionHIGH) {
135
- uniforms.uRTEPositionHIGH = refs.uRTEPositionHIGH;
136
- }
137
- if (refs.uRTCPosition) {
138
- uniforms.uRTCPosition = refs.uRTCPosition;
139
- }
103
+ uniforms.uLabelData = refs.uLabelData;
104
+ uniforms.uLabelTexSize = refs.uLabelTexSize;
140
105
  },
141
106
 
142
107
  updatePerFrame: (
@@ -172,17 +137,6 @@ export const createBaseMutates = (
172
137
  }
173
138
  },
174
139
 
175
- updateTextDimensions: (
176
- textWidth: number,
177
- textHeight: number,
178
- bgMinY: number,
179
- bgMaxY: number,
180
- ) => {
181
- refs.uTextWidth.value = textWidth;
182
- refs.uTextHeight.value = textHeight;
183
- refs.uBgYBounds.value.set(bgMinY, bgMaxY);
184
- },
185
-
186
140
  setAtlasTexture: (texture: UniformValue<DataTexture | null>) => {
187
141
  refs.uAtlas.value = texture.value;
188
142
  },
@@ -202,39 +156,18 @@ export const createBaseMutates = (
202
156
  }
203
157
  },
204
158
 
205
- setPosition: (
206
- position: Float32Array | { high: Float32Array; low: Float32Array },
207
- posUseRTE: boolean,
208
- posRtcCenter?: [number, number, number],
159
+ setLabelDataTexture: (
160
+ texture: DataTexture | null,
161
+ width: number,
162
+ height: number,
209
163
  ) => {
210
- if (posUseRTE) {
211
- const p = position as { high: Float32Array; low: Float32Array };
212
- refs.uRTEPositionLOW?.value.set(p.low[0], p.low[1], p.low[2] ?? 0.0);
213
- refs.uRTEPositionHIGH?.value.set(
214
- p.high[0],
215
- p.high[1],
216
- p.high[2] ?? 0.0,
217
- );
218
- } else {
219
- const p = position as Float32Array;
220
- refs.uRTCPosition?.value.set(p[0], p[1], p[2] ?? 0.0);
221
-
222
- if (posRtcCenter) {
223
- refs.uRTCCenter.value.set(
224
- posRtcCenter[0],
225
- posRtcCenter[1],
226
- posRtcCenter[2],
227
- );
228
- }
229
- }
230
- },
231
-
232
- setBatchId: (id: number) => {
233
- refs.nvr_uBatchId.value = id;
164
+ refs.uLabelData.value = texture;
165
+ // Guard against a zero size reaching the shader's modulo/divide.
166
+ refs.uLabelTexSize.value.set(Math.max(1, width), Math.max(1, height));
234
167
  },
235
168
 
236
- setDeclutterHide: (value: number) => {
237
- refs.uDeclutterHide.value = value;
169
+ setRtcCenter: (center: [number, number, number]) => {
170
+ refs.uRTCCenter.value.set(center[0], center[1], center[2]);
238
171
  },
239
172
  };
240
173
  };
@@ -0,0 +1,77 @@
1
+ import sdfTextFragmentShader from "@shaders/glsl/sdfText.frag.glsl";
2
+ import sdfTextVertexShader from "@shaders/glsl/sdfText.vert.glsl";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { LABEL_ROWS, LabelRow } from "./types";
6
+
7
+ import { createSdfTextBaseEnhancer, type SupportedMaterial } from ".";
8
+
9
+ describe("sdfTextBaseEnhancer shader", () => {
10
+ const createMockMaterial = () =>
11
+ ({
12
+ type: "ShaderMaterial",
13
+ userData: {},
14
+ }) as unknown as SupportedMaterial;
15
+
16
+ const createMockShader = () => ({
17
+ vertexShader: "",
18
+ fragmentShader: "",
19
+ uniforms: {} as Record<string, { value: unknown }>,
20
+ defines: {} as Record<string, unknown>,
21
+ });
22
+
23
+ const transform = (
24
+ props: Parameters<ReturnType<typeof createSdfTextBaseEnhancer>["mount"]>[0],
25
+ ) => {
26
+ const enhancer = createSdfTextBaseEnhancer(createMockMaterial());
27
+ enhancer.mount(props);
28
+ const shader = createMockShader();
29
+ enhancer.transformShader(shader as never);
30
+ return shader;
31
+ };
32
+
33
+ it("should set correct vertex and fragment shaders", () => {
34
+ const shader = transform({});
35
+ expect(shader.vertexShader).toBe(sdfTextVertexShader);
36
+ expect(shader.fragmentShader).toBe(sdfTextFragmentShader);
37
+ });
38
+
39
+ it("should set USE_RTE define only when useRTE=true", () => {
40
+ expect(transform({ useRTE: true }).defines.USE_RTE).toBe(1);
41
+ expect(transform({ useRTE: false }).defines.USE_RTE).toBeUndefined();
42
+ });
43
+
44
+ it("should set USE_MSDF define only when useMsdf=true", () => {
45
+ expect(transform({ useMsdf: true }).defines.USE_MSDF).toBe(1);
46
+ expect(transform({ useMsdf: false }).defines.USE_MSDF).toBeUndefined();
47
+ });
48
+
49
+ // The shader indexes the label data texture as `slot * LABEL_ROWS + row`.
50
+ // If the define and the CPU-side row table disagree, every label reads
51
+ // another label's state — hence injecting it rather than hard-coding it.
52
+ describe("label data texture contract", () => {
53
+ it("injects LABEL_ROWS as a define", () => {
54
+ expect(transform({}).defines.LABEL_ROWS).toBe(LABEL_ROWS);
55
+ });
56
+
57
+ it("keeps LABEL_ROWS in step with the row table", () => {
58
+ const rows = Object.values(LabelRow);
59
+ expect(LABEL_ROWS).toBe(rows.length);
60
+ // Rows must be a dense 0..n-1 range for the stride math to hold.
61
+ expect([...rows].sort((a, b) => a - b)).toEqual(rows.map((_, i) => i));
62
+ });
63
+
64
+ it("binds the label texture uniforms", () => {
65
+ const shader = transform({});
66
+ expect(shader.uniforms.uLabelData).toBeDefined();
67
+ expect(shader.uniforms.uLabelTexSize).toBeDefined();
68
+ });
69
+ });
70
+
71
+ it("should assign batch-wide uniforms to shader.uniforms", () => {
72
+ const shader = transform({ outlineOpacity: 0.25 });
73
+ expect(shader.uniforms.uCenter).toBeDefined();
74
+ expect(shader.uniforms.uOutlineOpacity?.value).toBe(0.25);
75
+ expect(shader.uniforms.nvr_uPickable).toBeDefined();
76
+ });
77
+ });
@@ -2,7 +2,11 @@ import sdfTextFragmentShader from "@shaders/glsl/sdfText.frag.glsl";
2
2
  import sdfTextVertexShader from "@shaders/glsl/sdfText.vert.glsl";
3
3
  import type { WebGLProgramParametersWithUniforms } from "three";
4
4
 
5
- import type { SdfTextBaseMutates, SdfTextBaseState } from "./types";
5
+ import {
6
+ LABEL_ROWS,
7
+ type SdfTextBaseMutates,
8
+ type SdfTextBaseState,
9
+ } from "./types";
6
10
 
7
11
  /**
8
12
  * Transform shader with sdfText base modifications.
@@ -24,6 +28,10 @@ export const transformShader = (
24
28
  // Set defines based on state
25
29
  shader.defines ??= {};
26
30
 
31
+ // Stride of the per-label data texture. Injected rather than hard-coded in
32
+ // GLSL so the shader's row addressing always matches `LabelRow`.
33
+ shader.defines.LABEL_ROWS = LABEL_ROWS;
34
+
27
35
  if (state.useRTE) {
28
36
  shader.defines.USE_RTE = 1;
29
37
  }
@@ -6,8 +6,8 @@ import { sdfRadiusFor } from "./types";
6
6
  describe("sdfTextBaseEnhancer / state", () => {
7
7
  describe("updateState", () => {
8
8
  it("initializes state from props with defaults", () => {
9
- const state = updateState({ fontSize: 24 }, DEFAULT_BASE_STATE);
10
- expect(state.fontSize).toBe(24);
9
+ const state = updateState({ offsetDepth: false }, DEFAULT_BASE_STATE);
10
+ expect(state.offsetDepth).toBe(false);
11
11
  expect(state.center).toEqual([0.5, 0.0]);
12
12
  expect(state.sizeInMeters).toBe(true);
13
13
  });
@@ -18,16 +18,6 @@ describe("sdfTextBaseEnhancer / state", () => {
18
18
  expect(state.useRTE).toBe(true);
19
19
  });
20
20
 
21
- it("updates color from hex to RGB tuple", () => {
22
- const state = updateState({ color: 0xff0000 }, DEFAULT_BASE_STATE);
23
- expect(state.color.getHex()).toBe(0xff0000);
24
- });
25
-
26
- it("updates fontSize", () => {
27
- const state = updateState({ fontSize: 32 }, DEFAULT_BASE_STATE);
28
- expect(state.fontSize).toBe(32);
29
- });
30
-
31
21
  it("updates center", () => {
32
22
  const state = updateState({ center: [0.0, 0.5] }, DEFAULT_BASE_STATE);
33
23
  expect(state.center).toEqual([0.0, 0.5]);
@@ -38,11 +28,6 @@ describe("sdfTextBaseEnhancer / state", () => {
38
28
  expect(state.sizeInMeters).toBe(true);
39
29
  });
40
30
 
41
- it("updates addHeight", () => {
42
- const state = updateState({ addHeight: 100 }, DEFAULT_BASE_STATE);
43
- expect(state.addHeight).toBe(100);
44
- });
45
-
46
31
  it("updates offsetDepth", () => {
47
32
  const state = updateState({ offsetDepth: false }, DEFAULT_BASE_STATE);
48
33
  expect(state.offsetDepth).toBe(false);
@@ -99,6 +84,17 @@ describe("sdfTextBaseEnhancer / state", () => {
99
84
  expect(state.pickable).toBe(true);
100
85
  });
101
86
 
87
+ // Colour, opacity, font size and height moved into the per-label data
88
+ // texture when text became a single batched draw call, so they must not
89
+ // reappear as batch-wide state.
90
+ it("carries no per-label style", () => {
91
+ const state = updateState({}, DEFAULT_BASE_STATE);
92
+ expect(state).not.toHaveProperty("color");
93
+ expect(state).not.toHaveProperty("opacity");
94
+ expect(state).not.toHaveProperty("fontSize");
95
+ expect(state).not.toHaveProperty("addHeight");
96
+ });
97
+
102
98
  it("updates depthTest", () => {
103
99
  const state = updateState({ depthTest: false }, DEFAULT_BASE_STATE);
104
100
  expect(state.depthTest).toBe(false);
@@ -107,11 +103,11 @@ describe("sdfTextBaseEnhancer / state", () => {
107
103
  it("falls back to currentState for missing props", () => {
108
104
  const currentState = {
109
105
  ...DEFAULT_BASE_STATE,
110
- fontSize: 42,
106
+ outlineOpacity: 0.25,
111
107
  center: [1, 2] as [number, number],
112
108
  };
113
109
  const state = updateState({}, currentState);
114
- expect(state.fontSize).toBe(42);
110
+ expect(state.outlineOpacity).toBe(0.25);
115
111
  expect(state.center).toEqual([1, 2]);
116
112
  });
117
113
  });
@@ -15,12 +15,8 @@ export const DEFAULT_BASE_PROPS: Required<
15
15
  > = {
16
16
  useRTE: false,
17
17
  useMsdf: false,
18
- color: 0xffffff,
19
- opacity: 1.0,
20
- fontSize: 16.0,
21
18
  center: [0.5, 0.0],
22
19
  sizeInMeters: true,
23
- addHeight: 0.0,
24
20
  offsetDepth: true,
25
21
  outlineWidth: 0,
26
22
  outlineColor: 0x000000,
@@ -38,12 +34,8 @@ export const DEFAULT_BASE_PROPS: Required<
38
34
  export const DEFAULT_BASE_STATE: SdfTextBaseState = {
39
35
  useRTE: DEFAULT_BASE_PROPS.useRTE,
40
36
  useMsdf: DEFAULT_BASE_PROPS.useMsdf,
41
- color: hexToColor(DEFAULT_BASE_PROPS.color),
42
- opacity: DEFAULT_BASE_PROPS.opacity,
43
- fontSize: DEFAULT_BASE_PROPS.fontSize,
44
37
  center: DEFAULT_BASE_PROPS.center,
45
38
  sizeInMeters: DEFAULT_BASE_PROPS.sizeInMeters,
46
- addHeight: DEFAULT_BASE_PROPS.addHeight,
47
39
  offsetDepth: DEFAULT_BASE_PROPS.offsetDepth,
48
40
  outlineWidth:
49
41
  DEFAULT_BASE_PROPS.outlineWidth / sdfRadiusFor(DEFAULT_BASE_PROPS.useMsdf),
@@ -72,13 +64,8 @@ export const updateState = (
72
64
  useRTE: currentState.useRTE,
73
65
  useMsdf: currentState.useMsdf,
74
66
  // Mutable
75
- color:
76
- props.color !== undefined ? hexToColor(props.color) : currentState.color,
77
- opacity: props.opacity ?? currentState.opacity,
78
- fontSize: props.fontSize ?? currentState.fontSize,
79
67
  center: props.center ?? currentState.center,
80
68
  sizeInMeters: props.sizeInMeters ?? currentState.sizeInMeters,
81
- addHeight: props.addHeight ?? currentState.addHeight,
82
69
  offsetDepth: props.offsetDepth ?? currentState.offsetDepth,
83
70
  outlineWidth:
84
71
  props.outlineWidth !== undefined
@@ -10,8 +10,39 @@ import type { Mutates } from "../../MaterialEnhancer";
10
10
  * `MSDF_RANGE_PX` (8 px). See [`atlasRangePx`] in `@navaramap/font`. */
11
11
  export const sdfRadiusFor = (useMsdf: boolean): number => atlasRangePx(useMsdf);
12
12
 
13
+ /**
14
+ * Layout of the per-label data texture (`uLabelData`).
15
+ *
16
+ * This is a contract between three places: the vertex shader's `nvr_readLabel`
17
+ * (`shaders/glsl/sdfText.vert.glsl`), which receives {@link LABEL_ROWS} as a
18
+ * define so it can't drift; the CPU-side writer
19
+ * (`mesh/sdfText/labelData.ts`); and the mesh that fills the rows. It lives
20
+ * here because the enhancer owns the shader.
21
+ */
22
+ export const LabelRow = {
23
+ /** xyz = anchor high (RTE) or RTC-relative anchor, w = fontSize. */
24
+ POSITION_HIGH_SIZE: 0,
25
+ /** xyz = anchor low (RTE only), w = addHeight. */
26
+ POSITION_LOW_HEIGHT: 1,
27
+ /** rgb = colour, a = opacity. */
28
+ COLOR_OPACITY: 2,
29
+ /** x = textWidth, y = textHeight, z = bgMinY, w = bgMaxY (all in ems). */
30
+ BOX: 3,
31
+ /** x = declutterHide, y = batchId, z = show, w = reserved. */
32
+ STATE: 4,
33
+ } as const;
34
+
35
+ /** Texels per label. Derived from {@link LabelRow} so the two can't disagree. */
36
+ export const LABEL_ROWS = Object.keys(LabelRow).length;
37
+
13
38
  /**
14
39
  * Props for the sdfText base enhancer.
40
+ *
41
+ * Everything here is **batch-wide**: one material draws every label in a
42
+ * tile-layer. Per-label values (position, colour, opacity, font size, height,
43
+ * block metrics, batch id, declutter fade) live in the label data texture
44
+ * owned by `BatchedSdfTextMesh`, not in this state — see
45
+ * `web/navara_three/src/mesh/sdfText/labelData.ts`.
15
46
  */
16
47
  export type SdfTextBaseProps = {
17
48
  // Immutable after mount
@@ -21,12 +52,8 @@ export type SdfTextBaseProps = {
21
52
  useMsdf?: boolean;
22
53
 
23
54
  // Mutable state
24
- color?: number; // hex color
25
- opacity?: number;
26
- fontSize?: number;
27
55
  center?: [number, number];
28
56
  sizeInMeters?: boolean;
29
- addHeight?: number;
30
57
  offsetDepth?: boolean;
31
58
  outlineWidth?: number; // raw width, converted in state via sdfRadiusFor(useMsdf)
32
59
  outlineColor?: number; // hex
@@ -55,12 +82,8 @@ export type SdfTextBaseState = Readonly<{
55
82
  useMsdf: boolean;
56
83
 
57
84
  // Mutable
58
- color: Color;
59
- opacity: number;
60
- fontSize: number;
61
85
  center: [number, number];
62
86
  sizeInMeters: boolean;
63
- addHeight: number;
64
87
  offsetDepth: boolean;
65
88
  outlineWidth: number; // pre-converted: raw / sdfRadiusFor(useMsdf)
66
89
  outlineColor: Color;
@@ -82,12 +105,8 @@ export type SdfTextBaseState = Readonly<{
82
105
  * Internal type - not exposed externally.
83
106
  */
84
107
  export type SdfTextBaseRefs = {
85
- uColor: UniformValue<Color>;
86
- uOpacity: UniformValue<number>;
87
- uFontSize: UniformValue<number>;
88
108
  uCenter: UniformValue<Vector2>;
89
109
  uSizeInMeters: UniformValue<boolean>;
90
- uAddHeight: UniformValue<number>;
91
110
  uOffsetDepth: UniformValue<boolean>;
92
111
  uSdfThreshold: UniformValue<number>;
93
112
  uOutlineWidth: UniformValue<number>;
@@ -100,20 +119,12 @@ export type SdfTextBaseRefs = {
100
119
  uFovRad: UniformValue<number>;
101
120
  uScreenHeightPx: UniformValue<number>;
102
121
  uFarPlane: UniformValue<number>;
103
- uTextWidth: UniformValue<number>;
104
- uTextHeight: UniformValue<number>;
105
- uBgYBounds: UniformValue<Vector2>;
106
- /** Screen-space declutter hide factor (0 = shown … 1 = hidden), animated
107
- * by the declutter fade. Driven by the declutter pass, not by material
108
- * style updates. */
109
- uDeclutterHide: UniformValue<number>;
110
122
  uRTCCenter: UniformValue<Vector3>;
111
123
  uRTCCenterView: UniformValue<Vector3>;
112
124
  uEyeRTELow: UniformValue<Vector3>;
113
125
  uEyeRTEHigh: UniformValue<Vector3>;
114
126
  /** Always 1.0 — blocks fast-math reassociation of the RTE recombination. */
115
127
  u_rteOne?: UniformValue<number>;
116
- nvr_uBatchId: UniformValue<number>;
117
128
  nvr_uPickable: UniformValue<number>;
118
129
  uAtlas: UniformValue<DataTexture | null>;
119
130
  /** COLRv1 RGBA atlas. `null` when the font has no color glyphs. */
@@ -122,11 +133,12 @@ export type SdfTextBaseRefs = {
122
133
  uSdfAtlasSize: UniformValue<Vector2>;
123
134
  /** Current color atlas pixel dimensions; updated when the color atlas grows. */
124
135
  uColorAtlasSize: UniformValue<Vector2>;
125
-
126
- // Conditional RTE/RTC position uniforms
127
- uRTEPositionLOW?: UniformValue<Vector3>;
128
- uRTEPositionHIGH?: UniformValue<Vector3>;
129
- uRTCPosition?: UniformValue<Vector3>;
136
+ /** Per-label state, indexed by the `labelIndex` instance attribute. Owned by
137
+ * the mesh and swapped wholesale when it outgrows its capacity. */
138
+ uLabelData: UniformValue<DataTexture | null>;
139
+ /** Dimensions of `uLabelData` in texels, for the shader's index-to-texel
140
+ * math. Read as an `ivec2`. */
141
+ uLabelTexSize: UniformValue<Vector2>;
130
142
  };
131
143
 
132
144
  export type SdfTextBaseUniforms = Partial<SdfTextBaseRefs>;
@@ -151,15 +163,6 @@ export type SdfTextBaseMutates = Mutates<
151
163
  viewMatrix: Matrix4,
152
164
  state: SdfTextBaseState,
153
165
  ) => void;
154
- /**
155
- * Update text dimension uniforms after text shaping.
156
- */
157
- updateTextDimensions: (
158
- textWidth: number,
159
- textHeight: number,
160
- bgMinY: number,
161
- bgMaxY: number,
162
- ) => void;
163
166
  /**
164
167
  * Set the SDF atlas texture external ref.
165
168
  */
@@ -175,23 +178,18 @@ export type SdfTextBaseMutates = Mutates<
175
178
  */
176
179
  updateAtlasSizes: () => void;
177
180
  /**
178
- * Update position uniforms (RTE or RTC).
181
+ * Point the shader at the mesh's per-label data texture. Must be re-called
182
+ * whenever the mesh grows it, since growing allocates a new texture.
179
183
  */
180
- setPosition: (
181
- position: Float32Array | { high: Float32Array; low: Float32Array },
182
- useRTE: boolean,
183
- rtcCenter?: [number, number, number],
184
+ setLabelDataTexture: (
185
+ texture: DataTexture | null,
186
+ width: number,
187
+ height: number,
184
188
  ) => void;
185
189
  /**
186
- * Set the batch ID uniform (immutable after mount, but needed for batch creation).
187
- */
188
- setBatchId: (batchId: number) => void;
189
- /**
190
- * Set the declutter hide factor (0 = shown … 1 = hidden); the fragment
191
- * shader scales opacity by `1 - value` and the vertex shader hard-culls
192
- * at 1. Kept separate from `show`/`opacity` so user-driven visibility and
193
- * declutter results compose instead of clobbering each other.
190
+ * Update the batch's RTC center (the tile transform). Only meaningful in
191
+ * RTC mode; in RTE mode anchors carry their own high/low split.
194
192
  */
195
- setDeclutterHide: (value: number) => void;
193
+ setRtcCenter: (center: [number, number, number]) => void;
196
194
  }
197
195
  >;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Geometry type constants for mesh objects.
3
+ * These types are used for feature evaluation and runtime validation.
4
+ */
5
+ export const GEOMETRY_TYPES = {
6
+ Billboard: "billboard",
7
+ Text: "text",
8
+ Point: "point",
9
+ Polyline: "polyline",
10
+ Polygon: "polygon",
11
+ Model: "model",
12
+ } as const;
13
+
14
+ /**
15
+ * Array of valid geometry type values for runtime validation.
16
+ */
17
+ export const VALID_GEOMETRY_TYPES = Object.values(GEOMETRY_TYPES);
18
+
19
+ /**
20
+ * Type representing a valid geometry type value.
21
+ */
22
+ export type GeometryType = (typeof GEOMETRY_TYPES)[keyof typeof GEOMETRY_TYPES];
package/src/mesh/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./constants";
1
2
  export * from "./model";
2
3
  export * from "./polygon";
3
4
  export * from "./polyline";
@@ -12,5 +13,4 @@ export * from "./pickableMeshWrapper";
12
13
  export * from "./rtcRteHelper";
13
14
  export * from "./sprite";
14
15
  export * from "./sdfText";
15
- export * from "./batchedSdfText";
16
16
  export * from "./DrapedMesh";
package/src/mesh/model.ts CHANGED
@@ -39,6 +39,7 @@ import {
39
39
  updateBatchAttribute,
40
40
  type BatchTextureConfig,
41
41
  } from "./batchTexture";
42
+ import { GEOMETRY_TYPES } from "./constants";
42
43
  import type { FeatureMesh } from "./featureMesh";
43
44
  import type { PickableMesh } from "./pickableMesh";
44
45
  import { releaseGeometryArraysAfterUpload } from "./releaseGeometryArrays";
@@ -96,6 +97,11 @@ export class ModelMesh
96
97
  });
97
98
  }
98
99
 
100
+ /**
101
+ * Geometry type of this mesh.
102
+ */
103
+ readonly geometryType = GEOMETRY_TYPES.Model;
104
+
99
105
  get water(): boolean {
100
106
  for (const enhancer of this._enhancers.values()) {
101
107
  // Assume the first enhancer has the common value.
@@ -32,6 +32,7 @@ import type {
32
32
  BatchTextureRowKey,
33
33
  DefaultBatchAttributeValues,
34
34
  } from "./batchTexture";
35
+ import { GEOMETRY_TYPES } from "./constants";
35
36
  import { releaseGeometryArraysAfterUpload } from "./releaseGeometryArrays";
36
37
  import { setupRTECallback } from "./rtcRteHelper";
37
38
 
@@ -85,6 +86,11 @@ export class PolygonMesh extends BatchedFeatureMesh<
85
86
  return !!this._enhancedMaterial;
86
87
  }
87
88
 
89
+ /**
90
+ * Geometry type of this mesh.
91
+ */
92
+ readonly geometryType = GEOMETRY_TYPES.Polygon;
93
+
88
94
  init(mesh: NavaraPolygonMesh, tileHandle: TileHandle | undefined) {
89
95
  this.batchLength = mesh.batch_length;
90
96
  // Register cleanup listener first (before any potential early returns)
@@ -24,6 +24,7 @@ import type {
24
24
  BatchTextureRowKey,
25
25
  DefaultBatchAttributeValues,
26
26
  } from "./batchTexture";
27
+ import { GEOMETRY_TYPES } from "./constants";
27
28
  import { releaseGeometryArraysAfterUpload } from "./releaseGeometryArrays";
28
29
  import { setupRTECallback } from "./rtcRteHelper";
29
30
 
@@ -65,6 +66,11 @@ export class PolylineMesh extends BatchedFeatureMesh<
65
66
  this.ctx = ctx;
66
67
  }
67
68
 
69
+ /**
70
+ * Geometry type of this mesh.
71
+ */
72
+ readonly geometryType = GEOMETRY_TYPES.Polyline;
73
+
68
74
  init(mesh: NavaraPolylineMesh) {
69
75
  this.batchLength = mesh.batch_length;
70
76
  const geometryResult = this.initGeometry(mesh);
@@ -1,5 +1,5 @@
1
1
  import type { Transform } from "@navaramap/engine";
2
- import { calcCameraPosition, calcModelMatrixRTE } from "@navaramap/three_api";
2
+ import { calcCameraPosition, calcModelMatrixRTE } from "@navaramap/three-api";
3
3
  import { Camera, Matrix4, Object3D, Vector3 } from "three";
4
4
 
5
5
  import { setTransform } from "../event";