three-text 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -25
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/three/react.d.ts +0 -1
- package/dist/types/core/types.d.ts +0 -1
- package/dist/types/vector/index.d.ts +1 -0
- package/dist/vector/core/index.cjs +20 -56
- package/dist/vector/core/index.js +20 -56
- package/dist/vector/index.d.ts +1 -0
- package/dist/vector/index2.cjs +6 -19
- package/dist/vector/index2.js +6 -19
- package/dist/vector/react.d.ts +0 -1
- package/dist/vector/slugTSL.cjs +4 -27
- package/dist/vector/slugTSL.js +4 -27
- package/dist/vector/webgl/index.cjs +1 -4
- package/dist/vector/webgl/index.js +1 -4
- package/dist/vector/webgpu/index.cjs +0 -3
- package/dist/vector/webgpu/index.js +0 -3
- package/package.json +2 -2
|
@@ -256,7 +256,6 @@ class GlyphOutlineCollector {
|
|
|
256
256
|
// GPU-ready packed textures + vertex attribute buffers
|
|
257
257
|
const TEX_WIDTH = 4096;
|
|
258
258
|
const LOG_TEX_WIDTH = 12;
|
|
259
|
-
// Float/Uint32 reinterpretation helpers
|
|
260
259
|
const _f32 = new Float32Array(1);
|
|
261
260
|
const _u32 = new Uint32Array(_f32.buffer);
|
|
262
261
|
function uintAsFloat(u) {
|
|
@@ -268,9 +267,7 @@ function uintAsFloat(u) {
|
|
|
268
267
|
function packSlugData(shapes, options) {
|
|
269
268
|
const bandCount = options?.bandCount ?? 16;
|
|
270
269
|
const evenOdd = options?.evenOdd ?? false;
|
|
271
|
-
// Pack all curves into curveTexture
|
|
272
270
|
const allCurves = [];
|
|
273
|
-
// Estimate max texels needed
|
|
274
271
|
let totalCurves = 0;
|
|
275
272
|
for (const shape of shapes) {
|
|
276
273
|
totalCurves += shape.curves.length;
|
|
@@ -283,13 +280,12 @@ function packSlugData(shapes, options) {
|
|
|
283
280
|
const entries = [];
|
|
284
281
|
const [ox, oy] = shape.bounds;
|
|
285
282
|
for (const curve of shape.curves) {
|
|
286
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
287
283
|
if (curveX >= TEX_WIDTH - 1) {
|
|
288
284
|
curveX = 0;
|
|
289
285
|
curveY++;
|
|
290
286
|
}
|
|
291
|
-
//
|
|
292
|
-
//
|
|
287
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
288
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
293
289
|
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
294
290
|
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
295
291
|
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
@@ -318,12 +314,10 @@ function packSlugData(shapes, options) {
|
|
|
318
314
|
allCurves.push(entries);
|
|
319
315
|
}
|
|
320
316
|
const actualCurveTexHeight = curveY + 1;
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// [
|
|
324
|
-
// [hBandMax+
|
|
325
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
326
|
-
// First pass: compute total band texels needed
|
|
317
|
+
// Band texture layout per shape (relative to glyphLoc):
|
|
318
|
+
// [0 .. hBandMax] h-band headers
|
|
319
|
+
// [hBandMax+1 .. hBandMax+1+vBandMax] v-band headers
|
|
320
|
+
// [hBandMax+vBandMax+2 .. ] curve index lists
|
|
327
321
|
const shapeBandData = [];
|
|
328
322
|
let totalBandTexels = 0;
|
|
329
323
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -343,21 +337,19 @@ function packSlugData(shapes, options) {
|
|
|
343
337
|
const vBandCount = Math.min(bandCount, 255);
|
|
344
338
|
const bandMaxY = hBandCount - 1;
|
|
345
339
|
const bandMaxX = vBandCount - 1;
|
|
346
|
-
//
|
|
340
|
+
// Horizontal bands (partition y-axis)
|
|
347
341
|
const hBands = [];
|
|
348
342
|
const hLists = [];
|
|
349
343
|
const bandH = h / hBandCount;
|
|
350
344
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
351
345
|
const bandMinY = bi * bandH;
|
|
352
346
|
const bandMaxYCoord = bandMinY + bandH;
|
|
353
|
-
// Collect curves whose y-range overlaps this band
|
|
354
347
|
const list = [];
|
|
355
348
|
for (const c of curves) {
|
|
356
349
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
357
350
|
list.push({ curve: c, sortKey: c.maxX });
|
|
358
351
|
}
|
|
359
352
|
}
|
|
360
|
-
// Sort by descending max-x for early exit
|
|
361
353
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
362
354
|
const flatList = [];
|
|
363
355
|
for (const item of list) {
|
|
@@ -366,7 +358,7 @@ function packSlugData(shapes, options) {
|
|
|
366
358
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
367
359
|
hLists.push(flatList);
|
|
368
360
|
}
|
|
369
|
-
//
|
|
361
|
+
// Vertical bands (partition x-axis)
|
|
370
362
|
const vBands = [];
|
|
371
363
|
const vLists = [];
|
|
372
364
|
const bandW = w / vBandCount;
|
|
@@ -379,7 +371,6 @@ function packSlugData(shapes, options) {
|
|
|
379
371
|
list.push({ curve: c, sortKey: c.maxY });
|
|
380
372
|
}
|
|
381
373
|
}
|
|
382
|
-
// Sort by descending max-y for early exit
|
|
383
374
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
384
375
|
const flatList = [];
|
|
385
376
|
for (const item of list) {
|
|
@@ -388,7 +379,6 @@ function packSlugData(shapes, options) {
|
|
|
388
379
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
389
380
|
vLists.push(flatList);
|
|
390
381
|
}
|
|
391
|
-
// Total texels for this shape: band headers + curve lists
|
|
392
382
|
const headerTexels = hBandCount + vBandCount;
|
|
393
383
|
let listTexels = 0;
|
|
394
384
|
for (const l of hLists)
|
|
@@ -402,10 +392,8 @@ function packSlugData(shapes, options) {
|
|
|
402
392
|
});
|
|
403
393
|
totalBandTexels += total;
|
|
404
394
|
}
|
|
405
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
406
395
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
407
396
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
408
|
-
// Pack band data per shape
|
|
409
397
|
let bandX = 0;
|
|
410
398
|
let bandY = 0;
|
|
411
399
|
const glyphLocs = [];
|
|
@@ -415,11 +403,8 @@ function packSlugData(shapes, options) {
|
|
|
415
403
|
glyphLocs.push({ x: 0, y: 0 });
|
|
416
404
|
continue;
|
|
417
405
|
}
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
421
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
422
|
-
// To be safe, start each glyph at the beginning of a row if remaining space is tight.
|
|
406
|
+
// Band headers are read without CalcBandLoc wrapping, so all
|
|
407
|
+
// headers for a glyph must fit within a single texture row
|
|
423
408
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
424
409
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
425
410
|
bandX = 0;
|
|
@@ -428,11 +413,8 @@ function packSlugData(shapes, options) {
|
|
|
428
413
|
const glyphLocX = bandX;
|
|
429
414
|
const glyphLocY = bandY;
|
|
430
415
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
431
|
-
// Curve lists start after all headers
|
|
432
416
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
433
|
-
//
|
|
434
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
435
|
-
// Pad the offset to the next row start when a list would cross a row boundary.
|
|
417
|
+
// Curve lists aren't row-wrapped by the shader, so pad to avoid crossing
|
|
436
418
|
const ensureListFits = (listLen) => {
|
|
437
419
|
if (listLen === 0)
|
|
438
420
|
return;
|
|
@@ -441,21 +423,18 @@ function packSlugData(shapes, options) {
|
|
|
441
423
|
listStartOffset += (TEX_WIDTH - startX);
|
|
442
424
|
}
|
|
443
425
|
};
|
|
444
|
-
// Assign list offsets for h-bands
|
|
445
426
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
446
427
|
const listLen = sd.hLists[bi].length / 2;
|
|
447
428
|
ensureListFits(listLen);
|
|
448
429
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
449
430
|
listStartOffset += listLen;
|
|
450
431
|
}
|
|
451
|
-
// Assign list offsets for v-bands
|
|
452
432
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
453
433
|
const listLen = sd.vLists[bi].length / 2;
|
|
454
434
|
ensureListFits(listLen);
|
|
455
435
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
456
436
|
listStartOffset += listLen;
|
|
457
437
|
}
|
|
458
|
-
// Write h-band headers
|
|
459
438
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
460
439
|
const tx = glyphLocX + bi;
|
|
461
440
|
const ty = glyphLocY;
|
|
@@ -465,7 +444,6 @@ function packSlugData(shapes, options) {
|
|
|
465
444
|
bandData[idx + 2] = 0;
|
|
466
445
|
bandData[idx + 3] = 0;
|
|
467
446
|
}
|
|
468
|
-
// Write v-band headers (after h-bands)
|
|
469
447
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
470
448
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
471
449
|
const tx = vBandStart + bi;
|
|
@@ -476,9 +454,7 @@ function packSlugData(shapes, options) {
|
|
|
476
454
|
bandData[idx + 2] = 0;
|
|
477
455
|
bandData[idx + 3] = 0;
|
|
478
456
|
}
|
|
479
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
480
457
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
481
|
-
// Write h-band curve lists
|
|
482
458
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
483
459
|
const list = sd.hLists[bi];
|
|
484
460
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -487,13 +463,12 @@ function packSlugData(shapes, options) {
|
|
|
487
463
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
488
464
|
bx &= texWidthMask;
|
|
489
465
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
490
|
-
bandData[idx + 0] = list[ci];
|
|
491
|
-
bandData[idx + 1] = list[ci + 1];
|
|
466
|
+
bandData[idx + 0] = list[ci];
|
|
467
|
+
bandData[idx + 1] = list[ci + 1];
|
|
492
468
|
bandData[idx + 2] = 0;
|
|
493
469
|
bandData[idx + 3] = 0;
|
|
494
470
|
}
|
|
495
471
|
}
|
|
496
|
-
// Write v-band curve lists
|
|
497
472
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
498
473
|
const list = sd.vLists[bi];
|
|
499
474
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -508,24 +483,20 @@ function packSlugData(shapes, options) {
|
|
|
508
483
|
bandData[idx + 3] = 0;
|
|
509
484
|
}
|
|
510
485
|
}
|
|
511
|
-
// Advance band cursor past this shape's data
|
|
512
486
|
let endBx = glyphLocX + listStartOffset;
|
|
513
487
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
514
488
|
bandX = endBx & texWidthMask;
|
|
515
489
|
}
|
|
516
490
|
const actualBandTexHeight = bandY + 1;
|
|
517
|
-
// Build vertex attributes
|
|
518
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
519
491
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
520
492
|
const VERTS_PER_SHAPE = 4;
|
|
521
493
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
522
494
|
const indices = new Uint16Array(shapes.length * 6);
|
|
523
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
524
495
|
const cornerNormals = [
|
|
525
|
-
[-1, -1],
|
|
526
|
-
[1, -1],
|
|
527
|
-
[1, 1],
|
|
528
|
-
[-1, 1],
|
|
496
|
+
[-1, -1],
|
|
497
|
+
[1, -1],
|
|
498
|
+
[1, 1],
|
|
499
|
+
[-1, 1],
|
|
529
500
|
];
|
|
530
501
|
for (let si = 0; si < shapes.length; si++) {
|
|
531
502
|
const shape = shapes[si];
|
|
@@ -534,28 +505,23 @@ function packSlugData(shapes, options) {
|
|
|
534
505
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
535
506
|
const w = bMaxX - bMinX;
|
|
536
507
|
const h = bMaxY - bMinY;
|
|
537
|
-
// Corner positions in object-space
|
|
538
508
|
const corners = [
|
|
539
509
|
[bMinX, bMinY],
|
|
540
510
|
[bMaxX, bMinY],
|
|
541
511
|
[bMaxX, bMaxY],
|
|
542
512
|
[bMinX, bMaxY],
|
|
543
513
|
];
|
|
544
|
-
// Em-space sample coords in glyph-local space (origin at bounds min)
|
|
545
514
|
const emCorners = [
|
|
546
515
|
[0, 0],
|
|
547
516
|
[w, 0],
|
|
548
517
|
[w, h],
|
|
549
518
|
[0, h],
|
|
550
519
|
];
|
|
551
|
-
// Pack tex.z: glyph location in band texture
|
|
552
520
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
553
|
-
// Pack tex.w: band max + flags
|
|
554
521
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
555
522
|
if (evenOdd)
|
|
556
523
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
557
524
|
const texW = uintAsFloat(texWBits);
|
|
558
|
-
// Band transform: scale to map glyph-local em-coords to band indices
|
|
559
525
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
560
526
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
561
527
|
for (let vi = 0; vi < 4; vi++) {
|
|
@@ -570,23 +536,22 @@ function packSlugData(shapes, options) {
|
|
|
570
536
|
vertices[base + 5] = emCorners[vi][1];
|
|
571
537
|
vertices[base + 6] = texZ;
|
|
572
538
|
vertices[base + 7] = texW;
|
|
573
|
-
// jac: identity
|
|
539
|
+
// jac: identity (em-space is a translation of object-space)
|
|
574
540
|
vertices[base + 8] = 1.0;
|
|
575
541
|
vertices[base + 9] = 0.0;
|
|
576
542
|
vertices[base + 10] = 0.0;
|
|
577
543
|
vertices[base + 11] = 1.0;
|
|
578
|
-
// bnd: band scale (offset
|
|
544
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
579
545
|
vertices[base + 12] = bandScaleX;
|
|
580
546
|
vertices[base + 13] = bandScaleY;
|
|
581
547
|
vertices[base + 14] = 0;
|
|
582
548
|
vertices[base + 15] = 0;
|
|
583
|
-
// col
|
|
549
|
+
// col
|
|
584
550
|
vertices[base + 16] = 1.0;
|
|
585
551
|
vertices[base + 17] = 1.0;
|
|
586
552
|
vertices[base + 18] = 1.0;
|
|
587
553
|
vertices[base + 19] = 1.0;
|
|
588
554
|
}
|
|
589
|
-
// Indices: two triangles per quad
|
|
590
555
|
const vBase = si * 4;
|
|
591
556
|
const iBase = si * 6;
|
|
592
557
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -795,7 +760,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
795
760
|
}
|
|
796
761
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
797
762
|
}
|
|
798
|
-
// Public API
|
|
799
763
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
800
764
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
801
765
|
let cachedQuery = null;
|
package/dist/vector/index.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ interface VectorTextResult {
|
|
|
69
69
|
query(options: TextQueryOptions): TextRange[];
|
|
70
70
|
getLoadedFont(): LoadedFont | undefined;
|
|
71
71
|
measureTextWidth(text: string, letterSpacing?: number): number;
|
|
72
|
+
setColor(r: number, g: number, b: number): void;
|
|
72
73
|
update(options: Partial<TextOptions>): Promise<VectorTextResult>;
|
|
73
74
|
dispose(): void;
|
|
74
75
|
}
|
package/dist/vector/index2.cjs
CHANGED
|
@@ -22,8 +22,6 @@ function _interopNamespaceDefault(e) {
|
|
|
22
22
|
|
|
23
23
|
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
24
24
|
|
|
25
|
-
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
26
|
-
// separate per-attribute arrays for GPU attribute binding
|
|
27
25
|
const FLOATS_PER_VERT = 20;
|
|
28
26
|
function unpackSlugVertices(gpuData) {
|
|
29
27
|
const vertCount = gpuData.shapeCount * 4;
|
|
@@ -45,7 +43,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
45
43
|
bandings[i * 4 + 1] = srcF[s + 13];
|
|
46
44
|
bandings[i * 4 + 2] = srcF[s + 14];
|
|
47
45
|
bandings[i * 4 + 3] = srcF[s + 15];
|
|
48
|
-
// Unpack glyph location and band metadata from bit-packed fields
|
|
49
46
|
const g0 = srcU[s + 6];
|
|
50
47
|
const g1 = srcU[s + 7];
|
|
51
48
|
glyphData[i * 4] = g0 & 0xFFFF;
|
|
@@ -57,7 +54,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
57
54
|
colors[i * 4 + 2] = srcF[s + 18];
|
|
58
55
|
colors[i * 4 + 3] = srcF[s + 19];
|
|
59
56
|
}
|
|
60
|
-
// Per-glyph center: average of quad corner positions
|
|
61
57
|
const glyphCenters = new Float32Array(vertCount * 3);
|
|
62
58
|
const glyphIndices = new Float32Array(vertCount);
|
|
63
59
|
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
@@ -82,23 +78,14 @@ function unpackSlugVertices(gpuData) {
|
|
|
82
78
|
|
|
83
79
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
84
80
|
|
|
85
|
-
// Slug shader source re-exports
|
|
86
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
87
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
88
81
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
89
82
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
90
83
|
|
|
91
|
-
// Slug
|
|
92
|
-
//
|
|
93
|
-
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
84
|
+
// Slug adapter using Three.js RawShaderMaterial (GLSL)
|
|
85
|
+
// Works with any Three.js renderer (WebGL or WebGPU via transpilation)
|
|
94
86
|
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// - Uses native Uint32 band texture (no float conversion)
|
|
98
|
-
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
99
|
-
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
100
|
-
//
|
|
101
|
-
// Requires peer dependency: three
|
|
87
|
+
// Supports GLSL animation injection and native Uint32 band textures
|
|
88
|
+
// No vertex dilation; may clip at sub-pixel edges under extreme zoom
|
|
102
89
|
// @ts-ignore - three is a peer dependency
|
|
103
90
|
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
104
91
|
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
@@ -149,13 +136,12 @@ function createSlugGLSLMesh(gpuData, options) {
|
|
|
149
136
|
geo.setAttribute('glyphCenter', new THREE__namespace.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
150
137
|
geo.setAttribute('glyphIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
151
138
|
geo.setIndex(new THREE__namespace.BufferAttribute(gpuData.indices, 1));
|
|
152
|
-
// Curve texture: RGBA32F
|
|
153
139
|
const curveTex = new THREE__namespace.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE__namespace.RGBAFormat, THREE__namespace.FloatType);
|
|
154
140
|
curveTex.minFilter = THREE__namespace.NearestFilter;
|
|
155
141
|
curveTex.magFilter = THREE__namespace.NearestFilter;
|
|
156
142
|
curveTex.generateMipmaps = false;
|
|
157
143
|
curveTex.needsUpdate = true;
|
|
158
|
-
//
|
|
144
|
+
// RGBA32UI (unlike TSL, GLSL supports integer textures natively)
|
|
159
145
|
const bandTex = new THREE__namespace.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
160
146
|
bandTex.format = THREE__namespace.RGBAIntegerFormat;
|
|
161
147
|
bandTex.type = THREE__namespace.UnsignedIntType;
|
|
@@ -249,6 +235,7 @@ async function wrapCoreResult(coreResult, options) {
|
|
|
249
235
|
glyphs: coreResult.glyphs,
|
|
250
236
|
planeBounds: coreResult.planeBounds,
|
|
251
237
|
query: (queryOptions) => coreResult.query(queryOptions),
|
|
238
|
+
setColor: (r, g, b) => slugMesh.setColor(r, g, b),
|
|
252
239
|
getLoadedFont: () => coreResult.getLoadedFont(),
|
|
253
240
|
measureTextWidth: (text, letterSpacing) => coreResult.measureTextWidth(text, letterSpacing),
|
|
254
241
|
update: async (newOptions) => {
|
package/dist/vector/index2.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Text as Text$1 } from './core/index.js';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
|
|
4
|
-
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
5
|
-
// separate per-attribute arrays for GPU attribute binding
|
|
6
4
|
const FLOATS_PER_VERT = 20;
|
|
7
5
|
function unpackSlugVertices(gpuData) {
|
|
8
6
|
const vertCount = gpuData.shapeCount * 4;
|
|
@@ -24,7 +22,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
24
22
|
bandings[i * 4 + 1] = srcF[s + 13];
|
|
25
23
|
bandings[i * 4 + 2] = srcF[s + 14];
|
|
26
24
|
bandings[i * 4 + 3] = srcF[s + 15];
|
|
27
|
-
// Unpack glyph location and band metadata from bit-packed fields
|
|
28
25
|
const g0 = srcU[s + 6];
|
|
29
26
|
const g1 = srcU[s + 7];
|
|
30
27
|
glyphData[i * 4] = g0 & 0xFFFF;
|
|
@@ -36,7 +33,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
36
33
|
colors[i * 4 + 2] = srcF[s + 18];
|
|
37
34
|
colors[i * 4 + 3] = srcF[s + 19];
|
|
38
35
|
}
|
|
39
|
-
// Per-glyph center: average of quad corner positions
|
|
40
36
|
const glyphCenters = new Float32Array(vertCount * 3);
|
|
41
37
|
const glyphIndices = new Float32Array(vertCount);
|
|
42
38
|
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
@@ -61,23 +57,14 @@ function unpackSlugVertices(gpuData) {
|
|
|
61
57
|
|
|
62
58
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
63
59
|
|
|
64
|
-
// Slug shader source re-exports
|
|
65
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
66
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
67
60
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
68
61
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
69
62
|
|
|
70
|
-
// Slug
|
|
71
|
-
//
|
|
72
|
-
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
63
|
+
// Slug adapter using Three.js RawShaderMaterial (GLSL)
|
|
64
|
+
// Works with any Three.js renderer (WebGL or WebGPU via transpilation)
|
|
73
65
|
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// - Uses native Uint32 band texture (no float conversion)
|
|
77
|
-
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
78
|
-
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
79
|
-
//
|
|
80
|
-
// Requires peer dependency: three
|
|
66
|
+
// Supports GLSL animation injection and native Uint32 band textures
|
|
67
|
+
// No vertex dilation; may clip at sub-pixel edges under extreme zoom
|
|
81
68
|
// @ts-ignore - three is a peer dependency
|
|
82
69
|
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
83
70
|
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
@@ -128,13 +115,12 @@ function createSlugGLSLMesh(gpuData, options) {
|
|
|
128
115
|
geo.setAttribute('glyphCenter', new THREE.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
129
116
|
geo.setAttribute('glyphIndex', new THREE.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
130
117
|
geo.setIndex(new THREE.BufferAttribute(gpuData.indices, 1));
|
|
131
|
-
// Curve texture: RGBA32F
|
|
132
118
|
const curveTex = new THREE.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE.RGBAFormat, THREE.FloatType);
|
|
133
119
|
curveTex.minFilter = THREE.NearestFilter;
|
|
134
120
|
curveTex.magFilter = THREE.NearestFilter;
|
|
135
121
|
curveTex.generateMipmaps = false;
|
|
136
122
|
curveTex.needsUpdate = true;
|
|
137
|
-
//
|
|
123
|
+
// RGBA32UI (unlike TSL, GLSL supports integer textures natively)
|
|
138
124
|
const bandTex = new THREE.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
139
125
|
bandTex.format = THREE.RGBAIntegerFormat;
|
|
140
126
|
bandTex.type = THREE.UnsignedIntType;
|
|
@@ -228,6 +214,7 @@ async function wrapCoreResult(coreResult, options) {
|
|
|
228
214
|
glyphs: coreResult.glyphs,
|
|
229
215
|
planeBounds: coreResult.planeBounds,
|
|
230
216
|
query: (queryOptions) => coreResult.query(queryOptions),
|
|
217
|
+
setColor: (r, g, b) => slugMesh.setColor(r, g, b),
|
|
231
218
|
getLoadedFont: () => coreResult.getLoadedFont(),
|
|
232
219
|
measureTextWidth: (text, letterSpacing) => coreResult.measureTextWidth(text, letterSpacing),
|
|
233
220
|
update: async (newOptions) => {
|
package/dist/vector/react.d.ts
CHANGED
|
@@ -208,7 +208,6 @@ interface TextOptions {
|
|
|
208
208
|
geometryOptimization?: GeometryOptimizationOptions;
|
|
209
209
|
layout?: LayoutOptions;
|
|
210
210
|
color?: [number, number, number] | ColorOptions;
|
|
211
|
-
/** Enable rotated RGSS-4 adaptive supersampling (4 samples per pixel). Takes effect when the GLSL rendering path is active. */
|
|
212
211
|
adaptiveSupersampling?: boolean;
|
|
213
212
|
}
|
|
214
213
|
interface HyphenationPatternsMap {
|
package/dist/vector/slugTSL.cjs
CHANGED
|
@@ -25,20 +25,11 @@ function _interopNamespaceDefault(e) {
|
|
|
25
25
|
|
|
26
26
|
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
27
27
|
|
|
28
|
-
// Slug
|
|
28
|
+
// Slug adapter using Three.js TSL (node materials)
|
|
29
|
+
// Works on both WebGPU and WebGL backends (r170+)
|
|
29
30
|
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// band-accelerated ray-curve intersection
|
|
33
|
-
//
|
|
34
|
-
// Works on both WebGPU and WebGL backends via Three.js TSL
|
|
35
|
-
//
|
|
36
|
-
// Compared to the raw GLSL/WGSL standalone renderers, this adapter
|
|
37
|
-
// trades some features for Three.js integration:
|
|
38
|
-
// - No vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
39
|
-
// - No adaptive supersampling (single-sample per fragment)
|
|
40
|
-
//
|
|
41
|
-
// Requires peer dependencies: three, three/tsl
|
|
31
|
+
// No vertex dilation or adaptive supersampling (unlike the raw
|
|
32
|
+
// GLSL/WGSL renderers); may clip at sub-pixel edges under extreme zoom
|
|
42
33
|
// @ts-ignore - three is a peer dependency
|
|
43
34
|
const LOG_BAND_TEX_W = 12;
|
|
44
35
|
const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
|
|
@@ -100,10 +91,6 @@ const calcBandLoc = tsl.Fn(([glyphX, glyphY, offset]) => {
|
|
|
100
91
|
bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
|
|
101
92
|
return tsl.ivec2(bx, by);
|
|
102
93
|
});
|
|
103
|
-
// Create a Three.js Mesh from SlugGPUData using TSL node materials.
|
|
104
|
-
// Returns a single transparent mesh suitable for any Three.js scene.
|
|
105
|
-
// The Slug algorithm evaluates per-fragment coverage analytically,
|
|
106
|
-
// so no stencil buffer or multi-pass rendering is required
|
|
107
94
|
function createSlugTSLMesh(gpuData, color) {
|
|
108
95
|
const attrs = index.unpackSlugVertices(gpuData);
|
|
109
96
|
const geo = new THREE__namespace.BufferGeometry();
|
|
@@ -130,16 +117,11 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
130
117
|
bandTex.magFilter = THREE__namespace.NearestFilter;
|
|
131
118
|
bandTex.generateMipmaps = false;
|
|
132
119
|
bandTex.needsUpdate = true;
|
|
133
|
-
// Varyings: vertex attributes interpolated to fragment stage
|
|
134
120
|
const vTexcoord = tsl.varying(tsl.attribute('slugTexcoord', 'vec2'), 'v_texcoord');
|
|
135
121
|
const vBanding = tsl.varying(tsl.attribute('slugBanding', 'vec4'), 'v_banding');
|
|
136
122
|
const vGlyph = tsl.varying(tsl.attribute('slugGlyph', 'vec4'), 'v_glyph');
|
|
137
123
|
const vColor = tsl.varying(tsl.attribute('slugColor', 'vec4'), 'v_color');
|
|
138
|
-
// Color uniform (allows dynamic color updates)
|
|
139
124
|
const textColor = tsl.uniform(new THREE__namespace.Color(color?.r ?? 1, color?.g ?? 1, color?.b ?? 1));
|
|
140
|
-
// Main per-fragment evaluation: SlugRenderSingle ported to TSL
|
|
141
|
-
// Evaluates horizontal and vertical band loops to compute
|
|
142
|
-
// analytic winding-number coverage
|
|
143
125
|
const slugRenderSingle = tsl.Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
|
|
144
126
|
const pixelsPerEm = tsl.vec2(tsl.float(1).div(emsPerPixel.x), tsl.float(1).div(emsPerPixel.y));
|
|
145
127
|
const glyphLocX = glyphData.x.toInt();
|
|
@@ -148,7 +130,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
148
130
|
const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
|
|
149
131
|
const bandIdxX = tsl.max(tsl.min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), tsl.int(0));
|
|
150
132
|
const bandIdxY = tsl.max(tsl.min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), tsl.int(0));
|
|
151
|
-
// Horizontal band loop
|
|
152
133
|
const xcov = tsl.float(0).toVar();
|
|
153
134
|
const xwgt = tsl.float(0).toVar();
|
|
154
135
|
const hbandData = tsl.textureLoad(bandTex, tsl.ivec2(glyphLocX.add(bandIdxY), glyphLocY));
|
|
@@ -181,7 +162,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
181
162
|
});
|
|
182
163
|
hIdx.addAssign(1);
|
|
183
164
|
});
|
|
184
|
-
// Vertical band loop
|
|
185
165
|
const ycov = tsl.float(0).toVar();
|
|
186
166
|
const ywgt = tsl.float(0).toVar();
|
|
187
167
|
const vbandOffset = bandMaxY.add(1).add(bandIdxX);
|
|
@@ -215,17 +195,14 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
215
195
|
});
|
|
216
196
|
vIdx.addAssign(1);
|
|
217
197
|
});
|
|
218
|
-
// CalcCoverage (nonzero winding rule)
|
|
219
198
|
const coverage = tsl.max(tsl.abs(xcov.mul(xwgt).add(ycov.mul(ywgt))).div(tsl.max(xwgt.add(ywgt), tsl.float(1.0 / 65536.0))), tsl.min(tsl.abs(xcov), tsl.abs(ycov)));
|
|
220
199
|
return tsl.clamp(coverage, 0, 1);
|
|
221
200
|
});
|
|
222
|
-
// Top-level fragment node
|
|
223
201
|
const fragmentNode = tsl.Fn(() => {
|
|
224
202
|
const emsPerPixel = tsl.fwidth(vTexcoord);
|
|
225
203
|
const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
|
|
226
204
|
return tsl.vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
|
|
227
205
|
})();
|
|
228
|
-
// Material & mesh
|
|
229
206
|
const material = new webgpu.MeshBasicNodeMaterial();
|
|
230
207
|
material.fragmentNode = fragmentNode;
|
|
231
208
|
material.transparent = true;
|
package/dist/vector/slugTSL.js
CHANGED
|
@@ -4,20 +4,11 @@ import { Fn, select, uint, float, sqrt, max, If, abs, vec2, ivec2, varying, attr
|
|
|
4
4
|
import { u as unpackSlugVertices } from './index2.js';
|
|
5
5
|
import './core/index.js';
|
|
6
6
|
|
|
7
|
-
// Slug
|
|
7
|
+
// Slug adapter using Three.js TSL (node materials)
|
|
8
|
+
// Works on both WebGPU and WebGL backends (r170+)
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// band-accelerated ray-curve intersection
|
|
12
|
-
//
|
|
13
|
-
// Works on both WebGPU and WebGL backends via Three.js TSL
|
|
14
|
-
//
|
|
15
|
-
// Compared to the raw GLSL/WGSL standalone renderers, this adapter
|
|
16
|
-
// trades some features for Three.js integration:
|
|
17
|
-
// - No vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
18
|
-
// - No adaptive supersampling (single-sample per fragment)
|
|
19
|
-
//
|
|
20
|
-
// Requires peer dependencies: three, three/tsl
|
|
10
|
+
// No vertex dilation or adaptive supersampling (unlike the raw
|
|
11
|
+
// GLSL/WGSL renderers); may clip at sub-pixel edges under extreme zoom
|
|
21
12
|
// @ts-ignore - three is a peer dependency
|
|
22
13
|
const LOG_BAND_TEX_W = 12;
|
|
23
14
|
const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
|
|
@@ -79,10 +70,6 @@ const calcBandLoc = Fn(([glyphX, glyphY, offset]) => {
|
|
|
79
70
|
bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
|
|
80
71
|
return ivec2(bx, by);
|
|
81
72
|
});
|
|
82
|
-
// Create a Three.js Mesh from SlugGPUData using TSL node materials.
|
|
83
|
-
// Returns a single transparent mesh suitable for any Three.js scene.
|
|
84
|
-
// The Slug algorithm evaluates per-fragment coverage analytically,
|
|
85
|
-
// so no stencil buffer or multi-pass rendering is required
|
|
86
73
|
function createSlugTSLMesh(gpuData, color) {
|
|
87
74
|
const attrs = unpackSlugVertices(gpuData);
|
|
88
75
|
const geo = new THREE.BufferGeometry();
|
|
@@ -109,16 +96,11 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
109
96
|
bandTex.magFilter = THREE.NearestFilter;
|
|
110
97
|
bandTex.generateMipmaps = false;
|
|
111
98
|
bandTex.needsUpdate = true;
|
|
112
|
-
// Varyings: vertex attributes interpolated to fragment stage
|
|
113
99
|
const vTexcoord = varying(attribute('slugTexcoord', 'vec2'), 'v_texcoord');
|
|
114
100
|
const vBanding = varying(attribute('slugBanding', 'vec4'), 'v_banding');
|
|
115
101
|
const vGlyph = varying(attribute('slugGlyph', 'vec4'), 'v_glyph');
|
|
116
102
|
const vColor = varying(attribute('slugColor', 'vec4'), 'v_color');
|
|
117
|
-
// Color uniform (allows dynamic color updates)
|
|
118
103
|
const textColor = uniform(new THREE.Color(color?.r ?? 1, color?.g ?? 1, color?.b ?? 1));
|
|
119
|
-
// Main per-fragment evaluation: SlugRenderSingle ported to TSL
|
|
120
|
-
// Evaluates horizontal and vertical band loops to compute
|
|
121
|
-
// analytic winding-number coverage
|
|
122
104
|
const slugRenderSingle = Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
|
|
123
105
|
const pixelsPerEm = vec2(float(1).div(emsPerPixel.x), float(1).div(emsPerPixel.y));
|
|
124
106
|
const glyphLocX = glyphData.x.toInt();
|
|
@@ -127,7 +109,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
127
109
|
const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
|
|
128
110
|
const bandIdxX = max(min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), int(0));
|
|
129
111
|
const bandIdxY = max(min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), int(0));
|
|
130
|
-
// Horizontal band loop
|
|
131
112
|
const xcov = float(0).toVar();
|
|
132
113
|
const xwgt = float(0).toVar();
|
|
133
114
|
const hbandData = textureLoad(bandTex, ivec2(glyphLocX.add(bandIdxY), glyphLocY));
|
|
@@ -160,7 +141,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
160
141
|
});
|
|
161
142
|
hIdx.addAssign(1);
|
|
162
143
|
});
|
|
163
|
-
// Vertical band loop
|
|
164
144
|
const ycov = float(0).toVar();
|
|
165
145
|
const ywgt = float(0).toVar();
|
|
166
146
|
const vbandOffset = bandMaxY.add(1).add(bandIdxX);
|
|
@@ -194,17 +174,14 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
194
174
|
});
|
|
195
175
|
vIdx.addAssign(1);
|
|
196
176
|
});
|
|
197
|
-
// CalcCoverage (nonzero winding rule)
|
|
198
177
|
const coverage = max(abs(xcov.mul(xwgt).add(ycov.mul(ywgt))).div(max(xwgt.add(ywgt), float(1.0 / 65536.0))), min(abs(xcov), abs(ycov)));
|
|
199
178
|
return clamp(coverage, 0, 1);
|
|
200
179
|
});
|
|
201
|
-
// Top-level fragment node
|
|
202
180
|
const fragmentNode = Fn(() => {
|
|
203
181
|
const emsPerPixel = fwidth(vTexcoord);
|
|
204
182
|
const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
|
|
205
183
|
return vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
|
|
206
184
|
})();
|
|
207
|
-
// Material & mesh
|
|
208
185
|
const material = new MeshBasicNodeMaterial();
|
|
209
186
|
material.fragmentNode = fragmentNode;
|
|
210
187
|
material.transparent = true;
|