@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.
- 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 +349 -282
- 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/atmosphere.test.ts +227 -28
- package/src/atmosphere.ts +219 -55
- package/src/constants/assets.ts +63 -13
- 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/point.ts +1 -0
- package/src/index.ts +7 -9
- 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 +18 -85
- 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/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 +32 -0
- 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
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
159
|
+
setLabelDataTexture: (
|
|
160
|
+
texture: DataTexture | null,
|
|
161
|
+
width: number,
|
|
162
|
+
height: number,
|
|
209
163
|
) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
237
|
-
refs.
|
|
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
|
|
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({
|
|
10
|
-
expect(state.
|
|
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
|
-
|
|
106
|
+
outlineOpacity: 0.25,
|
|
111
107
|
center: [1, 2] as [number, number],
|
|
112
108
|
};
|
|
113
109
|
const state = updateState({}, currentState);
|
|
114
|
-
expect(state.
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
setLabelDataTexture: (
|
|
185
|
+
texture: DataTexture | null,
|
|
186
|
+
width: number,
|
|
187
|
+
height: number,
|
|
184
188
|
) => void;
|
|
185
189
|
/**
|
|
186
|
-
*
|
|
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
|
-
|
|
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.
|
package/src/mesh/polygon.ts
CHANGED
|
@@ -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)
|
package/src/mesh/polyline.ts
CHANGED
|
@@ -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);
|