vgpu-render 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +605 -2
  3. package/dist/assets/fira-mono-medium-subset.d.ts +4 -0
  4. package/dist/assets/fira-mono-medium-subset.js +889 -0
  5. package/dist/assets/fira-mono-msdf-atlas.d.ts +13 -0
  6. package/dist/assets/fira-mono-msdf-atlas.js +6222 -0
  7. package/dist/camera-2d.d.ts +43 -0
  8. package/dist/camera-2d.js +75 -0
  9. package/dist/color-space.d.ts +32 -0
  10. package/dist/color-space.js +45 -0
  11. package/dist/curve.d.ts +16 -0
  12. package/dist/curve.js +46 -0
  13. package/dist/debug-draw-3d.d.ts +14 -0
  14. package/dist/debug-draw-3d.js +36 -0
  15. package/dist/frame-draw-list-2d.d.ts +177 -0
  16. package/dist/frame-draw-list-2d.js +378 -0
  17. package/dist/frame-draw-list.d.ts +413 -0
  18. package/dist/frame-draw-list.js +556 -0
  19. package/dist/geometry-generators.d.ts +101 -0
  20. package/dist/geometry-generators.js +481 -0
  21. package/dist/gpu-debug-draw-2d.d.ts +48 -0
  22. package/dist/gpu-debug-draw-2d.js +450 -0
  23. package/dist/gpu-debug-draw-3d.d.ts +52 -0
  24. package/dist/gpu-debug-draw-3d.js +892 -0
  25. package/dist/gpu-instancing.d.ts +34 -0
  26. package/dist/gpu-instancing.js +93 -0
  27. package/dist/gpu-text-3d.d.ts +98 -0
  28. package/dist/gpu-text-3d.js +514 -0
  29. package/dist/index.d.ts +21 -0
  30. package/dist/index.js +20 -0
  31. package/dist/infinite-grid-2d.d.ts +73 -0
  32. package/dist/infinite-grid-2d.js +253 -0
  33. package/dist/infinite-grid-3d.d.ts +134 -0
  34. package/dist/infinite-grid-3d.js +407 -0
  35. package/dist/mesh-batch-3d.d.ts +10 -0
  36. package/dist/mesh-batch-3d.js +67 -0
  37. package/dist/shape2d-shader.d.ts +14 -0
  38. package/dist/shape2d-shader.js +288 -0
  39. package/dist/text-atlas.d.ts +176 -0
  40. package/dist/text-atlas.js +498 -0
  41. package/dist/text-font.d.ts +190 -0
  42. package/dist/text-font.js +261 -0
  43. package/dist/text-layout.d.ts +175 -0
  44. package/dist/text-layout.js +390 -0
  45. package/dist/text-msdf-baked.d.ts +41 -0
  46. package/dist/text-msdf-baked.js +117 -0
  47. package/dist/text-msdf-font.d.ts +85 -0
  48. package/dist/text-msdf-font.js +159 -0
  49. package/dist/text-msdf.d.ts +70 -0
  50. package/dist/text-msdf.js +242 -0
  51. package/dist/text-raster.d.ts +40 -0
  52. package/dist/text-raster.js +60 -0
  53. package/dist/text-sdf.d.ts +28 -0
  54. package/dist/text-sdf.js +119 -0
  55. package/dist/vector-math.d.ts +19 -0
  56. package/dist/vector-math.js +37 -0
  57. package/package.json +80 -4
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Signed distance field generation for the glyph atlas.
3
+ *
4
+ * Canvas2D hands back an anti-aliased coverage mask, which is enough for the bitmap path but
5
+ * cannot be rescaled: a texel says "80% ink", not "the edge is 0.3 px that way". A distance field
6
+ * stores the latter, so the fragment stage can rebuild the edge at whatever magnification the
7
+ * camera asks for instead of interpolating coverage between texels.
8
+ *
9
+ * The mask is binarised, an exact Euclidean distance transform (Felzenszwalb & Huttenlocher, 2012)
10
+ * gives the distance to the opposite side away from the edge, and partially covered texels override
11
+ * that with the sub-texel offset their own coverage implies — the coverage carries the detail a
12
+ * binary transform would throw away.
13
+ *
14
+ * Values are stored as `distance / (2 × range) + 0.5`, so 128 is the edge, and the field saturates
15
+ * `range` texels either side. `range` is small on purpose: only the neighbourhood of the edge is
16
+ * ever resampled, and a smaller range spends the 8 available bits on a narrower band.
17
+ */
18
+ /** Distance in atlas texels that one side of the encoded range covers. */
19
+ export const DEFAULT_SDF_RANGE = 4;
20
+ const FAR = 1e20;
21
+ /**
22
+ * Squared 1D distance transform, in place through `out`.
23
+ *
24
+ * Each input sample seeds a parabola `(q - i)² + f[i]` and the lower envelope of all of them is the
25
+ * transform. The stack of parabolas is built in one forward pass and sampled in a second, which is
26
+ * what makes this O(n) rather than O(n²) while staying exact.
27
+ */
28
+ function edt1d(column, length, out) {
29
+ const vertex = new Int32Array(length);
30
+ const boundary = new Float64Array(length + 1);
31
+ let top = 0;
32
+ vertex[0] = 0;
33
+ boundary[0] = -FAR;
34
+ boundary[1] = FAR;
35
+ for (let q = 1; q < length; q++) {
36
+ const qValue = column[q] + q * q;
37
+ let intersection = (qValue - (column[vertex[top]] + vertex[top] * vertex[top])) / (2 * q - 2 * vertex[top]);
38
+ while (intersection <= boundary[top]) {
39
+ top--;
40
+ intersection = (qValue - (column[vertex[top]] + vertex[top] * vertex[top])) / (2 * q - 2 * vertex[top]);
41
+ }
42
+ top++;
43
+ vertex[top] = q;
44
+ boundary[top] = intersection;
45
+ boundary[top + 1] = FAR;
46
+ }
47
+ top = 0;
48
+ for (let q = 0; q < length; q++) {
49
+ while (boundary[top + 1] < q)
50
+ top++;
51
+ const delta = q - vertex[top];
52
+ out[q] = delta * delta + column[vertex[top]];
53
+ }
54
+ }
55
+ /** Exact squared Euclidean distance from every texel to the nearest set texel of `seeds`. */
56
+ function edtSquared(seeds, width, height) {
57
+ const field = new Float64Array(width * height);
58
+ for (let index = 0; index < field.length; index++)
59
+ field[index] = seeds[index] !== 0 ? 0 : FAR;
60
+ const longest = Math.max(width, height);
61
+ const line = new Float64Array(longest);
62
+ const transformed = new Float64Array(longest);
63
+ for (let x = 0; x < width; x++) {
64
+ for (let y = 0; y < height; y++)
65
+ line[y] = field[y * width + x];
66
+ edt1d(line.subarray(0, height), height, transformed.subarray(0, height));
67
+ for (let y = 0; y < height; y++)
68
+ field[y * width + x] = transformed[y];
69
+ }
70
+ for (let y = 0; y < height; y++) {
71
+ const row = y * width;
72
+ for (let x = 0; x < width; x++)
73
+ line[x] = field[row + x];
74
+ edt1d(line.subarray(0, width), width, transformed.subarray(0, width));
75
+ for (let x = 0; x < width; x++)
76
+ field[row + x] = transformed[x];
77
+ }
78
+ return field;
79
+ }
80
+ /**
81
+ * Turns one glyph's coverage mask into an 8-bit distance field, one byte per texel.
82
+ *
83
+ * Positive means inside the glyph. Wholly covered and wholly empty texels take their distance from
84
+ * the transform; partially covered ones use `coverage - 0.5` instead, which is the sub-texel edge
85
+ * offset for a straight edge crossing that texel and is what keeps the field smooth across the
86
+ * boundary rather than quantised to whole texels.
87
+ */
88
+ export function coverageToSdf(coverage, width, height, range = DEFAULT_SDF_RANGE) {
89
+ if (!(range > 0))
90
+ throw new RangeError("sdf range must be greater than zero");
91
+ const size = width * height;
92
+ if (coverage.length !== size)
93
+ throw new RangeError(`coverage must hold ${size} texels`);
94
+ const insideSeeds = new Uint8Array(size);
95
+ const outsideSeeds = new Uint8Array(size);
96
+ for (let index = 0; index < size; index++) {
97
+ if (coverage[index] > 127)
98
+ insideSeeds[index] = 1;
99
+ else
100
+ outsideSeeds[index] = 1;
101
+ }
102
+ const distanceToInside = edtSquared(insideSeeds, width, height);
103
+ const distanceToOutside = edtSquared(outsideSeeds, width, height);
104
+ const scale = 1 / (2 * range);
105
+ const result = new Uint8Array(size);
106
+ for (let index = 0; index < size; index++) {
107
+ const ink = coverage[index] / 255;
108
+ let signed;
109
+ if (ink > 0 && ink < 1)
110
+ signed = ink - 0.5;
111
+ else if (ink >= 1)
112
+ signed = Math.sqrt(distanceToOutside[index]) - 0.5;
113
+ else
114
+ signed = -(Math.sqrt(distanceToInside[index]) - 0.5);
115
+ const encoded = Math.round((signed * scale + 0.5) * 255);
116
+ result[index] = encoded < 0 ? 0 : encoded > 255 ? 255 : encoded;
117
+ }
118
+ return result;
119
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Small vector helpers shared by the geometry generators and the example layouts.
3
+ *
4
+ * These live in the library rather than next to the gallery scenes because
5
+ * `geometry-generators.ts` needs them, and a library module must not reach into
6
+ * the example code.
7
+ */
8
+ import type { Vec3 } from "./frame-draw-list.js";
9
+ export declare function normalizeVector(p: Vec3): Vec3;
10
+ export declare function crossVector(a: Vec3, b: Vec3): Vec3;
11
+ /** Right-handed Rodrigues rotation, equivalent to reference `Vector3.rotated(axis, angle)`. */
12
+ export declare function rotateAroundAxis(p: Vec3, axis: Vec3, angle: number): Vec3;
13
+ export type CircleBasis = {
14
+ readonly x: Vec3;
15
+ readonly y: Vec3;
16
+ readonly z: Vec3;
17
+ };
18
+ /** Orthonormal basis for a circle in the plane whose normal is `normal`. */
19
+ export declare function circleBasis(normal: Vec3, angle: number): CircleBasis;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Small vector helpers shared by the geometry generators and the example layouts.
3
+ *
4
+ * These live in the library rather than next to the gallery scenes because
5
+ * `geometry-generators.ts` needs them, and a library module must not reach into
6
+ * the example code.
7
+ */
8
+ export function normalizeVector(p) {
9
+ const length = Math.hypot(p[0], p[1], p[2]) || 1;
10
+ return [p[0] / length, p[1] / length, p[2] / length];
11
+ }
12
+ export function crossVector(a, b) {
13
+ return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
14
+ }
15
+ /** Right-handed Rodrigues rotation, equivalent to reference `Vector3.rotated(axis, angle)`. */
16
+ export function rotateAroundAxis(p, axis, angle) {
17
+ const a = normalizeVector(axis);
18
+ const cos = Math.cos(angle);
19
+ const sin = Math.sin(angle);
20
+ const dot = p[0] * a[0] + p[1] * a[1] + p[2] * a[2];
21
+ const cross = crossVector(a, p);
22
+ return [
23
+ p[0] * cos + cross[0] * sin + a[0] * dot * (1 - cos),
24
+ p[1] * cos + cross[1] * sin + a[1] * dot * (1 - cos),
25
+ p[2] * cos + cross[2] * sin + a[2] * dot * (1 - cos),
26
+ ];
27
+ }
28
+ /** Orthonormal basis for a circle in the plane whose normal is `normal`. */
29
+ export function circleBasis(normal, angle) {
30
+ const n = normalizeVector(normal);
31
+ const reference = Math.abs(n[0]) > 0.95 ? [0, 0, 1] : [1, 0, 0];
32
+ const projection = reference[0] * n[0] + reference[1] * n[1] + reference[2] * n[2];
33
+ const planar = [reference[0] - n[0] * projection, reference[1] - n[1] * projection, reference[2] - n[2] * projection];
34
+ const widthAxis = normalizeVector(rotateAroundAxis(normalizeVector(planar), n, angle));
35
+ const heightAxis = normalizeVector(crossVector(n, widthAxis));
36
+ return { x: widthAxis, y: heightAxis, z: n };
37
+ }
package/package.json CHANGED
@@ -1,6 +1,82 @@
1
1
  {
2
2
  "name": "vgpu-render",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "WebGPU debug-draw, gizmo, infinite grid and text layer built on vgpu",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "LICENSE",
22
+ "README.md"
23
+ ],
24
+ "keywords": [
25
+ "webgpu",
26
+ "wgsl",
27
+ "vgpu",
28
+ "debug-draw",
29
+ "gizmo",
30
+ "gizmos",
31
+ "infinite-grid",
32
+ "text-rendering",
33
+ "msdf"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/jwu/vgpu-render.git"
38
+ },
39
+ "homepage": "https://jwu.github.io/vgpu-render/",
40
+ "bugs": {
41
+ "url": "https://github.com/jwu/vgpu-render/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "dev": "vite",
48
+ "build": "vite build",
49
+ "build:lib": "tsc -p tsconfig.build.json && bun scripts/strip-brand-from-dist.ts",
50
+ "prepublishOnly": "bun run typecheck && bun run build:lib",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run"
53
+ },
54
+ "peerDependencies": {
55
+ "vgpu": "^0.6.0"
56
+ },
57
+ "dependencies": {
58
+ "@vgpu/wgsl": "^0.6.0",
59
+ "opentype.js": "^2.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@base-ui/react": "^1.9.0",
63
+ "@fontsource-variable/inter": "^5.3.0",
64
+ "@tailwindcss/vite": "^4.3.3",
65
+ "@types/node": "latest",
66
+ "@types/react": "^19.3.0",
67
+ "@types/react-dom": "^19.3.0",
68
+ "@vgpu/wgsl": "^0.6.0",
69
+ "class-variance-authority": "^0.7.1",
70
+ "cn": "^0.4.0",
71
+ "lucide-react": "^1.57.0",
72
+ "react": "^19.3.1",
73
+ "react-dom": "^19.3.1",
74
+ "shadcn": "^4.21.5",
75
+ "tailwindcss": "^4.3.3",
76
+ "tw-animate-css": "^1.4.0",
77
+ "typescript": "latest",
78
+ "vgpu": "^0.6.0",
79
+ "vite": "latest",
80
+ "vitest": "latest"
81
+ }
82
+ }