@multiplekex/shallot 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/extras/arrows/index.ts +3 -3
  3. package/src/extras/caustic.ts +37 -0
  4. package/src/extras/gradient/index.ts +63 -69
  5. package/src/extras/index.ts +3 -0
  6. package/src/extras/lines/index.ts +3 -3
  7. package/src/extras/skylab/index.ts +314 -0
  8. package/src/extras/text/font.ts +69 -14
  9. package/src/extras/text/index.ts +15 -7
  10. package/src/extras/text/sdf.ts +13 -2
  11. package/src/extras/water.ts +64 -0
  12. package/src/standard/defaults.ts +2 -0
  13. package/src/standard/index.ts +2 -0
  14. package/src/standard/raster/index.ts +517 -0
  15. package/src/standard/{render → raytracing}/bvh/blas.ts +3 -3
  16. package/src/standard/{render → raytracing}/bvh/tlas.ts +3 -0
  17. package/src/standard/{render → raytracing}/depth.ts +9 -9
  18. package/src/standard/raytracing/index.ts +380 -0
  19. package/src/standard/{render → raytracing}/instance.ts +3 -0
  20. package/src/standard/raytracing/shaders.ts +815 -0
  21. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  22. package/src/standard/render/camera.ts +88 -80
  23. package/src/standard/render/index.ts +68 -208
  24. package/src/standard/render/indirect.ts +9 -10
  25. package/src/standard/render/mesh/index.ts +35 -166
  26. package/src/standard/render/overlay.ts +4 -4
  27. package/src/standard/render/pass.ts +1 -1
  28. package/src/standard/render/postprocess.ts +75 -50
  29. package/src/standard/render/scene.ts +28 -16
  30. package/src/standard/render/surface/compile.ts +6 -8
  31. package/src/standard/render/surface/noise.ts +15 -2
  32. package/src/standard/render/surface/shaders.ts +257 -0
  33. package/src/standard/render/surface/structs.ts +13 -6
  34. package/src/standard/render/forward/index.ts +0 -259
  35. package/src/standard/render/forward/raster.ts +0 -228
  36. package/src/standard/render/shaders.ts +0 -484
  37. package/src/standard/render/surface/wgsl.ts +0 -573
  38. /package/src/standard/{render → raytracing}/bvh/radix.ts +0 -0
  39. /package/src/standard/{render → raytracing}/bvh/structs.ts +0 -0
  40. /package/src/standard/{render → raytracing}/bvh/traverse.ts +0 -0
  41. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
  42. /package/src/standard/{render → raytracing}/ray.ts +0 -0
@@ -82,7 +82,10 @@ function parseHead(r: Reader, table: TableEntry): { unitsPerEm: number; indexToL
82
82
  return { unitsPerEm, indexToLocFormat };
83
83
  }
84
84
 
