insomni 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +674 -0
- package/README.md +234 -0
- package/dist/advanced.d.mts +76 -0
- package/dist/advanced.mjs +81 -0
- package/dist/assemble-BT3CXbSx.mjs +1574 -0
- package/dist/camera-view-DHmMiKvP.d.mts +326 -0
- package/dist/frame-mHNdKRpF.mjs +135 -0
- package/dist/index-CmMZCMJT.d.mts +39 -0
- package/dist/index-DkJfpntS.d.mts +2417 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6612 -0
- package/dist/internal.d.mts +892 -0
- package/dist/internal.mjs +566 -0
- package/dist/logger-DSyBF3Y_.mjs +15 -0
- package/dist/particles.d.mts +816 -0
- package/dist/particles.mjs +4804 -0
- package/dist/pipeline-BWCAZTKx.mjs +470 -0
- package/dist/pipeline-DE3a1Pnk.d.mts +115 -0
- package/dist/reactivity-B7I0pvzm.mjs +191 -0
- package/dist/reactivity.d.mts +2 -0
- package/dist/reactivity.mjs +2 -0
- package/dist/renderer-DzZqd1bY.d.mts +4566 -0
- package/dist/root-CHradZKM.mjs +30 -0
- package/dist/shape-DfZP9Jdk.mjs +349 -0
- package/dist/space-CeDnj6eu.mjs +11240 -0
- package/dist/spatial-Bd3Ay8I2.d.mts +85 -0
- package/dist/spatial-hash-C1crBjTo.mjs +77 -0
- package/dist/spatial.d.mts +2 -0
- package/dist/spatial.mjs +121 -0
- package/dist/text-font-D7GGDtTK.d.mts +185 -0
- package/dist/text-ttf.d.mts +91 -0
- package/dist/text-ttf.mjs +298 -0
- package/dist/texture-dABoqFoP.mjs +131 -0
- package/dist/viewport.d.mts +2 -0
- package/dist/viewport.mjs +274 -0
- package/package.json +69 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import tgpu, { d } from "typegpu";
|
|
2
|
+
//#region src/core/context.ts
|
|
3
|
+
const DEPTH_FORMAT = "depth24plus";
|
|
4
|
+
function safeSize(width, height) {
|
|
5
|
+
return [Math.max(width, 1), Math.max(height, 1)];
|
|
6
|
+
}
|
|
7
|
+
function createBackbuffer(root, width, height, format) {
|
|
8
|
+
const texture = root.device.createTexture({
|
|
9
|
+
label: "insomni-backbuffer",
|
|
10
|
+
size: safeSize(width, height),
|
|
11
|
+
format,
|
|
12
|
+
sampleCount: 1,
|
|
13
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
texture,
|
|
17
|
+
view: texture.createView()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function createDepth(root, width, height, sampleCount) {
|
|
21
|
+
const depthTexture = root.device.createTexture({
|
|
22
|
+
label: "insomni-depth",
|
|
23
|
+
size: safeSize(width, height),
|
|
24
|
+
format: DEPTH_FORMAT,
|
|
25
|
+
sampleCount,
|
|
26
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
depthTexture,
|
|
30
|
+
depthView: depthTexture.createView()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createMsaaColor(root, width, height, format, sampleCount) {
|
|
34
|
+
if (sampleCount <= 1) return null;
|
|
35
|
+
const texture = root.device.createTexture({
|
|
36
|
+
label: "insomni-color-msaa",
|
|
37
|
+
size: safeSize(width, height),
|
|
38
|
+
format,
|
|
39
|
+
sampleCount,
|
|
40
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
texture,
|
|
44
|
+
view: texture.createView()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createCanvasContext(root, canvas, sampleCount = 1, persistent = false) {
|
|
48
|
+
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
49
|
+
const gpuContext = root.configureContext({
|
|
50
|
+
canvas,
|
|
51
|
+
format,
|
|
52
|
+
alphaMode: "premultiplied",
|
|
53
|
+
...persistent ? { usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST } : {}
|
|
54
|
+
});
|
|
55
|
+
const depth = createDepth(root, canvas.width, canvas.height, sampleCount);
|
|
56
|
+
const msaa = createMsaaColor(root, canvas.width, canvas.height, format, sampleCount);
|
|
57
|
+
const back = persistent ? createBackbuffer(root, canvas.width, canvas.height, format) : null;
|
|
58
|
+
return {
|
|
59
|
+
canvas,
|
|
60
|
+
gpuContext,
|
|
61
|
+
format,
|
|
62
|
+
width: canvas.width,
|
|
63
|
+
height: canvas.height,
|
|
64
|
+
sampleCount,
|
|
65
|
+
colorTexture: msaa?.texture ?? null,
|
|
66
|
+
colorView: msaa?.view ?? null,
|
|
67
|
+
depthTexture: depth.depthTexture,
|
|
68
|
+
depthView: depth.depthView,
|
|
69
|
+
persistent,
|
|
70
|
+
backbuffer: back?.texture ?? null,
|
|
71
|
+
backbufferView: back?.view ?? null
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function resizeCanvas(ctx, root, width, height) {
|
|
75
|
+
const w = Math.max(0, Math.floor(width));
|
|
76
|
+
const h = Math.max(0, Math.floor(height));
|
|
77
|
+
ctx.canvas.width = w;
|
|
78
|
+
ctx.canvas.height = h;
|
|
79
|
+
ctx.width = w;
|
|
80
|
+
ctx.height = h;
|
|
81
|
+
ctx.depthTexture.destroy();
|
|
82
|
+
const depth = createDepth(root, width, height, ctx.sampleCount);
|
|
83
|
+
ctx.depthTexture = depth.depthTexture;
|
|
84
|
+
ctx.depthView = depth.depthView;
|
|
85
|
+
if (ctx.colorTexture) ctx.colorTexture.destroy();
|
|
86
|
+
const msaa = createMsaaColor(root, width, height, ctx.format, ctx.sampleCount);
|
|
87
|
+
ctx.colorTexture = msaa?.texture ?? null;
|
|
88
|
+
ctx.colorView = msaa?.view ?? null;
|
|
89
|
+
if (ctx.persistent) {
|
|
90
|
+
if (ctx.backbuffer) ctx.backbuffer.destroy();
|
|
91
|
+
const back = createBackbuffer(root, width, height, ctx.format);
|
|
92
|
+
ctx.backbuffer = back.texture;
|
|
93
|
+
ctx.backbufferView = back.view;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/math/scalar.ts
|
|
98
|
+
const TAU = Math.PI * 2;
|
|
99
|
+
const PI = Math.PI;
|
|
100
|
+
const HALF_PI = Math.PI / 2;
|
|
101
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
102
|
+
const RAD_TO_DEG = 180 / Math.PI;
|
|
103
|
+
function clamp(value, min, max) {
|
|
104
|
+
return value < min ? min : value > max ? max : value;
|
|
105
|
+
}
|
|
106
|
+
function clamp01(value) {
|
|
107
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
108
|
+
}
|
|
109
|
+
function lerp(a, b, t) {
|
|
110
|
+
return a + (b - a) * t;
|
|
111
|
+
}
|
|
112
|
+
/** Inverse of `lerp`: where does `value` sit between `a` and `b`? */
|
|
113
|
+
function inverseLerp(a, b, value) {
|
|
114
|
+
return a === b ? 0 : (value - a) / (b - a);
|
|
115
|
+
}
|
|
116
|
+
/** Map `value` from `[a0, a1]` to `[b0, b1]` (unclamped). */
|
|
117
|
+
function remap(value, a0, a1, b0, b1) {
|
|
118
|
+
return b0 + (value - a0) * (b1 - b0) / (a1 - a0);
|
|
119
|
+
}
|
|
120
|
+
/** Hermite smoothstep. `t` is clamped to `[edge0, edge1]`. */
|
|
121
|
+
function smoothstep(edge0, edge1, value) {
|
|
122
|
+
const t = clamp01((value - edge0) / (edge1 - edge0));
|
|
123
|
+
return t * t * (3 - 2 * t);
|
|
124
|
+
}
|
|
125
|
+
/** Ken Perlin's 5th-order smoothstep — zero 1st & 2nd derivatives at edges. */
|
|
126
|
+
function smootherstep(edge0, edge1, value) {
|
|
127
|
+
const t = clamp01((value - edge0) / (edge1 - edge0));
|
|
128
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
129
|
+
}
|
|
130
|
+
function degToRad(deg) {
|
|
131
|
+
return deg * DEG_TO_RAD;
|
|
132
|
+
}
|
|
133
|
+
function radToDeg(rad) {
|
|
134
|
+
return rad * RAD_TO_DEG;
|
|
135
|
+
}
|
|
136
|
+
/** Wraps `value` into `[0, modulus)` (handles negatives, unlike `%`). */
|
|
137
|
+
function wrap(value, modulus) {
|
|
138
|
+
return (value % modulus + modulus) % modulus;
|
|
139
|
+
}
|
|
140
|
+
function sign(value) {
|
|
141
|
+
return value < 0 ? -1 : value > 0 ? 1 : 0;
|
|
142
|
+
}
|
|
143
|
+
function approxEqual(a, b, epsilon = 1e-6) {
|
|
144
|
+
return Math.abs(a - b) <= epsilon;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/shared/pipeline.ts
|
|
148
|
+
const ALPHA_BLEND = {
|
|
149
|
+
color: {
|
|
150
|
+
srcFactor: "one",
|
|
151
|
+
dstFactor: "one-minus-src-alpha",
|
|
152
|
+
operation: "add"
|
|
153
|
+
},
|
|
154
|
+
alpha: {
|
|
155
|
+
srcFactor: "one",
|
|
156
|
+
dstFactor: "one-minus-src-alpha",
|
|
157
|
+
operation: "add"
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const OPAQUE_ALPHA_THRESHOLD = .5;
|
|
161
|
+
/**
|
|
162
|
+
* Depth attachment uses `less` compare. Z is assigned in scene order — lower Z
|
|
163
|
+
* is closer to the camera. Opaque shapes are drawn front-to-back (lowest Z
|
|
164
|
+
* first) with depth write enabled; transparent shapes draw back-to-front with
|
|
165
|
+
* depth write disabled but depth test still rejecting fragments occluded by
|
|
166
|
+
* opaque content.
|
|
167
|
+
*/
|
|
168
|
+
const DEPTH_COMPARE = "less";
|
|
169
|
+
const CameraSchema = d.struct({
|
|
170
|
+
vpCol0: d.vec2f,
|
|
171
|
+
vpCol1: d.vec2f,
|
|
172
|
+
vpCol2: d.vec2f,
|
|
173
|
+
viewport: d.vec2f,
|
|
174
|
+
dpr: d.f32,
|
|
175
|
+
_pad: d.f32
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* Per-draw-call uniform supplying the Z slot range this draw occupies.
|
|
179
|
+
* `baseZ` is the Z value for the shape whose `instance_index === firstInstance`;
|
|
180
|
+
* subsequent instances step by `zStep`. Shader:
|
|
181
|
+
* z = baseZ + zStep * f32(instance_index - firstInstance)
|
|
182
|
+
*/
|
|
183
|
+
const DrawSchema = d.struct({
|
|
184
|
+
baseZ: d.f32,
|
|
185
|
+
zStep: d.f32,
|
|
186
|
+
firstInstance: d.u32,
|
|
187
|
+
_pad: d.u32
|
|
188
|
+
});
|
|
189
|
+
/**
|
|
190
|
+
* Per-instance shape record. 32 B, bit-packed. Byte-identical to the CPU-side
|
|
191
|
+
* layout in `pack.ts`:
|
|
192
|
+
* pos @ 0 vec2f
|
|
193
|
+
* sizePacked @ 8 u32 — `pack2x16float(sizeX, sizeY)`
|
|
194
|
+
* fillBits @ 12 u32 — `pack4x8unorm(fill)`
|
|
195
|
+
* strokeBits @ 16 u32
|
|
196
|
+
* rsPacked @ 20 u32 — `pack2x16float(rotation, strokeWidth)`
|
|
197
|
+
* crPacked @ 24 u32 — `pack2x16float(cornerRadius, _)`
|
|
198
|
+
* shapeType @ 28 u32
|
|
199
|
+
*
|
|
200
|
+
* Shaders `unpack4x8unorm` / `unpack2x16float` on access.
|
|
201
|
+
*/
|
|
202
|
+
const ShapeSchema = d.struct({
|
|
203
|
+
pos: d.vec2f,
|
|
204
|
+
sizePacked: d.u32,
|
|
205
|
+
fillBits: d.u32,
|
|
206
|
+
strokeBits: d.u32,
|
|
207
|
+
rsPacked: d.u32,
|
|
208
|
+
crPacked: d.u32,
|
|
209
|
+
shapeType: d.u32
|
|
210
|
+
});
|
|
211
|
+
const TriangleSchema = d.struct({
|
|
212
|
+
p1: d.vec2f,
|
|
213
|
+
p2: d.vec2f,
|
|
214
|
+
p3: d.vec2f,
|
|
215
|
+
fill: d.vec4f
|
|
216
|
+
});
|
|
217
|
+
const CameraLayout = tgpu.bindGroupLayout({ camera: {
|
|
218
|
+
uniform: CameraSchema,
|
|
219
|
+
visibility: ["vertex"]
|
|
220
|
+
} });
|
|
221
|
+
const ShapeBufferLayout = tgpu.bindGroupLayout({ shapes: {
|
|
222
|
+
storage: d.arrayOf(ShapeSchema),
|
|
223
|
+
access: "readonly",
|
|
224
|
+
visibility: ["vertex", "fragment"]
|
|
225
|
+
} });
|
|
226
|
+
const TriangleBufferLayout = tgpu.bindGroupLayout({ triangles: {
|
|
227
|
+
storage: d.arrayOf(TriangleSchema),
|
|
228
|
+
access: "readonly",
|
|
229
|
+
visibility: ["vertex"]
|
|
230
|
+
} });
|
|
231
|
+
const DrawLayout = tgpu.bindGroupLayout({ draw: {
|
|
232
|
+
uniform: DrawSchema,
|
|
233
|
+
visibility: ["vertex"]
|
|
234
|
+
} });
|
|
235
|
+
CameraLayout.bound.camera;
|
|
236
|
+
ShapeBufferLayout.bound.shapes;
|
|
237
|
+
TriangleBufferLayout.bound.triangles;
|
|
238
|
+
DrawLayout.bound.draw;
|
|
239
|
+
tgpu.bindGroupLayout({ clearColor: {
|
|
240
|
+
uniform: d.vec4f,
|
|
241
|
+
visibility: ["fragment"]
|
|
242
|
+
} }).bound.clearColor;
|
|
243
|
+
tgpu.fn([d.vec2f, d.f32], d.vec2f)`(v, a) {
|
|
244
|
+
let c = cos(a);
|
|
245
|
+
let s = sin(a);
|
|
246
|
+
return vec2f(c * v.x - s * v.y, s * v.x + c * v.y);
|
|
247
|
+
}`;
|
|
248
|
+
tgpu.fn([
|
|
249
|
+
d.vec2f,
|
|
250
|
+
d.vec2f,
|
|
251
|
+
d.f32
|
|
252
|
+
], d.f32)`(p, b, r) {
|
|
253
|
+
let q = abs(p) - b + r;
|
|
254
|
+
return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - r;
|
|
255
|
+
}`;
|
|
256
|
+
tgpu.fn([d.vec2f, d.f32], d.f32)`(p, r) {
|
|
257
|
+
return length(p) - r;
|
|
258
|
+
}`;
|
|
259
|
+
tgpu.fn([d.vec2f, d.vec2f], d.f32)`(p, ab) {
|
|
260
|
+
let k1 = length(p / ab);
|
|
261
|
+
let k2 = length(p / (ab * ab));
|
|
262
|
+
return k1 * (k1 - 1.0) / k2;
|
|
263
|
+
}`;
|
|
264
|
+
const SpriteSchema = d.struct({
|
|
265
|
+
pos: d.vec2f,
|
|
266
|
+
size: d.vec2f,
|
|
267
|
+
uvMin: d.vec2f,
|
|
268
|
+
uvMax: d.vec2f,
|
|
269
|
+
tint: d.vec4f,
|
|
270
|
+
anchor: d.vec2f,
|
|
271
|
+
rotation: d.f32,
|
|
272
|
+
_pad: d.f32
|
|
273
|
+
});
|
|
274
|
+
const SpriteBufferLayout = tgpu.bindGroupLayout({ sprites: {
|
|
275
|
+
storage: d.arrayOf(SpriteSchema),
|
|
276
|
+
access: "readonly",
|
|
277
|
+
visibility: ["vertex"]
|
|
278
|
+
} });
|
|
279
|
+
const TextureSamplerLayout = tgpu.bindGroupLayout({
|
|
280
|
+
spriteTexture: {
|
|
281
|
+
texture: "float",
|
|
282
|
+
visibility: ["fragment"]
|
|
283
|
+
},
|
|
284
|
+
spriteSampler: {
|
|
285
|
+
sampler: "filtering",
|
|
286
|
+
visibility: ["fragment"]
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
SpriteBufferLayout.bound.sprites;
|
|
290
|
+
TextureSamplerLayout.bound.spriteTexture;
|
|
291
|
+
TextureSamplerLayout.bound.spriteSampler;
|
|
292
|
+
/**
|
|
293
|
+
* Per-instance segment record. 24 B:
|
|
294
|
+
* start: vec2f @ 0
|
|
295
|
+
* end: vec2f @ 8
|
|
296
|
+
* color: u32 @ 16 (unorm8x4)
|
|
297
|
+
* width: f32 @ 20 (layer-local units)
|
|
298
|
+
* Byte-identical to the CPU layout in `pack.ts` (`SEGMENT_FLOATS = 6`).
|
|
299
|
+
*/
|
|
300
|
+
const SegmentSchema = d.struct({
|
|
301
|
+
start: d.vec2f,
|
|
302
|
+
end: d.vec2f,
|
|
303
|
+
colorBits: d.u32,
|
|
304
|
+
width: d.f32
|
|
305
|
+
});
|
|
306
|
+
tgpu.bindGroupLayout({ segments: {
|
|
307
|
+
storage: d.arrayOf(SegmentSchema),
|
|
308
|
+
access: "readonly",
|
|
309
|
+
visibility: ["vertex", "fragment"]
|
|
310
|
+
} }).bound.segments;
|
|
311
|
+
/**
|
|
312
|
+
* Per-instance curve record. 48 B, 16-B-aligned for storage buffer arrays:
|
|
313
|
+
* p0: vec2f @ 0
|
|
314
|
+
* p1: vec2f @ 8
|
|
315
|
+
* p2: vec2f @ 16
|
|
316
|
+
* p3: vec2f @ 24
|
|
317
|
+
* colorBits: u32 @ 32 (unorm8x4)
|
|
318
|
+
* widthPacked: u32 @ 36 (pack2x16float(widthStart, widthEnd))
|
|
319
|
+
* flagsBits: u32 @ 40 (cap style in low 2 bits)
|
|
320
|
+
* _pad: u32 @ 44
|
|
321
|
+
* Byte-identical to the CPU layout in `pack.ts` (`CURVE_FLOATS = 12`).
|
|
322
|
+
*/
|
|
323
|
+
const CurveSchema = d.struct({
|
|
324
|
+
p0: d.vec2f,
|
|
325
|
+
p1: d.vec2f,
|
|
326
|
+
p2: d.vec2f,
|
|
327
|
+
p3: d.vec2f,
|
|
328
|
+
colorBits: d.u32,
|
|
329
|
+
widthPacked: d.u32,
|
|
330
|
+
flagsBits: d.u32,
|
|
331
|
+
_pad: d.u32
|
|
332
|
+
});
|
|
333
|
+
tgpu.bindGroupLayout({ curves: {
|
|
334
|
+
storage: d.arrayOf(CurveSchema),
|
|
335
|
+
access: "readonly",
|
|
336
|
+
visibility: ["vertex", "fragment"]
|
|
337
|
+
} }).bound.curves;
|
|
338
|
+
tgpu.fn([
|
|
339
|
+
d.f32,
|
|
340
|
+
d.f32,
|
|
341
|
+
d.f32,
|
|
342
|
+
d.f32
|
|
343
|
+
], d.vec2f)`(c0, c1, c2, c3) {
|
|
344
|
+
var mn = min(c0, c3);
|
|
345
|
+
var mx = max(c0, c3);
|
|
346
|
+
let alpha = -c0 + 3.0 * c1 - 3.0 * c2 + c3;
|
|
347
|
+
let beta = c0 - 2.0 * c1 + c2;
|
|
348
|
+
let gamma = -c0 + c1;
|
|
349
|
+
if (abs(alpha) < 1e-6) {
|
|
350
|
+
if (abs(beta) > 1e-6) {
|
|
351
|
+
let t = -gamma / (2.0 * beta);
|
|
352
|
+
if (t > 0.0 && t < 1.0) {
|
|
353
|
+
let u = 1.0 - t;
|
|
354
|
+
let v = u * u * u * c0 + 3.0 * u * u * t * c1 + 3.0 * u * t * t * c2 + t * t * t * c3;
|
|
355
|
+
mn = min(mn, v);
|
|
356
|
+
mx = max(mx, v);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
let disc = beta * beta - alpha * gamma;
|
|
361
|
+
if (disc >= 0.0) {
|
|
362
|
+
let s = sqrt(disc);
|
|
363
|
+
let t1 = (-beta + s) / alpha;
|
|
364
|
+
if (t1 > 0.0 && t1 < 1.0) {
|
|
365
|
+
let u = 1.0 - t1;
|
|
366
|
+
let v = u * u * u * c0 + 3.0 * u * u * t1 * c1 + 3.0 * u * t1 * t1 * c2 + t1 * t1 * t1 * c3;
|
|
367
|
+
mn = min(mn, v);
|
|
368
|
+
mx = max(mx, v);
|
|
369
|
+
}
|
|
370
|
+
let t2 = (-beta - s) / alpha;
|
|
371
|
+
if (t2 > 0.0 && t2 < 1.0) {
|
|
372
|
+
let u = 1.0 - t2;
|
|
373
|
+
let v = u * u * u * c0 + 3.0 * u * u * t2 * c1 + 3.0 * u * t2 * t2 * c2 + t2 * t2 * t2 * c3;
|
|
374
|
+
mn = min(mn, v);
|
|
375
|
+
mx = max(mx, v);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return vec2f(mn, mx);
|
|
380
|
+
}`;
|
|
381
|
+
/**
|
|
382
|
+
* Per-instance arc record. 32 B (16-B aligned for storage struct arrays):
|
|
383
|
+
* center: vec2f @ 0
|
|
384
|
+
* radius: f32 @ 8
|
|
385
|
+
* theta0: f32 @ 12 start angle (radians)
|
|
386
|
+
* theta1: f32 @ 16 end angle (radians)
|
|
387
|
+
* width: f32 @ 20 stroke thickness (layer-local units)
|
|
388
|
+
* colorBits: u32 @ 24 unorm8x4 RGBA, unpremultiplied
|
|
389
|
+
* _pad: u32 @ 28
|
|
390
|
+
*
|
|
391
|
+
* Arc is the short path from `theta0` to `theta1`; direction is irrelevant
|
|
392
|
+
* to the rendered geometry. CPU normalises so the absolute span is ≤ 2π;
|
|
393
|
+
* spans ≥ 2π render as a full ring. Caps are butt (perpendicular at each
|
|
394
|
+
* endpoint).
|
|
395
|
+
*/
|
|
396
|
+
const ArcSchema = d.struct({
|
|
397
|
+
center: d.vec2f,
|
|
398
|
+
radius: d.f32,
|
|
399
|
+
theta0: d.f32,
|
|
400
|
+
theta1: d.f32,
|
|
401
|
+
width: d.f32,
|
|
402
|
+
colorBits: d.u32,
|
|
403
|
+
_pad: d.u32
|
|
404
|
+
});
|
|
405
|
+
tgpu.bindGroupLayout({ arcs: {
|
|
406
|
+
storage: d.arrayOf(ArcSchema),
|
|
407
|
+
access: "readonly",
|
|
408
|
+
visibility: ["vertex", "fragment"]
|
|
409
|
+
} }).bound.arcs;
|
|
410
|
+
tgpu.fn([
|
|
411
|
+
d.f32,
|
|
412
|
+
d.f32,
|
|
413
|
+
d.f32,
|
|
414
|
+
d.f32
|
|
415
|
+
], d.vec4f)`(r, halfAng, halfW, pad) {
|
|
416
|
+
let outer = r + halfW + pad;
|
|
417
|
+
let innerRaw = r - halfW - pad;
|
|
418
|
+
let inner = select(0.0, innerRaw, innerRaw > 0.0);
|
|
419
|
+
let ch = cos(halfAng);
|
|
420
|
+
let sh = sin(halfAng);
|
|
421
|
+
// +X axis (angle 0) is always inside the wedge → x_max = outer.
|
|
422
|
+
let xMax = outer;
|
|
423
|
+
var xMin: f32;
|
|
424
|
+
var yMax: f32;
|
|
425
|
+
var yMin: f32;
|
|
426
|
+
let PI = 3.141592653589793;
|
|
427
|
+
let HALF_PI = 1.5707963267948966;
|
|
428
|
+
if (halfAng >= PI) {
|
|
429
|
+
xMin = -outer;
|
|
430
|
+
yMax = outer;
|
|
431
|
+
yMin = -outer;
|
|
432
|
+
} else if (halfAng >= HALF_PI) {
|
|
433
|
+
// Wedge crosses ±π/2 → y reaches ±outer; x_min at the wedge boundary.
|
|
434
|
+
yMax = outer;
|
|
435
|
+
yMin = -outer;
|
|
436
|
+
xMin = outer * ch;
|
|
437
|
+
} else {
|
|
438
|
+
// Narrow wedge: y extrema come from the outer-ring endpoints; x_min
|
|
439
|
+
// comes from the inner-ring endpoints.
|
|
440
|
+
yMax = outer * sh;
|
|
441
|
+
yMin = -yMax;
|
|
442
|
+
xMin = inner * ch;
|
|
443
|
+
}
|
|
444
|
+
return vec4f(xMin, yMin, xMax, yMax);
|
|
445
|
+
}`;
|
|
446
|
+
tgpu.fn([
|
|
447
|
+
d.vec2f,
|
|
448
|
+
d.f32,
|
|
449
|
+
d.f32,
|
|
450
|
+
d.f32
|
|
451
|
+
], d.f32)`(p, r, mid, halfAng) {
|
|
452
|
+
let c = cos(mid);
|
|
453
|
+
let s = sin(mid);
|
|
454
|
+
// Rotate by -mid so the arc spans [-halfAng, halfAng] around +X.
|
|
455
|
+
let q = vec2f(c * p.x + s * p.y, -s * p.x + c * p.y);
|
|
456
|
+
// Reflect to upper half — the arc is symmetric in y.
|
|
457
|
+
let qa = vec2f(q.x, abs(q.y));
|
|
458
|
+
let ch = cos(halfAng);
|
|
459
|
+
let sh = sin(halfAng);
|
|
460
|
+
// qa is outside the wedge (angle > halfAng) iff cross > 0.
|
|
461
|
+
let cross = ch * qa.y - sh * qa.x;
|
|
462
|
+
if (cross <= 0.0) {
|
|
463
|
+
return abs(length(qa) - r);
|
|
464
|
+
}
|
|
465
|
+
// Outside the wedge: nearest point on the arc is the upper endpoint.
|
|
466
|
+
let endpoint = vec2f(r * ch, r * sh);
|
|
467
|
+
return length(qa - endpoint);
|
|
468
|
+
}`;
|
|
469
|
+
//#endregion
|
|
470
|
+
export { DEPTH_FORMAT as A, lerp as C, smootherstep as D, sign as E, resizeCanvas as M, smoothstep as O, inverseLerp as S, remap as T, TAU as _, OPAQUE_ALPHA_THRESHOLD as a, clamp01 as b, SpriteBufferLayout as c, TriangleBufferLayout as d, TriangleSchema as f, RAD_TO_DEG as g, PI as h, DEPTH_COMPARE as i, createCanvasContext as j, wrap as k, SpriteSchema as l, HALF_PI as m, CameraLayout as n, ShapeBufferLayout as o, DEG_TO_RAD as p, CameraSchema as r, ShapeSchema as s, ALPHA_BLEND as t, TextureSamplerLayout as u, approxEqual as v, radToDeg as w, degToRad as x, clamp as y };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as _$typegpu from "typegpu";
|
|
2
|
+
import { TgpuRoot, d } from "typegpu";
|
|
3
|
+
|
|
4
|
+
//#region src/shared/pipeline.d.ts
|
|
5
|
+
declare const CameraSchema: d.WgslStruct<{
|
|
6
|
+
vpCol0: d.Vec2f;
|
|
7
|
+
vpCol1: d.Vec2f;
|
|
8
|
+
vpCol2: d.Vec2f;
|
|
9
|
+
viewport: d.Vec2f;
|
|
10
|
+
dpr: d.F32;
|
|
11
|
+
_pad: d.F32;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Per-instance shape record. 32 B, bit-packed. Byte-identical to the CPU-side
|
|
15
|
+
* layout in `pack.ts`:
|
|
16
|
+
* pos @ 0 vec2f
|
|
17
|
+
* sizePacked @ 8 u32 — `pack2x16float(sizeX, sizeY)`
|
|
18
|
+
* fillBits @ 12 u32 — `pack4x8unorm(fill)`
|
|
19
|
+
* strokeBits @ 16 u32
|
|
20
|
+
* rsPacked @ 20 u32 — `pack2x16float(rotation, strokeWidth)`
|
|
21
|
+
* crPacked @ 24 u32 — `pack2x16float(cornerRadius, _)`
|
|
22
|
+
* shapeType @ 28 u32
|
|
23
|
+
*
|
|
24
|
+
* Shaders `unpack4x8unorm` / `unpack2x16float` on access.
|
|
25
|
+
*/
|
|
26
|
+
declare const ShapeSchema: d.WgslStruct<{
|
|
27
|
+
pos: d.Vec2f;
|
|
28
|
+
sizePacked: d.U32;
|
|
29
|
+
fillBits: d.U32;
|
|
30
|
+
strokeBits: d.U32;
|
|
31
|
+
rsPacked: d.U32;
|
|
32
|
+
crPacked: d.U32;
|
|
33
|
+
shapeType: d.U32;
|
|
34
|
+
}>;
|
|
35
|
+
declare const TriangleSchema: d.WgslStruct<{
|
|
36
|
+
p1: d.Vec2f;
|
|
37
|
+
p2: d.Vec2f;
|
|
38
|
+
p3: d.Vec2f;
|
|
39
|
+
fill: d.Vec4f;
|
|
40
|
+
}>;
|
|
41
|
+
declare const CameraLayout: _$typegpu.TgpuBindGroupLayout<{
|
|
42
|
+
camera: {
|
|
43
|
+
uniform: d.WgslStruct<{
|
|
44
|
+
vpCol0: d.Vec2f;
|
|
45
|
+
vpCol1: d.Vec2f;
|
|
46
|
+
vpCol2: d.Vec2f;
|
|
47
|
+
viewport: d.Vec2f;
|
|
48
|
+
dpr: d.F32;
|
|
49
|
+
_pad: d.F32;
|
|
50
|
+
}>;
|
|
51
|
+
visibility: "vertex"[];
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
declare const ShapeBufferLayout: _$typegpu.TgpuBindGroupLayout<{
|
|
55
|
+
shapes: {
|
|
56
|
+
storage: (elementCount: number) => d.WgslArray<d.WgslStruct<{
|
|
57
|
+
pos: d.Vec2f;
|
|
58
|
+
sizePacked: d.U32;
|
|
59
|
+
fillBits: d.U32;
|
|
60
|
+
strokeBits: d.U32;
|
|
61
|
+
rsPacked: d.U32;
|
|
62
|
+
crPacked: d.U32;
|
|
63
|
+
shapeType: d.U32;
|
|
64
|
+
}>>;
|
|
65
|
+
access: "readonly";
|
|
66
|
+
visibility: ("fragment" | "vertex")[];
|
|
67
|
+
};
|
|
68
|
+
}>;
|
|
69
|
+
declare const TriangleBufferLayout: _$typegpu.TgpuBindGroupLayout<{
|
|
70
|
+
triangles: {
|
|
71
|
+
storage: (elementCount: number) => d.WgslArray<d.WgslStruct<{
|
|
72
|
+
p1: d.Vec2f;
|
|
73
|
+
p2: d.Vec2f;
|
|
74
|
+
p3: d.Vec2f;
|
|
75
|
+
fill: d.Vec4f;
|
|
76
|
+
}>>;
|
|
77
|
+
access: "readonly";
|
|
78
|
+
visibility: "vertex"[];
|
|
79
|
+
};
|
|
80
|
+
}>;
|
|
81
|
+
declare const SpriteSchema: d.WgslStruct<{
|
|
82
|
+
pos: d.Vec2f;
|
|
83
|
+
size: d.Vec2f;
|
|
84
|
+
uvMin: d.Vec2f;
|
|
85
|
+
uvMax: d.Vec2f;
|
|
86
|
+
tint: d.Vec4f;
|
|
87
|
+
anchor: d.Vec2f;
|
|
88
|
+
rotation: d.F32;
|
|
89
|
+
_pad: d.F32;
|
|
90
|
+
}>;
|
|
91
|
+
declare const SpriteBufferLayout: _$typegpu.TgpuBindGroupLayout<{
|
|
92
|
+
sprites: {
|
|
93
|
+
storage: (elementCount: number) => d.WgslArray<d.WgslStruct<{
|
|
94
|
+
pos: d.Vec2f;
|
|
95
|
+
size: d.Vec2f;
|
|
96
|
+
uvMin: d.Vec2f;
|
|
97
|
+
uvMax: d.Vec2f;
|
|
98
|
+
tint: d.Vec4f;
|
|
99
|
+
anchor: d.Vec2f;
|
|
100
|
+
rotation: d.F32;
|
|
101
|
+
_pad: d.F32;
|
|
102
|
+
}>>;
|
|
103
|
+
access: "readonly";
|
|
104
|
+
visibility: "vertex"[];
|
|
105
|
+
};
|
|
106
|
+
}>;
|
|
107
|
+
declare const TextureSamplerLayout: _$typegpu.TgpuBindGroupLayout<{
|
|
108
|
+
spriteTexture: _$typegpu.TgpuLayoutTexture<d.WgslTexture2d<d.F32>>;
|
|
109
|
+
spriteSampler: {
|
|
110
|
+
sampler: "filtering";
|
|
111
|
+
visibility: "fragment"[];
|
|
112
|
+
};
|
|
113
|
+
}>;
|
|
114
|
+
//#endregion
|
|
115
|
+
export { SpriteBufferLayout as a, TriangleBufferLayout as c, ShapeSchema as i, TriangleSchema as l, CameraSchema as n, SpriteSchema as o, ShapeBufferLayout as r, TextureSamplerLayout as s, CameraLayout as t };
|