3d-spinner 0.9.2 → 0.9.4

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 (101) hide show
  1. package/README.md +66 -4
  2. package/dist/animation-label.d.ts +8 -0
  3. package/dist/animation-label.js +47 -0
  4. package/dist/animation.d.ts +2 -0
  5. package/dist/animations/charged-orb.d.ts +55 -0
  6. package/dist/animations/charged-orb.js +229 -0
  7. package/dist/animations/ghost-train.d.ts +72 -0
  8. package/dist/animations/ghost-train.js +247 -0
  9. package/dist/animations/grid-assembly.d.ts +70 -0
  10. package/dist/animations/grid-assembly.js +282 -0
  11. package/dist/animations/object-motion.d.ts +19 -7
  12. package/dist/animations/object-motion.js +38 -32
  13. package/dist/animations/particles.d.ts +110 -0
  14. package/dist/animations/particles.js +209 -0
  15. package/dist/animations/rocket-launch.d.ts +58 -0
  16. package/dist/animations/rocket-launch.js +375 -0
  17. package/dist/cjs/animations/charged-orb.cjs +1334 -0
  18. package/dist/cjs/animations/grid-assembly.cjs +1380 -0
  19. package/dist/cjs/animations/object-motion.cjs +124 -43
  20. package/dist/cjs/animations/particles.cjs +1940 -0
  21. package/dist/cjs/animations/spin.cjs +4 -3
  22. package/dist/cjs/composite-animation.cjs +58 -0
  23. package/dist/cjs/engines/little-3d-engine/little-3d-engine.cjs +143 -22
  24. package/dist/cjs/engines/little-3d-engine/loaders/obj.cjs +30 -1
  25. package/dist/cjs/engines/little-3d-engine/renderers/canvas2d-textured.cjs +345 -0
  26. package/dist/cjs/engines/little-3d-engine/renderers/webgl-textured.cjs +528 -0
  27. package/dist/cjs/engines/little-3d-engine/renderers/webgpu-textured.cjs +712 -0
  28. package/dist/cjs/prefabs/prefabs.cjs +3916 -0
  29. package/dist/composite-animation.d.ts +18 -0
  30. package/dist/composite-animation.js +39 -0
  31. package/dist/engines/little-3d-engine/little-3d-engine.d.ts +17 -12
  32. package/dist/engines/little-3d-engine/little-3d-engine.js +13 -8
  33. package/dist/engines/little-3d-engine/loaders/obj.d.ts +12 -4
  34. package/dist/engines/little-3d-engine/loaders/obj.js +44 -5
  35. package/dist/engines/little-3d-engine/renderer.d.ts +7 -1
  36. package/dist/engines/little-3d-engine/renderer.js +2 -0
  37. package/dist/engines/little-3d-engine/renderers/canvas2d-textured.d.ts +20 -0
  38. package/dist/engines/little-3d-engine/renderers/canvas2d-textured.js +135 -0
  39. package/dist/engines/little-3d-engine/renderers/textured-helpers.d.ts +9 -0
  40. package/dist/engines/little-3d-engine/renderers/textured-helpers.js +35 -0
  41. package/dist/engines/little-3d-engine/renderers/webgl-textured.d.ts +43 -0
  42. package/dist/engines/little-3d-engine/renderers/webgl-textured.js +211 -0
  43. package/dist/engines/little-3d-engine/renderers/webgpu-textured.d.ts +45 -0
  44. package/dist/engines/little-3d-engine/renderers/webgpu-textured.js +300 -0
  45. package/dist/engines/little-3d-engine/renderers/webgpu.d.ts +11 -6
  46. package/dist/engines/little-3d-engine/shapes/complex/plane.d.ts +3 -0
  47. package/dist/engines/little-3d-engine/shapes/complex/plane.js +33 -0
  48. package/dist/engines/little-3d-engine/shapes/{cube.d.ts → primitives/cube.d.ts} +1 -1
  49. package/dist/engines/little-3d-engine/shapes/{octahedron.d.ts → primitives/octahedron.d.ts} +1 -1
  50. package/dist/engines/little-3d-engine/shapes/{pyramid.d.ts → primitives/pyramid.d.ts} +1 -1
  51. package/dist/engines/little-3d-engine/shapes/primitives/quad.d.ts +9 -0
  52. package/dist/engines/little-3d-engine/shapes/primitives/quad.js +18 -0
  53. package/dist/engines/little-3d-engine/shapes/{cube-sphere.d.ts → primitives/spheres/cube-sphere.d.ts} +1 -1
  54. package/dist/engines/little-3d-engine/shapes/{icosphere.d.ts → primitives/spheres/icosphere.d.ts} +1 -1
  55. package/dist/engines/little-3d-engine/shapes/{icosphere.js → primitives/spheres/icosphere.js} +1 -1
  56. package/dist/engines/little-3d-engine/shapes/{octa-sphere.d.ts → primitives/spheres/octa-sphere.d.ts} +1 -1
  57. package/dist/engines/little-3d-engine/shapes/{octa-sphere.js → primitives/spheres/octa-sphere.js} +1 -1
  58. package/dist/engines/little-3d-engine/shapes/{uv-sphere.d.ts → primitives/spheres/uv-sphere.d.ts} +1 -1
  59. package/dist/engines/little-3d-engine/shapes/{tetrahedron.d.ts → primitives/tetrahedron.d.ts} +1 -1
  60. package/dist/engines/little-3d-engine/textures/dynamic/canvas-texture.d.ts +1 -0
  61. package/dist/engines/little-3d-engine/textures/dynamic/canvas-texture.js +8 -0
  62. package/dist/engines/little-3d-engine/textures/dynamic/shine.d.ts +2 -0
  63. package/dist/engines/little-3d-engine/textures/dynamic/shine.js +14 -0
  64. package/dist/engines/little-3d-engine/textures/dynamic/star.d.ts +14 -0
  65. package/dist/engines/little-3d-engine/textures/dynamic/star.js +31 -0
  66. package/dist/engines/little-3d-engine/textures/dynamic/streak.d.ts +5 -0
  67. package/dist/engines/little-3d-engine/textures/dynamic/streak.js +20 -0
  68. package/dist/index.d.ts +2 -1
  69. package/dist/prefabs/charged-orb.d.ts +17 -0
  70. package/dist/prefabs/charged-orb.js +37 -0
  71. package/dist/prefabs/crystal-comet.d.ts +4 -0
  72. package/dist/prefabs/crystal-comet.js +41 -0
  73. package/dist/prefabs/ghost-train.d.ts +10 -0
  74. package/dist/prefabs/ghost-train.js +37 -0
  75. package/dist/prefabs/grid-assembly.d.ts +13 -0
  76. package/dist/prefabs/grid-assembly.js +15 -0
  77. package/dist/prefabs/monochrome-streak.d.ts +4 -0
  78. package/dist/prefabs/monochrome-streak.js +25 -0
  79. package/dist/prefabs/plane-star-trail.d.ts +4 -0
  80. package/dist/prefabs/plane-star-trail.js +40 -0
  81. package/dist/prefabs/prefabs.d.ts +10 -0
  82. package/dist/prefabs/prefabs.js +9 -0
  83. package/dist/prefabs/pulsing-starfield.d.ts +4 -0
  84. package/dist/prefabs/pulsing-starfield.js +27 -0
  85. package/dist/prefabs/rocket-launch.d.ts +10 -0
  86. package/dist/prefabs/rocket-launch.js +16 -0
  87. package/dist/prefabs/spinner.d.ts +7 -0
  88. package/dist/prefabs/spinner.js +17 -0
  89. package/dist/prefabs/star-swarm.d.ts +4 -0
  90. package/dist/prefabs/star-swarm.js +27 -0
  91. package/dist/prefabs/types.d.ts +46 -0
  92. package/dist/prefabs/types.js +1 -0
  93. package/dist/umd/spinner.global.js +2407 -85
  94. package/dist/umd/spinner.global.min.js +60 -5
  95. package/package.json +43 -2
  96. /package/dist/engines/little-3d-engine/shapes/{cube.js → primitives/cube.js} +0 -0
  97. /package/dist/engines/little-3d-engine/shapes/{octahedron.js → primitives/octahedron.js} +0 -0
  98. /package/dist/engines/little-3d-engine/shapes/{pyramid.js → primitives/pyramid.js} +0 -0
  99. /package/dist/engines/little-3d-engine/shapes/{cube-sphere.js → primitives/spheres/cube-sphere.js} +0 -0
  100. /package/dist/engines/little-3d-engine/shapes/{uv-sphere.js → primitives/spheres/uv-sphere.js} +0 -0
  101. /package/dist/engines/little-3d-engine/shapes/{tetrahedron.js → primitives/tetrahedron.js} +0 -0
