three-text 0.6.0 → 0.6.2
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.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/vector/core/index.cjs +45 -77
- package/dist/vector/core/index.js +45 -77
- package/dist/vector/index2.cjs +5 -19
- package/dist/vector/index2.js +5 -19
- 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;
|
|
@@ -281,28 +278,33 @@ function packSlugData(shapes, options) {
|
|
|
281
278
|
let curveY = 0;
|
|
282
279
|
for (const shape of shapes) {
|
|
283
280
|
const entries = [];
|
|
281
|
+
const [ox, oy] = shape.bounds;
|
|
284
282
|
for (const curve of shape.curves) {
|
|
285
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
286
283
|
if (curveX >= TEX_WIDTH - 1) {
|
|
287
284
|
curveX = 0;
|
|
288
285
|
curveY++;
|
|
289
286
|
}
|
|
287
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
288
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
289
|
+
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
290
|
+
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
291
|
+
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
290
292
|
const base = (curveY * TEX_WIDTH + curveX) * 4;
|
|
291
|
-
curveData[base + 0] =
|
|
292
|
-
curveData[base + 1] =
|
|
293
|
-
curveData[base + 2] =
|
|
294
|
-
curveData[base + 3] =
|
|
293
|
+
curveData[base + 0] = lp1x;
|
|
294
|
+
curveData[base + 1] = lp1y;
|
|
295
|
+
curveData[base + 2] = lp2x;
|
|
296
|
+
curveData[base + 3] = lp2y;
|
|
295
297
|
const base2 = base + 4;
|
|
296
|
-
curveData[base2 + 0] =
|
|
297
|
-
curveData[base2 + 1] =
|
|
298
|
-
const minX = Math.min(
|
|
299
|
-
const minY = Math.min(
|
|
300
|
-
const maxX = Math.max(
|
|
301
|
-
const maxY = Math.max(
|
|
298
|
+
curveData[base2 + 0] = lp3x;
|
|
299
|
+
curveData[base2 + 1] = lp3y;
|
|
300
|
+
const minX = Math.min(lp1x, lp2x, lp3x);
|
|
301
|
+
const minY = Math.min(lp1y, lp2y, lp3y);
|
|
302
|
+
const maxX = Math.max(lp1x, lp2x, lp3x);
|
|
303
|
+
const maxY = Math.max(lp1y, lp2y, lp3y);
|
|
302
304
|
entries.push({
|
|
303
|
-
p1x:
|
|
304
|
-
p2x:
|
|
305
|
-
p3x:
|
|
305
|
+
p1x: lp1x, p1y: lp1y,
|
|
306
|
+
p2x: lp2x, p2y: lp2y,
|
|
307
|
+
p3x: lp3x, p3y: lp3y,
|
|
306
308
|
minX, minY, maxX, maxY,
|
|
307
309
|
curveTexX: curveX,
|
|
308
310
|
curveTexY: curveY
|
|
@@ -312,12 +314,10 @@ function packSlugData(shapes, options) {
|
|
|
312
314
|
allCurves.push(entries);
|
|
313
315
|
}
|
|
314
316
|
const actualCurveTexHeight = curveY + 1;
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
// [
|
|
318
|
-
// [hBandMax+
|
|
319
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
320
|
-
// 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
|
|
321
321
|
const shapeBandData = [];
|
|
322
322
|
let totalBandTexels = 0;
|
|
323
323
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -337,21 +337,19 @@ function packSlugData(shapes, options) {
|
|
|
337
337
|
const vBandCount = Math.min(bandCount, 255);
|
|
338
338
|
const bandMaxY = hBandCount - 1;
|
|
339
339
|
const bandMaxX = vBandCount - 1;
|
|
340
|
-
//
|
|
340
|
+
// Horizontal bands (partition y-axis)
|
|
341
341
|
const hBands = [];
|
|
342
342
|
const hLists = [];
|
|
343
343
|
const bandH = h / hBandCount;
|
|
344
344
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
345
|
-
const bandMinY =
|
|
345
|
+
const bandMinY = bi * bandH;
|
|
346
346
|
const bandMaxYCoord = bandMinY + bandH;
|
|
347
|
-
// Collect curves whose y-range overlaps this band
|
|
348
347
|
const list = [];
|
|
349
348
|
for (const c of curves) {
|
|
350
349
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
351
350
|
list.push({ curve: c, sortKey: c.maxX });
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
|
-
// Sort by descending max-x for early exit
|
|
355
353
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
356
354
|
const flatList = [];
|
|
357
355
|
for (const item of list) {
|
|
@@ -360,12 +358,12 @@ function packSlugData(shapes, options) {
|
|
|
360
358
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
361
359
|
hLists.push(flatList);
|
|
362
360
|
}
|
|
363
|
-
//
|
|
361
|
+
// Vertical bands (partition x-axis)
|
|
364
362
|
const vBands = [];
|
|
365
363
|
const vLists = [];
|
|
366
364
|
const bandW = w / vBandCount;
|
|
367
365
|
for (let bi = 0; bi < vBandCount; bi++) {
|
|
368
|
-
const bandMinX =
|
|
366
|
+
const bandMinX = bi * bandW;
|
|
369
367
|
const bandMaxXCoord = bandMinX + bandW;
|
|
370
368
|
const list = [];
|
|
371
369
|
for (const c of curves) {
|
|
@@ -373,7 +371,6 @@ function packSlugData(shapes, options) {
|
|
|
373
371
|
list.push({ curve: c, sortKey: c.maxY });
|
|
374
372
|
}
|
|
375
373
|
}
|
|
376
|
-
// Sort by descending max-y for early exit
|
|
377
374
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
378
375
|
const flatList = [];
|
|
379
376
|
for (const item of list) {
|
|
@@ -382,7 +379,6 @@ function packSlugData(shapes, options) {
|
|
|
382
379
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
383
380
|
vLists.push(flatList);
|
|
384
381
|
}
|
|
385
|
-
// Total texels for this shape: band headers + curve lists
|
|
386
382
|
const headerTexels = hBandCount + vBandCount;
|
|
387
383
|
let listTexels = 0;
|
|
388
384
|
for (const l of hLists)
|
|
@@ -396,10 +392,8 @@ function packSlugData(shapes, options) {
|
|
|
396
392
|
});
|
|
397
393
|
totalBandTexels += total;
|
|
398
394
|
}
|
|
399
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
400
395
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
401
396
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
402
|
-
// Pack band data per shape
|
|
403
397
|
let bandX = 0;
|
|
404
398
|
let bandY = 0;
|
|
405
399
|
const glyphLocs = [];
|
|
@@ -409,11 +403,8 @@ function packSlugData(shapes, options) {
|
|
|
409
403
|
glyphLocs.push({ x: 0, y: 0 });
|
|
410
404
|
continue;
|
|
411
405
|
}
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
415
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
416
|
-
// 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
|
|
417
408
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
418
409
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
419
410
|
bandX = 0;
|
|
@@ -422,11 +413,8 @@ function packSlugData(shapes, options) {
|
|
|
422
413
|
const glyphLocX = bandX;
|
|
423
414
|
const glyphLocY = bandY;
|
|
424
415
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
425
|
-
// Curve lists start after all headers
|
|
426
416
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
427
|
-
//
|
|
428
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
429
|
-
// 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
|
|
430
418
|
const ensureListFits = (listLen) => {
|
|
431
419
|
if (listLen === 0)
|
|
432
420
|
return;
|
|
@@ -435,21 +423,18 @@ function packSlugData(shapes, options) {
|
|
|
435
423
|
listStartOffset += (TEX_WIDTH - startX);
|
|
436
424
|
}
|
|
437
425
|
};
|
|
438
|
-
// Assign list offsets for h-bands
|
|
439
426
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
440
427
|
const listLen = sd.hLists[bi].length / 2;
|
|
441
428
|
ensureListFits(listLen);
|
|
442
429
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
443
430
|
listStartOffset += listLen;
|
|
444
431
|
}
|
|
445
|
-
// Assign list offsets for v-bands
|
|
446
432
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
447
433
|
const listLen = sd.vLists[bi].length / 2;
|
|
448
434
|
ensureListFits(listLen);
|
|
449
435
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
450
436
|
listStartOffset += listLen;
|
|
451
437
|
}
|
|
452
|
-
// Write h-band headers
|
|
453
438
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
454
439
|
const tx = glyphLocX + bi;
|
|
455
440
|
const ty = glyphLocY;
|
|
@@ -459,7 +444,6 @@ function packSlugData(shapes, options) {
|
|
|
459
444
|
bandData[idx + 2] = 0;
|
|
460
445
|
bandData[idx + 3] = 0;
|
|
461
446
|
}
|
|
462
|
-
// Write v-band headers (after h-bands)
|
|
463
447
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
464
448
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
465
449
|
const tx = vBandStart + bi;
|
|
@@ -470,9 +454,7 @@ function packSlugData(shapes, options) {
|
|
|
470
454
|
bandData[idx + 2] = 0;
|
|
471
455
|
bandData[idx + 3] = 0;
|
|
472
456
|
}
|
|
473
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
474
457
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
475
|
-
// Write h-band curve lists
|
|
476
458
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
477
459
|
const list = sd.hLists[bi];
|
|
478
460
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -481,13 +463,12 @@ function packSlugData(shapes, options) {
|
|
|
481
463
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
482
464
|
bx &= texWidthMask;
|
|
483
465
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
484
|
-
bandData[idx + 0] = list[ci];
|
|
485
|
-
bandData[idx + 1] = list[ci + 1];
|
|
466
|
+
bandData[idx + 0] = list[ci];
|
|
467
|
+
bandData[idx + 1] = list[ci + 1];
|
|
486
468
|
bandData[idx + 2] = 0;
|
|
487
469
|
bandData[idx + 3] = 0;
|
|
488
470
|
}
|
|
489
471
|
}
|
|
490
|
-
// Write v-band curve lists
|
|
491
472
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
492
473
|
const list = sd.vLists[bi];
|
|
493
474
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -502,24 +483,20 @@ function packSlugData(shapes, options) {
|
|
|
502
483
|
bandData[idx + 3] = 0;
|
|
503
484
|
}
|
|
504
485
|
}
|
|
505
|
-
// Advance band cursor past this shape's data
|
|
506
486
|
let endBx = glyphLocX + listStartOffset;
|
|
507
487
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
508
488
|
bandX = endBx & texWidthMask;
|
|
509
489
|
}
|
|
510
490
|
const actualBandTexHeight = bandY + 1;
|
|
511
|
-
// Build vertex attributes
|
|
512
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
513
491
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
514
492
|
const VERTS_PER_SHAPE = 4;
|
|
515
493
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
516
494
|
const indices = new Uint16Array(shapes.length * 6);
|
|
517
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
518
495
|
const cornerNormals = [
|
|
519
|
-
[-1, -1],
|
|
520
|
-
[1, -1],
|
|
521
|
-
[1, 1],
|
|
522
|
-
[-1, 1],
|
|
496
|
+
[-1, -1],
|
|
497
|
+
[1, -1],
|
|
498
|
+
[1, 1],
|
|
499
|
+
[-1, 1],
|
|
523
500
|
];
|
|
524
501
|
for (let si = 0; si < shapes.length; si++) {
|
|
525
502
|
const shape = shapes[si];
|
|
@@ -528,32 +505,25 @@ function packSlugData(shapes, options) {
|
|
|
528
505
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
529
506
|
const w = bMaxX - bMinX;
|
|
530
507
|
const h = bMaxY - bMinY;
|
|
531
|
-
// Corner positions in object-space
|
|
532
508
|
const corners = [
|
|
533
509
|
[bMinX, bMinY],
|
|
534
510
|
[bMaxX, bMinY],
|
|
535
511
|
[bMaxX, bMaxY],
|
|
536
512
|
[bMinX, bMaxY],
|
|
537
513
|
];
|
|
538
|
-
// Em-space sample coords at corners (same as object-space for 1:1 mapping)
|
|
539
514
|
const emCorners = [
|
|
540
|
-
[
|
|
541
|
-
[
|
|
542
|
-
[
|
|
543
|
-
[
|
|
515
|
+
[0, 0],
|
|
516
|
+
[w, 0],
|
|
517
|
+
[w, h],
|
|
518
|
+
[0, h],
|
|
544
519
|
];
|
|
545
|
-
// Pack tex.z: glyph location in band texture
|
|
546
520
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
547
|
-
// Pack tex.w: band max + flags
|
|
548
521
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
549
522
|
if (evenOdd)
|
|
550
523
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
551
524
|
const texW = uintAsFloat(texWBits);
|
|
552
|
-
// Band transform: scale and offset to map em-coords to band indices
|
|
553
525
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
554
526
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
555
|
-
const bandOffsetX = -bMinX * bandScaleX;
|
|
556
|
-
const bandOffsetY = -bMinY * bandScaleY;
|
|
557
527
|
for (let vi = 0; vi < 4; vi++) {
|
|
558
528
|
const base = (si * 4 + vi) * FLOATS_PER_VERTEX;
|
|
559
529
|
// pos: .xy = position, .zw = normal
|
|
@@ -566,23 +536,22 @@ function packSlugData(shapes, options) {
|
|
|
566
536
|
vertices[base + 5] = emCorners[vi][1];
|
|
567
537
|
vertices[base + 6] = texZ;
|
|
568
538
|
vertices[base + 7] = texW;
|
|
569
|
-
// jac: identity
|
|
539
|
+
// jac: identity (em-space is a translation of object-space)
|
|
570
540
|
vertices[base + 8] = 1.0;
|
|
571
541
|
vertices[base + 9] = 0.0;
|
|
572
542
|
vertices[base + 10] = 0.0;
|
|
573
543
|
vertices[base + 11] = 1.0;
|
|
574
|
-
// bnd: band scale
|
|
544
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
575
545
|
vertices[base + 12] = bandScaleX;
|
|
576
546
|
vertices[base + 13] = bandScaleY;
|
|
577
|
-
vertices[base + 14] =
|
|
578
|
-
vertices[base + 15] =
|
|
579
|
-
// col
|
|
547
|
+
vertices[base + 14] = 0;
|
|
548
|
+
vertices[base + 15] = 0;
|
|
549
|
+
// col
|
|
580
550
|
vertices[base + 16] = 1.0;
|
|
581
551
|
vertices[base + 17] = 1.0;
|
|
582
552
|
vertices[base + 18] = 1.0;
|
|
583
553
|
vertices[base + 19] = 1.0;
|
|
584
554
|
}
|
|
585
|
-
// Indices: two triangles per quad
|
|
586
555
|
const vBase = si * 4;
|
|
587
556
|
const iBase = si * 6;
|
|
588
557
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -791,7 +760,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
791
760
|
}
|
|
792
761
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
793
762
|
}
|
|
794
|
-
// Public API
|
|
795
763
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
796
764
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
797
765
|
let cachedQuery = null;
|
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;
|
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;
|
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;
|