@multiplekex/shallot 0.1.12 → 0.2.0
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/package.json +3 -4
- package/src/core/builder.ts +71 -32
- package/src/core/component.ts +25 -11
- package/src/core/index.ts +14 -13
- package/src/core/math.ts +135 -0
- package/src/core/runtime.ts +0 -1
- package/src/core/state.ts +9 -68
- package/src/core/xml.ts +381 -265
- package/src/editor/format.ts +5 -0
- package/src/editor/index.ts +101 -0
- package/src/extras/arrows/index.ts +28 -69
- package/src/extras/gradient/index.ts +36 -52
- package/src/extras/lines/index.ts +51 -122
- package/src/extras/orbit/index.ts +40 -15
- package/src/extras/text/font.ts +546 -0
- package/src/extras/text/index.ts +158 -204
- package/src/extras/text/sdf.ts +429 -0
- package/src/standard/activity/index.ts +172 -0
- package/src/standard/compute/graph.ts +23 -23
- package/src/standard/compute/index.ts +76 -61
- package/src/standard/defaults.ts +8 -5
- package/src/standard/index.ts +1 -0
- package/src/standard/input/index.ts +30 -19
- package/src/standard/loading/index.ts +18 -13
- package/src/standard/render/bvh/blas.ts +752 -0
- package/src/standard/render/bvh/radix.ts +476 -0
- package/src/standard/render/bvh/structs.ts +167 -0
- package/src/standard/render/bvh/tlas.ts +886 -0
- package/src/standard/render/bvh/traverse.ts +467 -0
- package/src/standard/render/camera.ts +302 -27
- package/src/standard/render/data.ts +93 -0
- package/src/standard/render/depth.ts +117 -0
- package/src/standard/render/forward/index.ts +259 -0
- package/src/standard/render/forward/raster.ts +228 -0
- package/src/standard/render/index.ts +443 -70
- package/src/standard/render/indirect.ts +40 -0
- package/src/standard/render/instance.ts +214 -0
- package/src/standard/render/intersection.ts +72 -0
- package/src/standard/render/light.ts +16 -16
- package/src/standard/render/mesh/index.ts +67 -75
- package/src/standard/render/mesh/unified.ts +96 -0
- package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
- package/src/standard/render/pass.ts +10 -4
- package/src/standard/render/postprocess.ts +142 -64
- package/src/standard/render/ray.ts +61 -0
- package/src/standard/render/scene.ts +38 -164
- package/src/standard/render/shaders.ts +484 -0
- package/src/standard/render/surface/compile.ts +3 -10
- package/src/standard/render/surface/index.ts +60 -30
- package/src/standard/render/surface/noise.ts +45 -0
- package/src/standard/render/surface/structs.ts +60 -19
- package/src/standard/render/surface/wgsl.ts +573 -0
- package/src/standard/render/triangle.ts +84 -0
- package/src/standard/transforms/index.ts +4 -6
- package/src/standard/tween/index.ts +10 -1
- package/src/standard/tween/sequence.ts +24 -16
- package/src/standard/tween/tween.ts +67 -16
- package/src/core/types.ts +0 -37
- package/src/standard/compute/inspect.ts +0 -201
- package/src/standard/compute/pass.ts +0 -23
- package/src/standard/compute/timing.ts +0 -139
- package/src/standard/render/forward.ts +0 -273
package/src/extras/text/index.ts
CHANGED
|
@@ -1,142 +1,88 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Font, loadFont } from "./font";
|
|
2
|
+
import { SDFGenerator } from "./sdf";
|
|
2
3
|
import {
|
|
3
4
|
MAX_ENTITIES,
|
|
4
5
|
resource,
|
|
5
6
|
registerPostLoadHook,
|
|
7
|
+
createFieldProxy,
|
|
6
8
|
type Plugin,
|
|
7
9
|
type State,
|
|
8
10
|
type System,
|
|
9
11
|
type PostLoadContext,
|
|
12
|
+
type FieldProxy,
|
|
10
13
|
} from "../../core";
|
|
11
|
-
import { setTraits
|
|
14
|
+
import { setTraits } from "../../core/component";
|
|
12
15
|
import { Compute, ComputePlugin } from "../../standard/compute";
|
|
13
16
|
import {
|
|
14
17
|
Render,
|
|
15
18
|
RenderPlugin,
|
|
16
|
-
DEPTH_FORMAT,
|
|
17
19
|
Pass,
|
|
18
20
|
registerDraw,
|
|
19
21
|
type Draw,
|
|
20
22
|
type SharedPassContext,
|
|
21
23
|
} from "../../standard/render";
|
|
24
|
+
import { DEPTH_FORMAT } from "../../standard/render/scene";
|
|
25
|
+
import { SCENE_STRUCT_WGSL } from "../../standard/render/shaders";
|
|
22
26
|
import { Transform } from "../../standard/transforms";
|
|
23
27
|
|
|
24
28
|
const MAX_GLYPHS = 50000;
|
|
25
29
|
const GLYPH_FLOATS = 16;
|
|
30
|
+
const SDF_SIZE = 64;
|
|
26
31
|
const SDF_EXPONENT = 9;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Apply exponential encoding (Troika formula)
|
|
41
|
-
const absDist = Math.min(1, Math.abs(signedDist));
|
|
42
|
-
let alpha = Math.pow(1 - absDist, SDF_EXPONENT) / 2;
|
|
43
|
-
if (signedDist < 0) {
|
|
44
|
-
alpha = 1 - alpha;
|
|
45
|
-
}
|
|
46
|
-
encoded[i] = Math.round(Math.max(0, Math.min(255, alpha * 255)));
|
|
47
|
-
}
|
|
48
|
-
return encoded;
|
|
32
|
+
|
|
33
|
+
const fontUrls: string[] = [];
|
|
34
|
+
const loadedFonts: (Font | null)[] = [];
|
|
35
|
+
|
|
36
|
+
export function font(url: string): number {
|
|
37
|
+
const id = fontUrls.length;
|
|
38
|
+
fontUrls.push(url);
|
|
39
|
+
loadedFonts.push(null);
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getFont(id: number): Font | null {
|
|
44
|
+
return loadedFonts[id] ?? null;
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
export function clearFonts(): void {
|
|
48
|
+
fontUrls.length = 0;
|
|
49
|
+
loadedFonts.length = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function loadAllFonts(): Promise<void> {
|
|
53
|
+
await Promise.all(
|
|
54
|
+
fontUrls.map(async (url, id) => {
|
|
55
|
+
loadedFonts[id] = await loadFont(url);
|
|
56
|
+
})
|
|
57
|
+
);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export const TextData = {
|
|
59
61
|
data: new Float32Array(MAX_ENTITIES * 12),
|
|
62
|
+
fonts: new Uint32Array(MAX_ENTITIES),
|
|
60
63
|
};
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function textProxy(offset: number): TextProxy {
|
|
65
|
-
const data = TextData.data;
|
|
66
|
-
|
|
67
|
-
function getValue(eid: number): number {
|
|
68
|
-
return data[eid * 12 + offset];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function setValue(eid: number, value: number): void {
|
|
72
|
-
data[eid * 12 + offset] = value;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return new Proxy([] as unknown as TextProxy, {
|
|
76
|
-
get(_, prop) {
|
|
77
|
-
if (prop === "get") return getValue;
|
|
78
|
-
if (prop === "set") return setValue;
|
|
79
|
-
const eid = Number(prop);
|
|
80
|
-
if (Number.isNaN(eid)) return undefined;
|
|
81
|
-
return getValue(eid);
|
|
82
|
-
},
|
|
83
|
-
set(_, prop, value) {
|
|
84
|
-
const eid = Number(prop);
|
|
85
|
-
if (Number.isNaN(eid)) return false;
|
|
86
|
-
setValue(eid, value);
|
|
87
|
-
return true;
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function colorProxy(): TextProxy {
|
|
93
|
-
const data = TextData.data;
|
|
65
|
+
const data = TextData.data;
|
|
66
|
+
const fonts = TextData.fonts;
|
|
94
67
|
|
|
68
|
+
function packedColorProxy(data: Float32Array, stride: number, offset: number): FieldProxy {
|
|
95
69
|
function getValue(eid: number): number {
|
|
96
|
-
const
|
|
97
|
-
const r = Math.round(data[
|
|
98
|
-
const g = Math.round(data[
|
|
99
|
-
const b = Math.round(data[
|
|
70
|
+
const o = eid * stride + offset;
|
|
71
|
+
const r = Math.round(data[o] * 255);
|
|
72
|
+
const g = Math.round(data[o + 1] * 255);
|
|
73
|
+
const b = Math.round(data[o + 2] * 255);
|
|
100
74
|
return (r << 16) | (g << 8) | b;
|
|
101
75
|
}
|
|
102
76
|
|
|
103
77
|
function setValue(eid: number, value: number): void {
|
|
104
|
-
const
|
|
105
|
-
data[
|
|
106
|
-
data[
|
|
107
|
-
data[
|
|
108
|
-
data[
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return new Proxy([] as unknown as TextProxy, {
|
|
112
|
-
get(_, prop) {
|
|
113
|
-
if (prop === "get") return getValue;
|
|
114
|
-
if (prop === "set") return setValue;
|
|
115
|
-
const eid = Number(prop);
|
|
116
|
-
if (Number.isNaN(eid)) return undefined;
|
|
117
|
-
return getValue(eid);
|
|
118
|
-
},
|
|
119
|
-
set(_, prop, value) {
|
|
120
|
-
const eid = Number(prop);
|
|
121
|
-
if (Number.isNaN(eid)) return false;
|
|
122
|
-
setValue(eid, value);
|
|
123
|
-
return true;
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function colorChannelProxy(channelIndex: number): TextProxy {
|
|
129
|
-
const data = TextData.data;
|
|
130
|
-
|
|
131
|
-
function getValue(eid: number): number {
|
|
132
|
-
return data[eid * 12 + 8 + channelIndex];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function setValue(eid: number, value: number): void {
|
|
136
|
-
data[eid * 12 + 8 + channelIndex] = value;
|
|
78
|
+
const o = eid * stride + offset;
|
|
79
|
+
data[o] = ((value >> 16) & 0xff) / 255;
|
|
80
|
+
data[o + 1] = ((value >> 8) & 0xff) / 255;
|
|
81
|
+
data[o + 2] = (value & 0xff) / 255;
|
|
82
|
+
data[o + 3] = 1;
|
|
137
83
|
}
|
|
138
84
|
|
|
139
|
-
return new Proxy([] as unknown as
|
|
85
|
+
return new Proxy([] as unknown as FieldProxy, {
|
|
140
86
|
get(_, prop) {
|
|
141
87
|
if (prop === "get") return getValue;
|
|
142
88
|
if (prop === "set") return setValue;
|
|
@@ -181,15 +127,16 @@ function contentProxy(): TextContentProxy {
|
|
|
181
127
|
|
|
182
128
|
export const Text = {
|
|
183
129
|
content: contentProxy(),
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
130
|
+
font: fonts,
|
|
131
|
+
fontSize: createFieldProxy(data, 12, 0),
|
|
132
|
+
opacity: createFieldProxy(data, 12, 1),
|
|
133
|
+
visible: createFieldProxy(data, 12, 2),
|
|
134
|
+
anchorX: createFieldProxy(data, 12, 3),
|
|
135
|
+
anchorY: createFieldProxy(data, 12, 4),
|
|
136
|
+
color: packedColorProxy(data, 12, 8),
|
|
137
|
+
colorR: createFieldProxy(data, 12, 8),
|
|
138
|
+
colorG: createFieldProxy(data, 12, 9),
|
|
139
|
+
colorB: createFieldProxy(data, 12, 10),
|
|
193
140
|
};
|
|
194
141
|
|
|
195
142
|
interface PendingText {
|
|
@@ -223,6 +170,7 @@ function finalizePendingText(_state: State, _context: PostLoadContext): void {
|
|
|
223
170
|
|
|
224
171
|
setTraits(Text, {
|
|
225
172
|
defaults: () => ({
|
|
173
|
+
font: 0,
|
|
226
174
|
fontSize: 1,
|
|
227
175
|
opacity: 1,
|
|
228
176
|
visible: 1,
|
|
@@ -238,6 +186,7 @@ setTraits(Text, {
|
|
|
238
186
|
pendingTextContent.push({ eid, content: parsed.content });
|
|
239
187
|
}
|
|
240
188
|
|
|
189
|
+
if (parsed.font) result.font = parseInt(parsed.font, 10);
|
|
241
190
|
if (parsed["font-size"]) result.fontSize = parseFloat(parsed["font-size"]);
|
|
242
191
|
if (parsed.fontSize) result.fontSize = parseFloat(parsed.fontSize);
|
|
243
192
|
if (parsed.opacity) result.opacity = parseFloat(parsed.opacity);
|
|
@@ -259,17 +208,6 @@ setTraits(Text, {
|
|
|
259
208
|
|
|
260
209
|
return result;
|
|
261
210
|
},
|
|
262
|
-
accessors: {
|
|
263
|
-
fontSize: Text.fontSize,
|
|
264
|
-
opacity: Text.opacity,
|
|
265
|
-
visible: Text.visible,
|
|
266
|
-
anchorX: Text.anchorX,
|
|
267
|
-
anchorY: Text.anchorY,
|
|
268
|
-
color: Text.color,
|
|
269
|
-
colorR: Text.colorR,
|
|
270
|
-
colorG: Text.colorG,
|
|
271
|
-
colorB: Text.colorB,
|
|
272
|
-
},
|
|
273
211
|
});
|
|
274
212
|
|
|
275
213
|
interface GlyphMetrics {
|
|
@@ -295,30 +233,25 @@ interface GlyphAtlas {
|
|
|
295
233
|
rowHeight: number;
|
|
296
234
|
cursorX: number;
|
|
297
235
|
cursorY: number;
|
|
298
|
-
|
|
299
|
-
|
|
236
|
+
font: Font;
|
|
237
|
+
sdfGenerator: SDFGenerator;
|
|
300
238
|
}
|
|
301
239
|
|
|
302
|
-
function createGlyphAtlas(device: GPUDevice): GlyphAtlas {
|
|
240
|
+
function createGlyphAtlas(device: GPUDevice, font: Font): GlyphAtlas {
|
|
303
241
|
const width = 2048;
|
|
304
242
|
const height = 2048;
|
|
305
|
-
const fontSize = 128;
|
|
306
243
|
|
|
307
244
|
const texture = device.createTexture({
|
|
308
245
|
size: { width, height },
|
|
309
246
|
format: "r8unorm",
|
|
310
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.
|
|
247
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
311
248
|
label: "glyphAtlas",
|
|
312
249
|
});
|
|
313
250
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
fontStyle: "normal",
|
|
319
|
-
buffer: 16,
|
|
320
|
-
radius: 48,
|
|
321
|
-
cutoff: 0.5,
|
|
251
|
+
const sdfGenerator = new SDFGenerator({
|
|
252
|
+
device,
|
|
253
|
+
sdfSize: SDF_SIZE,
|
|
254
|
+
exponent: SDF_EXPONENT,
|
|
322
255
|
});
|
|
323
256
|
|
|
324
257
|
return {
|
|
@@ -330,63 +263,71 @@ function createGlyphAtlas(device: GPUDevice): GlyphAtlas {
|
|
|
330
263
|
rowHeight: 0,
|
|
331
264
|
cursorX: 0,
|
|
332
265
|
cursorY: 0,
|
|
333
|
-
|
|
334
|
-
|
|
266
|
+
font,
|
|
267
|
+
sdfGenerator,
|
|
335
268
|
};
|
|
336
269
|
}
|
|
337
270
|
|
|
338
|
-
function ensureGlyph(
|
|
271
|
+
function ensureGlyph(atlas: GlyphAtlas, char: string): GlyphMetrics | null {
|
|
339
272
|
const existing = atlas.glyphs.get(char);
|
|
340
273
|
if (existing) return existing;
|
|
341
274
|
|
|
342
|
-
const
|
|
275
|
+
const path = atlas.font.glyphPath(char);
|
|
276
|
+
const bounds = atlas.font.glyphBounds(char);
|
|
277
|
+
const advance = atlas.font.advance(char);
|
|
278
|
+
|
|
279
|
+
if (!path || !bounds) return null;
|
|
280
|
+
|
|
281
|
+
const [xMin, yMin, xMax, yMax] = bounds;
|
|
282
|
+
const unitsPerEm = atlas.font.unitsPerEm;
|
|
283
|
+
|
|
284
|
+
const padding = unitsPerEm * 0.1;
|
|
285
|
+
const paddedBounds: [number, number, number, number] = [
|
|
286
|
+
xMin - padding,
|
|
287
|
+
yMin - padding,
|
|
288
|
+
xMax + padding,
|
|
289
|
+
yMax + padding,
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
const glyphWidth = paddedBounds[2] - paddedBounds[0];
|
|
293
|
+
const glyphHeight = paddedBounds[3] - paddedBounds[1];
|
|
343
294
|
|
|
344
|
-
if (atlas.cursorX +
|
|
295
|
+
if (atlas.cursorX + SDF_SIZE > atlas.width) {
|
|
345
296
|
atlas.cursorX = 0;
|
|
346
297
|
atlas.cursorY += atlas.rowHeight;
|
|
347
298
|
atlas.rowHeight = 0;
|
|
348
299
|
}
|
|
349
300
|
|
|
350
|
-
if (atlas.cursorY +
|
|
301
|
+
if (atlas.cursorY + SDF_SIZE > atlas.height) {
|
|
351
302
|
throw new Error("Glyph atlas full");
|
|
352
303
|
}
|
|
353
304
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
device.queue.writeTexture(
|
|
357
|
-
{
|
|
358
|
-
texture: atlas.texture,
|
|
359
|
-
origin: { x: atlas.cursorX, y: atlas.cursorY },
|
|
360
|
-
},
|
|
361
|
-
glyphData as Uint8Array<ArrayBuffer>,
|
|
362
|
-
{ bytesPerRow: glyph.width },
|
|
363
|
-
{ width: glyph.width, height: glyph.height }
|
|
364
|
-
);
|
|
305
|
+
atlas.sdfGenerator.generate(path, paddedBounds, atlas.texture, atlas.cursorX, atlas.cursorY);
|
|
365
306
|
|
|
366
307
|
const metrics: GlyphMetrics = {
|
|
367
|
-
width:
|
|
368
|
-
height:
|
|
369
|
-
glyphWidth:
|
|
370
|
-
glyphHeight:
|
|
371
|
-
glyphTop:
|
|
372
|
-
glyphLeft:
|
|
373
|
-
advance:
|
|
308
|
+
width: SDF_SIZE,
|
|
309
|
+
height: SDF_SIZE,
|
|
310
|
+
glyphWidth: glyphWidth / unitsPerEm,
|
|
311
|
+
glyphHeight: glyphHeight / unitsPerEm,
|
|
312
|
+
glyphTop: paddedBounds[3] / unitsPerEm,
|
|
313
|
+
glyphLeft: paddedBounds[0] / unitsPerEm,
|
|
314
|
+
advance: advance / unitsPerEm,
|
|
374
315
|
u0: atlas.cursorX / atlas.width,
|
|
375
316
|
v0: atlas.cursorY / atlas.height,
|
|
376
|
-
u1: (atlas.cursorX +
|
|
377
|
-
v1: (atlas.cursorY +
|
|
317
|
+
u1: (atlas.cursorX + SDF_SIZE) / atlas.width,
|
|
318
|
+
v1: (atlas.cursorY + SDF_SIZE) / atlas.height,
|
|
378
319
|
};
|
|
379
320
|
|
|
380
321
|
atlas.glyphs.set(char, metrics);
|
|
381
|
-
atlas.cursorX +=
|
|
382
|
-
atlas.rowHeight = Math.max(atlas.rowHeight,
|
|
322
|
+
atlas.cursorX += SDF_SIZE;
|
|
323
|
+
atlas.rowHeight = Math.max(atlas.rowHeight, SDF_SIZE);
|
|
383
324
|
|
|
384
325
|
return metrics;
|
|
385
326
|
}
|
|
386
327
|
|
|
387
|
-
function ensureString(
|
|
328
|
+
function ensureString(atlas: GlyphAtlas, text: string): void {
|
|
388
329
|
for (const char of text) {
|
|
389
|
-
ensureGlyph(
|
|
330
|
+
ensureGlyph(atlas, char);
|
|
390
331
|
}
|
|
391
332
|
}
|
|
392
333
|
|
|
@@ -411,15 +352,20 @@ interface LayoutResult {
|
|
|
411
352
|
|
|
412
353
|
function layoutText(text: string, atlas: GlyphAtlas, fontSize: number): LayoutResult {
|
|
413
354
|
const glyphs: LayoutGlyph[] = [];
|
|
414
|
-
const scale = fontSize
|
|
355
|
+
const scale = fontSize;
|
|
415
356
|
|
|
416
357
|
let cursorX = 0;
|
|
417
358
|
let maxHeight = 0;
|
|
359
|
+
let prevChar: string | null = null;
|
|
418
360
|
|
|
419
361
|
for (const char of text) {
|
|
420
362
|
const metrics = atlas.glyphs.get(char);
|
|
421
363
|
if (!metrics) continue;
|
|
422
364
|
|
|
365
|
+
if (prevChar) {
|
|
366
|
+
cursorX += atlas.font.kerning(prevChar, char) / atlas.font.unitsPerEm * scale;
|
|
367
|
+
}
|
|
368
|
+
|
|
423
369
|
const glyphW = metrics.glyphWidth * scale;
|
|
424
370
|
const glyphH = metrics.glyphHeight * scale;
|
|
425
371
|
const advance = metrics.advance * scale;
|
|
@@ -442,6 +388,7 @@ function layoutText(text: string, atlas: GlyphAtlas, fontSize: number): LayoutRe
|
|
|
442
388
|
|
|
443
389
|
cursorX += advance;
|
|
444
390
|
maxHeight = Math.max(maxHeight, glyphH);
|
|
391
|
+
prevChar = char;
|
|
445
392
|
}
|
|
446
393
|
|
|
447
394
|
return {
|
|
@@ -452,16 +399,7 @@ function layoutText(text: string, atlas: GlyphAtlas, fontSize: number): LayoutRe
|
|
|
452
399
|
}
|
|
453
400
|
|
|
454
401
|
const textShader = /* wgsl */ `
|
|
455
|
-
|
|
456
|
-
viewProj: mat4x4<f32>,
|
|
457
|
-
cameraWorld: mat4x4<f32>,
|
|
458
|
-
ambientColor: vec4<f32>,
|
|
459
|
-
sunDirection: vec4<f32>,
|
|
460
|
-
sunColor: vec4<f32>,
|
|
461
|
-
cameraMode: f32,
|
|
462
|
-
cameraSize: f32,
|
|
463
|
-
viewport: vec2<f32>,
|
|
464
|
-
}
|
|
402
|
+
${SCENE_STRUCT_WGSL}
|
|
465
403
|
|
|
466
404
|
struct GlyphInstance {
|
|
467
405
|
posX: f32,
|
|
@@ -490,7 +428,7 @@ struct VertexOutput {
|
|
|
490
428
|
@location(0) uv: vec2<f32>,
|
|
491
429
|
@location(1) color: vec4<f32>,
|
|
492
430
|
@location(2) localUV: vec2<f32>,
|
|
493
|
-
@location(3)
|
|
431
|
+
@location(3) glyphDimensions: vec2<f32>,
|
|
494
432
|
}
|
|
495
433
|
|
|
496
434
|
@vertex
|
|
@@ -506,27 +444,27 @@ fn vs(@builtin(vertex_index) vid: u32) -> VertexOutput {
|
|
|
506
444
|
switch cornerIdx {
|
|
507
445
|
case 0u: {
|
|
508
446
|
localPos = vec2(0.0, 0.0);
|
|
509
|
-
uv = vec2(glyph.u0, glyph.
|
|
447
|
+
uv = vec2(glyph.u0, glyph.v0);
|
|
510
448
|
}
|
|
511
449
|
case 1u: {
|
|
512
450
|
localPos = vec2(1.0, 0.0);
|
|
513
|
-
uv = vec2(glyph.u1, glyph.
|
|
451
|
+
uv = vec2(glyph.u1, glyph.v0);
|
|
514
452
|
}
|
|
515
453
|
case 2u: {
|
|
516
454
|
localPos = vec2(1.0, 1.0);
|
|
517
|
-
uv = vec2(glyph.u1, glyph.
|
|
455
|
+
uv = vec2(glyph.u1, glyph.v1);
|
|
518
456
|
}
|
|
519
457
|
case 3u: {
|
|
520
458
|
localPos = vec2(0.0, 0.0);
|
|
521
|
-
uv = vec2(glyph.u0, glyph.
|
|
459
|
+
uv = vec2(glyph.u0, glyph.v0);
|
|
522
460
|
}
|
|
523
461
|
case 4u: {
|
|
524
462
|
localPos = vec2(1.0, 1.0);
|
|
525
|
-
uv = vec2(glyph.u1, glyph.
|
|
463
|
+
uv = vec2(glyph.u1, glyph.v1);
|
|
526
464
|
}
|
|
527
465
|
case 5u: {
|
|
528
466
|
localPos = vec2(0.0, 1.0);
|
|
529
|
-
uv = vec2(glyph.u0, glyph.
|
|
467
|
+
uv = vec2(glyph.u0, glyph.v1);
|
|
530
468
|
}
|
|
531
469
|
default: {
|
|
532
470
|
localPos = vec2(0.0);
|
|
@@ -548,7 +486,7 @@ fn vs(@builtin(vertex_index) vid: u32) -> VertexOutput {
|
|
|
548
486
|
out.uv = uv;
|
|
549
487
|
out.color = glyph.color;
|
|
550
488
|
out.localUV = localPos;
|
|
551
|
-
out.
|
|
489
|
+
out.glyphDimensions = vec2(glyph.width, glyph.height);
|
|
552
490
|
return out;
|
|
553
491
|
}
|
|
554
492
|
|
|
@@ -561,21 +499,17 @@ struct FragmentOutput {
|
|
|
561
499
|
fn fs(input: VertexOutput) -> FragmentOutput {
|
|
562
500
|
let sdfValue = textureSample(atlasTexture, atlasSampler, input.uv).r;
|
|
563
501
|
|
|
564
|
-
// Decode exponential SDF (inverse of Troika-style encoding)
|
|
565
502
|
let sdfExponent = 9.0;
|
|
566
503
|
let isOutside = sdfValue < 0.5;
|
|
567
504
|
let processedAlpha = select(1.0 - sdfValue, sdfValue, isOutside);
|
|
568
505
|
let normalizedDist = 1.0 - pow(2.0 * processedAlpha, 1.0 / sdfExponent);
|
|
569
|
-
let signedDist = select(-normalizedDist, normalizedDist, isOutside);
|
|
570
506
|
|
|
571
|
-
|
|
572
|
-
let
|
|
573
|
-
let
|
|
574
|
-
let smoothing = clamp(aaRadius * 0.5, 0.01, 0.25);
|
|
507
|
+
let maxDimension = max(input.glyphDimensions.x, input.glyphDimensions.y);
|
|
508
|
+
let absDist = normalizedDist * maxDimension;
|
|
509
|
+
let signedDist = select(-absDist, absDist, isOutside);
|
|
575
510
|
|
|
576
|
-
|
|
577
|
-
let
|
|
578
|
-
let alpha = smoothstep(edgeOffset + smoothing, edgeOffset - smoothing, signedDist);
|
|
511
|
+
let aaDist = length(fwidth(input.localUV * input.glyphDimensions)) * 0.5;
|
|
512
|
+
let alpha = smoothstep(aaDist, -aaDist, signedDist);
|
|
579
513
|
|
|
580
514
|
if alpha < 0.01 {
|
|
581
515
|
discard;
|
|
@@ -653,7 +587,7 @@ function createTextDraw(config: TextConfig): Draw {
|
|
|
653
587
|
|
|
654
588
|
return {
|
|
655
589
|
id: "text",
|
|
656
|
-
pass: Pass.
|
|
590
|
+
pass: Pass.Overlay,
|
|
657
591
|
order: 2,
|
|
658
592
|
|
|
659
593
|
execute() {},
|
|
@@ -686,26 +620,26 @@ function createTextDraw(config: TextConfig): Draw {
|
|
|
686
620
|
};
|
|
687
621
|
}
|
|
688
622
|
|
|
689
|
-
export interface
|
|
690
|
-
|
|
623
|
+
export interface Glyphs {
|
|
624
|
+
atlases: GlyphAtlas[];
|
|
691
625
|
sampler: GPUSampler;
|
|
692
626
|
buffer: GPUBuffer;
|
|
693
627
|
staging: Float32Array;
|
|
694
628
|
count: number;
|
|
695
629
|
}
|
|
696
630
|
|
|
697
|
-
export const
|
|
631
|
+
export const Glyphs = resource<Glyphs>("glyphs");
|
|
698
632
|
|
|
699
633
|
const TextSystem: System = {
|
|
700
634
|
group: "draw",
|
|
701
635
|
|
|
702
636
|
update(state: State) {
|
|
703
637
|
const compute = Compute.from(state);
|
|
704
|
-
const text =
|
|
638
|
+
const text = Glyphs.from(state);
|
|
705
639
|
if (!compute || !text) return;
|
|
706
640
|
|
|
707
641
|
const { device } = compute;
|
|
708
|
-
const {
|
|
642
|
+
const { atlases, staging } = text;
|
|
709
643
|
const stagingU32 = new Uint32Array(staging.buffer);
|
|
710
644
|
|
|
711
645
|
let glyphCount = 0;
|
|
@@ -716,7 +650,11 @@ const TextSystem: System = {
|
|
|
716
650
|
const content = textContent.get(eid);
|
|
717
651
|
if (!content) continue;
|
|
718
652
|
|
|
719
|
-
|
|
653
|
+
const fontId = Text.font[eid];
|
|
654
|
+
const atlas = atlases[fontId] ?? atlases[0];
|
|
655
|
+
if (!atlas) continue;
|
|
656
|
+
|
|
657
|
+
ensureString(atlas, content);
|
|
720
658
|
|
|
721
659
|
const fontSize = Text.fontSize[eid];
|
|
722
660
|
const layout = layoutText(content, atlas, fontSize);
|
|
@@ -780,23 +718,39 @@ export const TextPlugin: Plugin = {
|
|
|
780
718
|
components: { Text },
|
|
781
719
|
dependencies: [ComputePlugin, RenderPlugin],
|
|
782
720
|
|
|
783
|
-
initialize(state: State) {
|
|
721
|
+
async initialize(state: State) {
|
|
784
722
|
registerPostLoadHook(finalizePendingText);
|
|
785
723
|
|
|
786
724
|
const compute = Compute.from(state);
|
|
787
725
|
const render = Render.from(state);
|
|
788
726
|
if (!compute || !render) return;
|
|
789
727
|
|
|
728
|
+
if (fontUrls.length === 0) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
await loadAllFonts();
|
|
733
|
+
|
|
790
734
|
const { device } = compute;
|
|
791
735
|
|
|
792
|
-
const
|
|
736
|
+
const atlases: GlyphAtlas[] = [];
|
|
737
|
+
for (const loadedFont of loadedFonts) {
|
|
738
|
+
if (loadedFont) {
|
|
739
|
+
atlases.push(createGlyphAtlas(device, loadedFont));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (atlases.length === 0) {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
793
747
|
const sampler = device.createSampler({
|
|
794
748
|
magFilter: "linear",
|
|
795
749
|
minFilter: "linear",
|
|
796
750
|
});
|
|
797
751
|
|
|
798
|
-
const textState:
|
|
799
|
-
|
|
752
|
+
const textState: Glyphs = {
|
|
753
|
+
atlases,
|
|
800
754
|
sampler,
|
|
801
755
|
buffer: device.createBuffer({
|
|
802
756
|
label: "glyphs",
|
|
@@ -807,14 +761,14 @@ export const TextPlugin: Plugin = {
|
|
|
807
761
|
count: 0,
|
|
808
762
|
};
|
|
809
763
|
|
|
810
|
-
state.setResource(
|
|
764
|
+
state.setResource(Glyphs, textState);
|
|
811
765
|
|
|
812
766
|
registerDraw(
|
|
813
767
|
state,
|
|
814
768
|
createTextDraw({
|
|
815
769
|
scene: render.scene,
|
|
816
770
|
glyphs: textState.buffer,
|
|
817
|
-
atlas:
|
|
771
|
+
atlas: atlases[0].textureView,
|
|
818
772
|
sampler,
|
|
819
773
|
matrices: render.matrices,
|
|
820
774
|
getCount: () => textState.count,
|