85
- function parseHhea(r: Reader, table: TableEntry): { ascender: number; descender: number; lineGap: number; numHMetrics: number } {
85
+ function parseHhea(
86
+ r: Reader,
87
+ table: TableEntry
88
+ ): { ascender: number; descender: number; lineGap: number; numHMetrics: number } {
86
89
  seek(r, table.offset + 4);
87
90
  const ascender = i16(r);
88
91
  const descender = i16(r);
@@ -92,7 +95,12 @@ function parseHhea(r: Reader, table: TableEntry): { ascender: number; descender:
92
95
  return { ascender, descender, lineGap, numHMetrics };
93
96
  }
94
97
 
95
- function parseHmtx(r: Reader, table: TableEntry, numHMetrics: number, numGlyphs: number): { advances: Uint16Array } {
98
+ function parseHmtx(
99
+ r: Reader,
100
+ table: TableEntry,
101
+ numHMetrics: number,
102
+ numGlyphs: number
103
+ ): { advances: Uint16Array } {
96
104
  const advances = new Uint16Array(numGlyphs);
97
105
  seek(r, table.offset);
98
106
 
@@ -114,7 +122,12 @@ function parseMaxp(r: Reader, table: TableEntry): number {
114
122
  return u16(r);
115
123
  }
116
124
 
117
- function parseLoca(r: Reader, table: TableEntry, numGlyphs: number, indexToLocFormat: number): Uint32Array {
125
+ function parseLoca(
126
+ r: Reader,
127
+ table: TableEntry,
128
+ numGlyphs: number,
129
+ indexToLocFormat: number
130
+ ): Uint32Array {
118
131
  const offsets = new Uint32Array(numGlyphs + 1);
119
132
  seek(r, table.offset);
120
133
 
@@ -210,7 +223,8 @@ function parseCmap(r: Reader, table: TableEntry): Map<number, number> {
210
223
  if (rangeOffset === 0) {
211
224
  glyphId = (c + delta) & 0xffff;
212
225
  } else {
213
- const glyphIdOffset = idRangeOffsetPos + i * 2 + rangeOffset + (c - start) * 2;
226
+ const glyphIdOffset =
227
+ idRangeOffsetPos + i * 2 + rangeOffset + (c - start) * 2;
214
228
  seek(r, glyphIdOffset);
215
229
  glyphId = u16(r);
216
230
  if (glyphId !== 0) {
@@ -291,7 +305,12 @@ const REPEAT = 8;
291
305
  const X_SAME = 16;
292
306
  const Y_SAME = 32;
293
307
 
294
- function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: number): { path: string; bounds: [number, number, number, number] } | null {
308
+ function parseGlyph(
309
+ r: Reader,
310
+ glyfOffset: number,
311
+ loca: Uint32Array,
312
+ glyphId: number
313
+ ): { path: string; bounds: [number, number, number, number] } | null {
295
314
  const start = loca[glyphId];
296
315
  const end = loca[glyphId + 1];
297
316
  if (start === end) return null;
@@ -372,7 +391,11 @@ function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: n
372
391
  while (firstOn < points.length && !points[firstOn].on) firstOn++;
373
392
 
374
393
  if (firstOn === points.length) {
375
- const mid = { x: (points[0].x + points[1].x) / 2, y: (points[0].y + points[1].y) / 2, on: true };
394
+ const mid = {
395
+ x: (points[0].x + points[1].x) / 2,
396
+ y: (points[0].y + points[1].y) / 2,
397
+ on: true,
398
+ };
376
399
  points.unshift(mid);
377
400
  firstOn = 0;
378
401
  }
@@ -412,17 +435,28 @@ function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: n
412
435
  return { path, bounds: [xMin, yMin, xMax, yMax] };
413
436
  }
414
437
 
415
- function parseCompositeGlyph(r: Reader, glyfOffset: number, loca: Uint32Array): { path: string; bounds: [number, number, number, number] } | null {
438
+ function parseCompositeGlyph(
439
+ r: Reader,
440
+ glyfOffset: number,
441
+ loca: Uint32Array
442
+ ): { path: string; bounds: [number, number, number, number] } | null {
416
443
  let path = "";
417
- let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
444
+ let xMin = Infinity,
445
+ yMin = Infinity,
446
+ xMax = -Infinity,
447
+ yMax = -Infinity;
418
448
  let hasMore = true;
419
449
 
420
450
  while (hasMore) {
421
451
  const flags = u16(r);
422
452
  const glyphIndex = u16(r);
423
453
 
424
- let dx = 0, dy = 0;
425
- let a = 1, b = 0, c = 0, d = 1;
454
+ let dx = 0,
455
+ dy = 0;
456
+ let a = 1,
457
+ b = 0,
458
+ c = 0,
459
+ d = 1;
426
460
 
427
461
  if (flags & 1) {
428
462
  dx = i16(r);
@@ -464,7 +498,15 @@ function parseCompositeGlyph(r: Reader, glyfOffset: number, loca: Uint32Array):
464
498
  return { path, bounds: [xMin, yMin, xMax, yMax] };
465
499
  }
466
500
 
467
- function transformPath(path: string, a: number, b: number, c: number, d: number, dx: number, dy: number): string {
501
+ function transformPath(
502
+ path: string,
503
+ a: number,
504
+ b: number,
505
+ c: number,
506
+ d: number,
507
+ dx: number,
508
+ dy: number
509
+ ): string {
468
510
  return path.replace(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g, (_, x, y) => {
469
511
  const nx = parseFloat(x) * a + parseFloat(y) * b + dx;
470
512
  const ny = parseFloat(x) * c + parseFloat(y) * d + dy;
@@ -485,7 +527,15 @@ export function parseFont(buffer: ArrayBuffer): Font {
485
527
  const glyfTable = tables.get("glyf");
486
528
  const kernTable = tables.get("kern");
487
529
 
488
- if (!headTable || !hheaTable || !hmtxTable || !maxpTable || !cmapTable || !locaTable || !glyfTable) {
530
+ if (
531
+ !headTable ||
532
+ !hheaTable ||
533
+ !hmtxTable ||
534
+ !maxpTable ||
535
+ !cmapTable ||
536
+ !locaTable ||
537
+ !glyfTable
538
+ ) {
489
539
  throw new Error("Missing required font tables");
490
540
  }
491
541
 
@@ -497,14 +547,19 @@ export function parseFont(buffer: ArrayBuffer): Font {
497
547
  const cmap = parseCmap(r, cmapTable);
498
548
  const kern = kernTable ? parseKern(r, kernTable) : new Map<number, number>();
499
549
 
500
- const glyphCache = new Map<number, { path: string; bounds: [number, number, number, number] } | null>();
550
+ const glyphCache = new Map<
551
+ number,
552
+ { path: string; bounds: [number, number, number, number] } | null
553
+ >();
501
554
  const glyfOffset = glyfTable.offset;
502
555
 
503
556
  function getGlyphId(char: string): number {
504
557
  return cmap.get(char.codePointAt(0) ?? 0) ?? 0;
505
558
  }
506
559
 
507
- function getGlyph(glyphId: number): { path: string; bounds: [number, number, number, number] } | null {
560
+ function getGlyph(
561
+ glyphId: number
562
+ ): { path: string; bounds: [number, number, number, number] } | null {
508
563
  if (glyphCache.has(glyphId)) return glyphCache.get(glyphId)!;
509
564
  const glyph = parseGlyph(r, glyfOffset, loca, glyphId);
510
565
  glyphCache.set(glyphId, glyph);
@@ -21,8 +21,8 @@ import {
21
21
  type Draw,
22
22
  type SharedPassContext,
23
23
  } from "../../standard/render";
24
- import { DEPTH_FORMAT } from "../../standard/render/scene";
25
- import { SCENE_STRUCT_WGSL } from "../../standard/render/shaders";
24
+ import { Z_FORMAT } from "../../standard/render/scene";
25
+ import { SCENE_STRUCT_WGSL } from "../../standard/render/surface/structs";
26
26
  import { Transform } from "../../standard/transforms";
27
27
 
28
28
  const MAX_GLYPHS = 50000;
@@ -366,7 +366,7 @@ function layoutText(text: string, atlas: GlyphAtlas, fontSize: number): LayoutRe
366
366
  if (!metrics) continue;
367
367
 
368
368
  if (prevChar) {
369
- cursorX += atlas.font.kerning(prevChar, char) / atlas.font.unitsPerEm * scale;
369
+ cursorX += (atlas.font.kerning(prevChar, char) / atlas.font.unitsPerEm) * scale;
370
370
  }
371
371
 
372
372
  const glyphW = metrics.glyphWidth * scale;
@@ -520,13 +520,18 @@ fn fs(input: VertexOutput) -> FragmentOutput {
520
520
  // Smoothstep with adaptive band
521
521
  let alpha = smoothstep(aaDist, -aaDist, signedDist);
522
522
 
523
- if alpha < 0.01 {
523
+ // Dilate mask by FXAA span (8 pixels) to prevent edge bleeding
524
+ let fxaaSpan = aaDist * 8.0;
525
+ let inMaskRegion = signedDist < fxaaSpan;
526
+
527
+ // Only discard if outside both visible region and mask region
528
+ if alpha < 0.01 && !inMaskRegion {
524
529
  discard;
525
530
  }
526
531
 
527
532
  var out: FragmentOutput;
528
533
  out.color = vec4(input.color.rgb, input.color.a * alpha);
529
- out.mask = select(0.0, 1.0, alpha > 0.01);
534
+ out.mask = select(0.0, 1.0, inMaskRegion);
530
535
  return out;
531
536
  }
532
537
  `;
@@ -574,7 +579,7 @@ function createTextPipeline(
574
579
  cullMode: "none",
575
580
  },
576
581
  depthStencil: {
577
- format: DEPTH_FORMAT,
582
+ format: Z_FORMAT,
578
583
  depthCompare: "less",
579
584
  depthWriteEnabled: false,
580
585
  },
@@ -664,6 +669,8 @@ interface PendingGlyph {
664
669
  a: number;
665
670
  }
666
671
 
672
+ let glyphsByFont: PendingGlyph[][] = [];
673
+
667
674
  const TextSystem: System = {
668
675
  group: "draw",
669
676
 
@@ -676,7 +683,8 @@ const TextSystem: System = {
676
683
  const { atlases, staging, ranges } = text;
677
684
  const stagingU32 = new Uint32Array(staging.buffer);
678
685
 
679
- const glyphsByFont: PendingGlyph[][] = atlases.map(() => []);
686
+ while (glyphsByFont.length < atlases.length) glyphsByFont.push([]);
687
+ for (let i = 0; i < atlases.length; i++) glyphsByFont[i].length = 0;
680
688
 
681
689
  for (const eid of state.query([Text, Transform])) {
682
690
  if (!Text.visible[eid]) continue;
@@ -340,14 +340,25 @@ export class SDFGenerator {
340
340
  const segments = segmentPath(path, this.curveSubdivisions);
341
341
  if (segments.length === 0) return;
342
342
  if (segments.length > this.maxSegments) {
343
- console.warn(`Too many segments (${segments.length}), truncating to ${this.maxSegments}`);
343
+ console.warn(
344
+ `Too many segments (${segments.length}), truncating to ${this.maxSegments}`
345
+ );
344
346
  segments.length = this.maxSegments;
345
347
  }
346
348
 
347
349
  const [xMin, yMin, xMax, yMax] = bounds;
348
350
  const maxDist = Math.max(xMax - xMin, yMax - yMin) / 2;
349
351
 
350
- const uniformData = new Float32Array([xMin, yMin, xMax, yMax, maxDist, this.exponent, 0, 0]);
352
+ const uniformData = new Float32Array([
353
+ xMin,
354
+ yMin,
355
+ xMax,
356
+ yMax,
357
+ maxDist,
358
+ this.exponent,
359
+ 0,
360
+ 0,
361
+ ]);
351
362
  this.device.queue.writeBuffer(this.uniformBuffer, 0, uniformData);
352
363
 
353
364
  const segmentData = new Float32Array(segments.length * 4);
@@ -0,0 +1,64 @@
1
+ import { surface } from "../standard/render/surface";
2
+ import type { MeshData } from "../standard/render/mesh";
3
+
4
+ const SEED1 = "vec2(127.1, 311.7)";
5
+ const SEED2 = "vec2(269.5, 183.3)";
6
+
7
+ export const Water = surface({
8
+ fragment: `
9
+ let p = (*surface).worldPos.xz * 3.0;
10
+ let t = scene.time;
11
+ let t1 = vec2(t * 0.6, t * 0.4);
12
+ let t2 = vec2(t * 0.5, t * 0.7);
13
+ let eps = 0.1;
14
+
15
+ let h = (value2d(p + t1, ${SEED1}) + value2d(p - t2, ${SEED2})) * 0.5;
16
+ let hx = (value2d(p + vec2(eps, 0.0) + t1, ${SEED1}) + value2d(p + vec2(eps, 0.0) - t2, ${SEED2})) * 0.5;
17
+ let hz = (value2d(p + vec2(0.0, eps) + t1, ${SEED1}) + value2d(p + vec2(0.0, eps) - t2, ${SEED2})) * 0.5;
18
+
19
+ let dx = (hx - h) / eps;
20
+ let dz = (hz - h) / eps;
21
+ (*surface).normal = normalize(vec3(-dx * 0.015, 1.0, -dz * 0.015));`,
22
+ });
23
+
24
+ export function createSubdividedPlane(subdivisions: number = 32): MeshData {
25
+ const segments = subdivisions;
26
+ const vertexCount = (segments + 1) * (segments + 1);
27
+ const indexCount = segments * segments * 6;
28
+
29
+ const vertices = new Float32Array(vertexCount * 6);
30
+ const indices = new Uint16Array(indexCount);
31
+
32
+ let vi = 0;
33
+ for (let z = 0; z <= segments; z++) {
34
+ for (let x = 0; x <= segments; x++) {
35
+ const u = x / segments;
36
+ const v = z / segments;
37
+ vertices[vi++] = u - 0.5;
38
+ vertices[vi++] = 0;
39
+ vertices[vi++] = v - 0.5;
40
+ vertices[vi++] = 0;
41
+ vertices[vi++] = 1;
42
+ vertices[vi++] = 0;
43
+ }
44
+ }
45
+
46
+ let ii = 0;
47
+ for (let z = 0; z < segments; z++) {
48
+ for (let x = 0; x < segments; x++) {
49
+ const topLeft = z * (segments + 1) + x;
50
+ const topRight = topLeft + 1;
51
+ const bottomLeft = (z + 1) * (segments + 1) + x;
52
+ const bottomRight = bottomLeft + 1;
53
+
54
+ indices[ii++] = topLeft;
55
+ indices[ii++] = bottomLeft;
56
+ indices[ii++] = topRight;
57
+ indices[ii++] = topRight;
58
+ indices[ii++] = bottomLeft;
59
+ indices[ii++] = bottomRight;
60
+ }
61
+ }
62
+
63
+ return { vertices, indices, vertexCount, indexCount };
64
+ }
@@ -3,6 +3,7 @@ import { TransformsPlugin } from "./transforms";
3
3
  import { InputPlugin } from "./input";
4
4
  import { ComputePlugin, initCanvas } from "./compute";
5
5
  import { RenderPlugin } from "./render";
6
+ import { RasterPlugin } from "./raster";
6
7
  import { ActivityPlugin, spinnerDark } from "./activity";
7
8
  import { shallotDark } from "./loading";
8
9
 
@@ -12,6 +13,7 @@ export const DEFAULT_PLUGINS: readonly Plugin[] = [
12
13
  InputPlugin,
13
14
  ComputePlugin,
14
15
  RenderPlugin,
16
+ RasterPlugin,
15
17
  ];
16
18
 
17
19
  StateBuilder.defaultPlugins = DEFAULT_PLUGINS;
@@ -4,5 +4,7 @@ export * from "./input";
4
4
  export * from "./tween";
5
5
  export * from "./transforms";
6
6
  export * from "./render";
7
+ export * from "./raster";
8
+ export * from "./raytracing";
7
9
  export * from "./loading";
8
10
  export * from "./defaults";