@@ -0,0 +1,712 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res, err) => function __init() {
7
+ if (err) throw err[0];
8
+ try {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ } catch (e) {
11
+ throw err = [e], e;
12
+ }
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+
28
+ // src/engines/little-3d-engine/core/math.ts
29
+ function subtract(a, b) {
30
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
31
+ }
32
+ function cross(a, b) {
33
+ return {
34
+ x: a.y * b.z - a.z * b.y,
35
+ y: a.z * b.x - a.x * b.z,
36
+ z: a.x * b.y - a.y * b.x
37
+ };
38
+ }
39
+ function normalize(v) {
40
+ const length = Math.hypot(v.x, v.y, v.z);
41
+ if (length === 0) return { x: 0, y: 0, z: 0 };
42
+ return { x: v.x / length, y: v.y / length, z: v.z / length };
43
+ }
44
+ function multiply(a, b) {
45
+ const out = new Array(16);
46
+ for (let col = 0; col < 4; col++) {
47
+ for (let row = 0; row < 4; row++) {
48
+ let sum = 0;
49
+ for (let k = 0; k < 4; k++) {
50
+ sum += a[k * 4 + row] * b[col * 4 + k];
51
+ }
52
+ out[col * 4 + row] = sum;
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ var init_math = __esm({
58
+ "src/engines/little-3d-engine/core/math.ts"() {
59
+ "use strict";
60
+ }
61
+ });
62
+
63
+ // src/engines/little-3d-engine/core/geometry.ts
64
+ function parseColor(color) {
65
+ const hex = color.trim().replace("#", "");
66
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
67
+ const n = parseInt(full, 16);
68
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
69
+ }
70
+ function expandToTriangles(mesh) {
71
+ let triangles = 0;
72
+ for (const face of mesh.faces) triangles += Math.max(0, face.indices.length - 2);
73
+ const positions = new Float32Array(triangles * 9);
74
+ const normals = new Float32Array(triangles * 9);
75
+ const colors = new Float32Array(triangles * 9);
76
+ let o = 0;
77
+ for (const face of mesh.faces) {
78
+ const v0 = mesh.vertices[face.indices[0]];
79
+ const v1 = mesh.vertices[face.indices[1]];
80
+ const v2 = mesh.vertices[face.indices[2]];
81
+ const normal = normalize(cross(subtract(v1, v0), subtract(v2, v0)));
82
+ const [r, g, b] = parseColor(face.color);
83
+ const cr = r / 255;
84
+ const cg = g / 255;
85
+ const cb = b / 255;
86
+ for (let k = 1; k < face.indices.length - 1; k++) {
87
+ const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
88
+ for (const index of tri) {
89
+ const v = mesh.vertices[index];
90
+ positions[o] = v.x;
91
+ positions[o + 1] = v.y;
92
+ positions[o + 2] = v.z;
93
+ normals[o] = normal.x;
94
+ normals[o + 1] = normal.y;
95
+ normals[o + 2] = normal.z;
96
+ colors[o] = cr;
97
+ colors[o + 1] = cg;
98
+ colors[o + 2] = cb;
99
+ o += 3;
100
+ }
101
+ }
102
+ }
103
+ return { positions, normals, colors, count: positions.length / 3 };
104
+ }
105
+ var init_geometry = __esm({
106
+ "src/engines/little-3d-engine/core/geometry.ts"() {
107
+ "use strict";
108
+ init_math();
109
+ }
110
+ });
111
+
112
+ // src/engines/little-3d-engine/renderer.ts
113
+ function opacity(value, fallback) {
114
+ return Math.max(0, Math.min(1, value ?? fallback));
115
+ }
116
+ function resolveTwoSidedOpacity(transparency) {
117
+ const front = opacity(
118
+ transparency.frontOpacity ?? transparency.opacity,
119
+ DEFAULT_FRONT_OPACITY
120
+ );
121
+ const backFallback = transparency.opacity === void 0 ? DEFAULT_BACK_OPACITY : front * (2 / 3);
122
+ return {
123
+ front,
124
+ back: opacity(transparency.backOpacity, backFallback)
125
+ };
126
+ }
127
+ var DEFAULT_ONE_SIDED_OPACITY, DEFAULT_BACK_OPACITY, DEFAULT_FRONT_OPACITY;
128
+ var init_renderer = __esm({
129
+ "src/engines/little-3d-engine/renderer.ts"() {
130
+ "use strict";
131
+ DEFAULT_ONE_SIDED_OPACITY = 0.35;
132
+ DEFAULT_BACK_OPACITY = 0.84;
133
+ DEFAULT_FRONT_OPACITY = 0.56;
134
+ }
135
+ });
136
+
137
+ // src/engines/little-3d-engine/renderers/webgpu.ts
138
+ var WGSL, CLIP_Z_FIX, UNIFORM_STRIDE, WebGPURenderer;
139
+ var init_webgpu = __esm({
140
+ "src/engines/little-3d-engine/renderers/webgpu.ts"() {
141
+ "use strict";
142
+ init_geometry();
143
+ init_math();
144
+ init_renderer();
145
+ WGSL = `
146
+ struct Uniforms {
147
+ viewProj: mat4x4<f32>,
148
+ model: mat4x4<f32>,
149
+ toLight: vec4<f32>,
150
+ params: vec4<f32>,
151
+ };
152
+ @group(0) @binding(0) var<uniform> u: Uniforms;
153
+
154
+ struct VSOut {
155
+ @builtin(position) position: vec4<f32>,
156
+ @location(0) normal: vec3<f32>,
157
+ @location(1) color: vec3<f32>,
158
+ };
159
+
160
+ @vertex
161
+ fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
162
+ var out: VSOut;
163
+ let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
164
+ out.normal = m * normal;
165
+ out.color = color;
166
+ out.position = u.viewProj * u.model * vec4<f32>(pos, 1.0);
167
+ return out;
168
+ }
169
+
170
+ @fragment
171
+ fn fs(in: VSOut) -> @location(0) vec4<f32> {
172
+ let lambert = max(dot(normalize(in.normal), normalize(u.toLight.xyz)), 0.0);
173
+ let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
174
+ return vec4<f32>(in.color * brightness, u.params.z);
175
+ }
176
+ `;
177
+ CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
178
+ UNIFORM_STRIDE = 256;
179
+ WebGPURenderer = class {
180
+ constructor(options = {}) {
181
+ this.uniformCapacity = 0;
182
+ this.depthSize = "";
183
+ this.destroyed = false;
184
+ this.cache = /* @__PURE__ */ new Map();
185
+ if (options.background) {
186
+ const [r, g, b] = parseColor(options.background);
187
+ this.clearValue = { r: r / 255, g: g / 255, b: b / 255, a: 1 };
188
+ this.alphaMode = "opaque";
189
+ } else {
190
+ this.clearValue = { r: 0, g: 0, b: 0, a: 0 };
191
+ this.alphaMode = "premultiplied";
192
+ }
193
+ }
194
+ async init(canvas) {
195
+ const gpu = navigator.gpu;
196
+ if (!gpu) throw new Error("3d-spinner: WebGPU is not supported in this browser.");
197
+ const adapter = await gpu.requestAdapter();
198
+ if (!adapter) throw new Error("3d-spinner: no WebGPU adapter is available.");
199
+ const device = await adapter.requestDevice();
200
+ if (this.destroyed) {
201
+ device.destroy?.();
202
+ return;
203
+ }
204
+ const context = canvas.getContext("webgpu");
205
+ if (!context) throw new Error("3d-spinner: could not get a WebGPU canvas context.");
206
+ const format = gpu.getPreferredCanvasFormat();
207
+ context.configure({ device, format, alphaMode: this.alphaMode });
208
+ const module2 = device.createShaderModule({ code: WGSL });
209
+ const stage = globalThis.GPUShaderStage;
210
+ const layout = device.createBindGroupLayout({
211
+ entries: [
212
+ {
213
+ binding: 0,
214
+ visibility: stage.VERTEX | stage.FRAGMENT,
215
+ buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 160 }
216
+ }
217
+ ]
218
+ });
219
+ const vertexBuffer = (location) => ({
220
+ arrayStride: 12,
221
+ attributes: [{ shaderLocation: location, offset: 0, format: "float32x3" }]
222
+ });
223
+ const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
224
+ const blend = {
225
+ color: {
226
+ srcFactor: "src-alpha",
227
+ dstFactor: "one-minus-src-alpha",
228
+ operation: "add"
229
+ },
230
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
231
+ };
232
+ const pipeline = (cullMode, transparent) => device.createRenderPipeline({
233
+ layout: pipelineLayout,
234
+ vertex: {
235
+ module: module2,
236
+ entryPoint: "vs",
237
+ buffers: [vertexBuffer(0), vertexBuffer(1), vertexBuffer(2)]
238
+ },
239
+ fragment: {
240
+ module: module2,
241
+ entryPoint: "fs",
242
+ targets: [{ format, ...transparent ? { blend } : {} }]
243
+ },
244
+ primitive: { topology: "triangle-list", cullMode, frontFace: "ccw" },
245
+ depthStencil: {
246
+ format: "depth24plus",
247
+ depthWriteEnabled: !transparent,
248
+ depthCompare: "less"
249
+ }
250
+ });
251
+ this.pipeline = pipeline("back", false);
252
+ this.transparentBackPipeline = pipeline("front", true);
253
+ this.transparentFrontPipeline = pipeline("back", true);
254
+ this.canvas = canvas;
255
+ this.device = device;
256
+ this.context = context;
257
+ }
258
+ resize() {
259
+ this.ensureDepth();
260
+ }
261
+ ensureDepth() {
262
+ const canvas = this.canvas;
263
+ if (!this.device || !canvas) return;
264
+ const width = Math.max(1, canvas.width);
265
+ const height = Math.max(1, canvas.height);
266
+ const key = `${width}x${height}`;
267
+ if (key === this.depthSize && this.depthTexture) return;
268
+ this.depthTexture?.destroy?.();
269
+ this.depthTexture = this.device.createTexture({
270
+ size: { width, height },
271
+ format: "depth24plus",
272
+ usage: globalThis.GPUTextureUsage.RENDER_ATTACHMENT
273
+ });
274
+ this.depthSize = key;
275
+ }
276
+ buffers(mesh) {
277
+ const cached = this.cache.get(mesh);
278
+ if (cached) return cached;
279
+ const data = expandToTriangles(mesh);
280
+ const usage = globalThis.GPUBufferUsage.VERTEX | globalThis.GPUBufferUsage.COPY_DST;
281
+ const upload = (array) => {
282
+ const buffer = this.device.createBuffer({ size: array.byteLength, usage });
283
+ this.device.queue.writeBuffer(buffer, 0, array);
284
+ return buffer;
285
+ };
286
+ const result = {
287
+ position: upload(data.positions),
288
+ normal: upload(data.normals),
289
+ color: upload(data.colors),
290
+ count: data.count
291
+ };
292
+ this.cache.set(mesh, result);
293
+ return result;
294
+ }
295
+ ensureUniformCapacity(draws) {
296
+ if (draws <= this.uniformCapacity && this.uniformBuffer) return;
297
+ this.uniformBuffer?.destroy?.();
298
+ this.uniformBuffer = this.device.createBuffer({
299
+ size: Math.max(1, draws) * UNIFORM_STRIDE,
300
+ usage: globalThis.GPUBufferUsage.UNIFORM | globalThis.GPUBufferUsage.COPY_DST
301
+ });
302
+ this.uniformCapacity = draws;
303
+ }
304
+ render(frame) {
305
+ if (this.destroyed || !this.device || !this.context || !this.pipeline) return;
306
+ if (frame.width === 0 || frame.height === 0 || frame.items.length === 0) return;
307
+ this.ensureDepth();
308
+ const draws = [];
309
+ for (const item of frame.items) {
310
+ if (!item.transparency) draws.push({ item, opacity: 1, pipeline: this.pipeline });
311
+ }
312
+ for (const item of frame.items) {
313
+ const transparency = item.transparency;
314
+ if (!transparency) continue;
315
+ if (transparency.mode === "two-sided") {
316
+ const resolved = resolveTwoSidedOpacity(transparency);
317
+ draws.push({
318
+ item,
319
+ opacity: resolved.back,
320
+ pipeline: this.transparentBackPipeline
321
+ });
322
+ draws.push({
323
+ item,
324
+ opacity: resolved.front,
325
+ pipeline: this.transparentFrontPipeline
326
+ });
327
+ } else {
328
+ draws.push({
329
+ item,
330
+ opacity: opacity(transparency.opacity, DEFAULT_ONE_SIDED_OPACITY),
331
+ pipeline: this.transparentFrontPipeline
332
+ });
333
+ }
334
+ }
335
+ this.ensureUniformCapacity(draws.length);
336
+ const viewProj = multiply(CLIP_Z_FIX, frame.viewProjection);
337
+ const layout = this.pipeline.getBindGroupLayout(0);
338
+ const bindGroup = this.device.createBindGroup({
339
+ layout,
340
+ entries: [
341
+ { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 160 } }
342
+ ]
343
+ });
344
+ draws.forEach((draw, i) => {
345
+ const data = new Float32Array(UNIFORM_STRIDE / 4);
346
+ data.set(viewProj, 0);
347
+ data.set(draw.item.model, 16);
348
+ data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
349
+ data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
350
+ this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
351
+ });
352
+ const encoder = this.device.createCommandEncoder();
353
+ const pass = encoder.beginRenderPass({
354
+ colorAttachments: [
355
+ {
356
+ view: this.context.getCurrentTexture().createView(),
357
+ clearValue: this.clearValue,
358
+ loadOp: "clear",
359
+ storeOp: "store"
360
+ }
361
+ ],
362
+ depthStencilAttachment: {
363
+ view: this.depthTexture.createView(),
364
+ depthClearValue: 1,
365
+ depthLoadOp: "clear",
366
+ depthStoreOp: "store"
367
+ }
368
+ });
369
+ draws.forEach((draw, i) => {
370
+ const mesh = this.buffers(draw.item.mesh);
371
+ pass.setPipeline(draw.pipeline);
372
+ pass.setBindGroup(0, bindGroup, [i * UNIFORM_STRIDE]);
373
+ pass.setVertexBuffer(0, mesh.position);
374
+ pass.setVertexBuffer(1, mesh.normal);
375
+ pass.setVertexBuffer(2, mesh.color);
376
+ pass.draw(mesh.count);
377
+ });
378
+ pass.end();
379
+ this.device.queue.submit([encoder.finish()]);
380
+ }
381
+ destroy() {
382
+ this.destroyed = true;
383
+ for (const mesh of this.cache.values()) {
384
+ mesh.position.destroy?.();
385
+ mesh.normal.destroy?.();
386
+ mesh.color.destroy?.();
387
+ }
388
+ this.cache.clear();
389
+ this.uniformBuffer?.destroy?.();
390
+ this.depthTexture?.destroy?.();
391
+ this.device?.destroy?.();
392
+ this.device = void 0;
393
+ this.context = void 0;
394
+ this.pipeline = void 0;
395
+ this.transparentBackPipeline = void 0;
396
+ this.transparentFrontPipeline = void 0;
397
+ this.uniformBuffer = void 0;
398
+ this.depthTexture = void 0;
399
+ this.canvas = void 0;
400
+ }
401
+ };
402
+ }
403
+ });
404
+
405
+ // src/engines/little-3d-engine/renderers/webgpu-textured.ts
406
+ var webgpu_textured_exports = {};
407
+ __export(webgpu_textured_exports, {
408
+ WebGPUTexturedRenderer: () => WebGPUTexturedRenderer
409
+ });
410
+ module.exports = __toCommonJS(webgpu_textured_exports);
411
+ init_geometry();
412
+ init_math();
413
+ init_renderer();
414
+
415
+ // src/engines/little-3d-engine/renderers/textured-helpers.ts
416
+ function planarUVs(mesh) {
417
+ let minX = Infinity;
418
+ let minY = Infinity;
419
+ let maxX = -Infinity;
420
+ let maxY = -Infinity;
421
+ for (const v of mesh.vertices) {
422
+ minX = Math.min(minX, v.x);
423
+ minY = Math.min(minY, v.y);
424
+ maxX = Math.max(maxX, v.x);
425
+ maxY = Math.max(maxY, v.y);
426
+ }
427
+ const width = maxX - minX || 1;
428
+ const height = maxY - minY || 1;
429
+ let triangles = 0;
430
+ for (const face of mesh.faces) triangles += Math.max(0, face.indices.length - 2);
431
+ const uvs = new Float32Array(triangles * 6);
432
+ let o = 0;
433
+ for (const face of mesh.faces) {
434
+ for (let k = 1; k < face.indices.length - 1; k++) {
435
+ for (const index of [face.indices[0], face.indices[k], face.indices[k + 1]]) {
436
+ const v = mesh.vertices[index];
437
+ uvs[o] = (v.x - minX) / width;
438
+ uvs[o + 1] = 1 - (v.y - minY) / height;
439
+ o += 2;
440
+ }
441
+ }
442
+ }
443
+ return uvs;
444
+ }
445
+
446
+ // src/engines/little-3d-engine/renderers/webgpu-textured.ts
447
+ init_webgpu();
448
+ var WGSL2 = `
449
+ struct Uniforms {
450
+ viewProj: mat4x4<f32>,
451
+ model: mat4x4<f32>,
452
+ params: vec4<f32>,
453
+ };
454
+ @group(0) @binding(0) var<uniform> u: Uniforms;
455
+ @group(0) @binding(1) var tex: texture_2d<f32>;
456
+ @group(0) @binding(2) var samp: sampler;
457
+
458
+ struct VSOut {
459
+ @builtin(position) position: vec4<f32>,
460
+ @location(0) uv: vec2<f32>,
461
+ @location(1) color: vec3<f32>,
462
+ };
463
+
464
+ @vertex
465
+ fn vs(@location(0) pos: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) color: vec3<f32>) -> VSOut {
466
+ var out: VSOut;
467
+ out.uv = uv;
468
+ out.color = color;
469
+ out.position = u.viewProj * u.model * vec4<f32>(pos, 1.0);
470
+ return out;
471
+ }
472
+
473
+ @fragment
474
+ fn fs(in: VSOut) -> @location(0) vec4<f32> {
475
+ let t = textureSample(tex, samp, in.uv);
476
+ return vec4<f32>(t.rgb * in.color, t.a * u.params.x);
477
+ }
478
+ `;
479
+ var CLIP_Z_FIX2 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
480
+ var UNIFORM_STRIDE2 = 256;
481
+ function itemOpacity(transparency) {
482
+ if (!transparency) return 1;
483
+ if (transparency.mode === "two-sided") return resolveTwoSidedOpacity(transparency).front;
484
+ return opacity(transparency.opacity, DEFAULT_ONE_SIDED_OPACITY);
485
+ }
486
+ var WebGPUTexturedRenderer = class extends WebGPURenderer {
487
+ constructor() {
488
+ super(...arguments);
489
+ this.texturedCapacity = 0;
490
+ this.sources = /* @__PURE__ */ new Map();
491
+ this.textures = /* @__PURE__ */ new Map();
492
+ this.retired = [];
493
+ this.texturedBuffers = /* @__PURE__ */ new Map();
494
+ this.bindGroups = /* @__PURE__ */ new Map();
495
+ }
496
+ /** Texture every instance of `mesh` with `source`. Call any time, also before init. */
497
+ setTexture(mesh, source) {
498
+ this.sources.set(mesh, source);
499
+ }
500
+ async init(canvas) {
501
+ await super.init(canvas);
502
+ const device = this.device;
503
+ if (!device) return;
504
+ const format = navigator.gpu.getPreferredCanvasFormat();
505
+ const module2 = device.createShaderModule({ code: WGSL2 });
506
+ const stage = globalThis.GPUShaderStage;
507
+ const layout = device.createBindGroupLayout({
508
+ entries: [
509
+ {
510
+ binding: 0,
511
+ visibility: stage.VERTEX | stage.FRAGMENT,
512
+ buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 144 }
513
+ },
514
+ { binding: 1, visibility: stage.FRAGMENT, texture: {} },
515
+ { binding: 2, visibility: stage.FRAGMENT, sampler: {} }
516
+ ]
517
+ });
518
+ const vertexBuffer = (location, components) => ({
519
+ arrayStride: components * 4,
520
+ attributes: [{ shaderLocation: location, offset: 0, format: `float32x${components}` }]
521
+ });
522
+ this.texturedPipeline = device.createRenderPipeline({
523
+ layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
524
+ vertex: {
525
+ module: module2,
526
+ entryPoint: "vs",
527
+ buffers: [vertexBuffer(0, 3), vertexBuffer(1, 2), vertexBuffer(2, 3)]
528
+ },
529
+ fragment: {
530
+ module: module2,
531
+ entryPoint: "fs",
532
+ targets: [
533
+ {
534
+ format,
535
+ blend: {
536
+ color: {
537
+ srcFactor: "src-alpha",
538
+ dstFactor: "one-minus-src-alpha",
539
+ operation: "add"
540
+ },
541
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
542
+ }
543
+ }
544
+ ]
545
+ },
546
+ primitive: { topology: "triangle-list", cullMode: "back", frontFace: "ccw" },
547
+ depthStencil: {
548
+ format: "depth24plus",
549
+ depthWriteEnabled: false,
550
+ depthCompare: "less"
551
+ }
552
+ });
553
+ this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
554
+ }
555
+ textureFor(mesh) {
556
+ const cached = this.textures.get(mesh);
557
+ if (cached) return cached;
558
+ const device = this.device;
559
+ const usage = globalThis.GPUTextureUsage;
560
+ const white = device.createTexture({
561
+ size: { width: 1, height: 1 },
562
+ format: "rgba8unorm",
563
+ usage: usage.TEXTURE_BINDING | usage.COPY_DST
564
+ });
565
+ device.queue.writeTexture(
566
+ { texture: white },
567
+ new Uint8Array([255, 255, 255, 255]),
568
+ {},
569
+ { width: 1, height: 1 }
570
+ );
571
+ this.textures.set(mesh, white);
572
+ const upload = async (source2) => {
573
+ const image = source2 instanceof HTMLImageElement ? await createImageBitmap(source2) : source2;
574
+ if (this.destroyed || !this.device || this.textures.get(mesh) !== white) return;
575
+ const width = image.width || 1;
576
+ const height = image.height || 1;
577
+ const texture = this.device.createTexture({
578
+ size: { width, height },
579
+ format: "rgba8unorm",
580
+ usage: usage.TEXTURE_BINDING | usage.COPY_DST | usage.RENDER_ATTACHMENT
581
+ });
582
+ this.device.queue.copyExternalImageToTexture(
583
+ { source: image },
584
+ { texture },
585
+ { width, height }
586
+ );
587
+ this.retired.push(white);
588
+ this.textures.set(mesh, texture);
589
+ this.bindGroups.delete(mesh);
590
+ };
591
+ const source = this.sources.get(mesh);
592
+ if (typeof source === "string") {
593
+ const image = new Image();
594
+ image.onload = () => void upload(image);
595
+ image.src = source;
596
+ } else {
597
+ void upload(source);
598
+ }
599
+ return this.textures.get(mesh);
600
+ }
601
+ buffersFor(mesh) {
602
+ const cached = this.texturedBuffers.get(mesh);
603
+ if (cached) return cached;
604
+ const data = expandToTriangles(mesh);
605
+ const usage = globalThis.GPUBufferUsage.VERTEX | globalThis.GPUBufferUsage.COPY_DST;
606
+ const upload = (array) => {
607
+ const buffer = this.device.createBuffer({ size: array.byteLength, usage });
608
+ this.device.queue.writeBuffer(buffer, 0, array);
609
+ return buffer;
610
+ };
611
+ const result = {
612
+ position: upload(data.positions),
613
+ uv: upload(planarUVs(mesh)),
614
+ color: upload(data.colors),
615
+ count: data.count
616
+ };
617
+ this.texturedBuffers.set(mesh, result);
618
+ return result;
619
+ }
620
+ bindGroupFor(mesh) {
621
+ const texture = this.textureFor(mesh);
622
+ const cached = this.bindGroups.get(mesh);
623
+ if (cached && cached.buffer === this.texturedUniforms && cached.texture === texture) {
624
+ return cached.group;
625
+ }
626
+ const group = this.device.createBindGroup({
627
+ layout: this.texturedPipeline.getBindGroupLayout(0),
628
+ entries: [
629
+ { binding: 0, resource: { buffer: this.texturedUniforms, offset: 0, size: 144 } },
630
+ { binding: 1, resource: texture.createView() },
631
+ { binding: 2, resource: this.sampler }
632
+ ]
633
+ });
634
+ this.bindGroups.set(mesh, { group, buffer: this.texturedUniforms, texture });
635
+ return group;
636
+ }
637
+ render(frame) {
638
+ const plain = [];
639
+ const textured = [];
640
+ for (const item of frame.items) {
641
+ (this.sources.has(item.mesh) ? textured : plain).push(item);
642
+ }
643
+ super.render(textured.length ? { ...frame, items: plain } : frame);
644
+ if (!textured.length) return;
645
+ if (this.destroyed || !this.device || !this.context || !this.texturedPipeline) return;
646
+ if (frame.width === 0 || frame.height === 0) return;
647
+ this.ensureDepth();
648
+ if (textured.length > this.texturedCapacity || !this.texturedUniforms) {
649
+ this.texturedUniforms?.destroy?.();
650
+ this.texturedUniforms = this.device.createBuffer({
651
+ size: textured.length * UNIFORM_STRIDE2,
652
+ usage: globalThis.GPUBufferUsage.UNIFORM | globalThis.GPUBufferUsage.COPY_DST
653
+ });
654
+ this.texturedCapacity = textured.length;
655
+ }
656
+ const viewProj = multiply(CLIP_Z_FIX2, frame.viewProjection);
657
+ textured.forEach((item, i) => {
658
+ const data = new Float32Array(UNIFORM_STRIDE2 / 4);
659
+ data.set(viewProj, 0);
660
+ data.set(item.model, 16);
661
+ data.set([itemOpacity(item.transparency), 0, 0, 0], 32);
662
+ this.device.queue.writeBuffer(this.texturedUniforms, i * UNIFORM_STRIDE2, data);
663
+ });
664
+ const cleared = plain.length > 0;
665
+ const encoder = this.device.createCommandEncoder();
666
+ const pass = encoder.beginRenderPass({
667
+ colorAttachments: [
668
+ {
669
+ view: this.context.getCurrentTexture().createView(),
670
+ clearValue: this.clearValue,
671
+ loadOp: cleared ? "load" : "clear",
672
+ storeOp: "store"
673
+ }
674
+ ],
675
+ depthStencilAttachment: {
676
+ view: this.depthTexture.createView(),
677
+ depthClearValue: 1,
678
+ depthLoadOp: cleared ? "load" : "clear",
679
+ depthStoreOp: "store"
680
+ }
681
+ });
682
+ pass.setPipeline(this.texturedPipeline);
683
+ textured.forEach((item, i) => {
684
+ const mesh = this.buffersFor(item.mesh);
685
+ pass.setBindGroup(0, this.bindGroupFor(item.mesh), [i * UNIFORM_STRIDE2]);
686
+ pass.setVertexBuffer(0, mesh.position);
687
+ pass.setVertexBuffer(1, mesh.uv);
688
+ pass.setVertexBuffer(2, mesh.color);
689
+ pass.draw(mesh.count);
690
+ });
691
+ pass.end();
692
+ this.device.queue.submit([encoder.finish()]);
693
+ }
694
+ destroy() {
695
+ for (const texture of this.textures.values()) texture.destroy?.();
696
+ for (const texture of this.retired.splice(0)) texture.destroy?.();
697
+ for (const buffers of this.texturedBuffers.values()) {
698
+ buffers.position.destroy?.();
699
+ buffers.uv.destroy?.();
700
+ buffers.color.destroy?.();
701
+ }
702
+ this.textures.clear();
703
+ this.texturedBuffers.clear();
704
+ this.bindGroups.clear();
705
+ this.sources.clear();
706
+ this.texturedUniforms?.destroy?.();
707
+ this.texturedUniforms = void 0;
708
+ this.texturedPipeline = void 0;
709
+ this.sampler = void 0;
710
+ super.destroy();
711
+ }
712
+ };