3d-spinner 0.9.3 → 0.9.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 (62) hide show
  1. package/dist/animations/charged-orb.js +4 -2
  2. package/dist/animations/ghost-train.d.ts +72 -0
  3. package/dist/animations/ghost-train.js +249 -0
  4. package/dist/animations/grid-assembly.d.ts +11 -8
  5. package/dist/animations/grid-assembly.js +44 -30
  6. package/dist/animations/rocket-launch.d.ts +58 -0
  7. package/dist/animations/rocket-launch.js +375 -0
  8. package/dist/animations/spin.d.ts +3 -1
  9. package/dist/animations/spin.js +6 -1
  10. package/dist/cjs/animations/charged-orb.cjs +170 -28
  11. package/dist/cjs/animations/grid-assembly.cjs +192 -172
  12. package/dist/cjs/animations/object-motion.cjs +156 -24
  13. package/dist/cjs/animations/particles.cjs +167 -26
  14. package/dist/cjs/animations/spin.cjs +169 -27
  15. package/dist/cjs/engines/little-3d-engine/little-3d-engine.cjs +215 -55
  16. package/dist/cjs/engines/little-3d-engine/loaders/obj.cjs +51 -13
  17. package/dist/cjs/engines/little-3d-engine/renderers/canvas2d-textured.cjs +56 -6
  18. package/dist/cjs/engines/little-3d-engine/renderers/webgl-textured.cjs +55 -6
  19. package/dist/cjs/engines/little-3d-engine/renderers/webgpu-textured.cjs +64 -11
  20. package/dist/cjs/prefabs/prefabs.cjs +817 -205
  21. package/dist/engines/little-3d-engine/core/geometry.d.ts +15 -2
  22. package/dist/engines/little-3d-engine/core/geometry.js +25 -3
  23. package/dist/engines/little-3d-engine/core/light.d.ts +28 -4
  24. package/dist/engines/little-3d-engine/core/light.js +48 -7
  25. package/dist/engines/little-3d-engine/core/mesh.d.ts +33 -0
  26. package/dist/engines/little-3d-engine/core/mesh.js +12 -0
  27. package/dist/engines/little-3d-engine/little-3d-engine.d.ts +2 -2
  28. package/dist/engines/little-3d-engine/little-3d-engine.js +1 -1
  29. package/dist/engines/little-3d-engine/loaders/obj.d.ts +8 -7
  30. package/dist/engines/little-3d-engine/loaders/obj.js +74 -20
  31. package/dist/engines/little-3d-engine/renderers/canvas2d.js +23 -1
  32. package/dist/engines/little-3d-engine/renderers/webgl.js +34 -5
  33. package/dist/engines/little-3d-engine/renderers/webgpu.js +43 -10
  34. package/dist/engines/little-3d-engine/shapes/complex/plane.d.ts +8 -3
  35. package/dist/engines/little-3d-engine/shapes/complex/plane.js +10 -4
  36. package/dist/engines/little-3d-engine/shapes/primitives/cube.d.ts +3 -2
  37. package/dist/engines/little-3d-engine/shapes/primitives/cube.js +4 -2
  38. package/dist/engines/little-3d-engine/shapes/primitives/octahedron.d.ts +3 -2
  39. package/dist/engines/little-3d-engine/shapes/primitives/octahedron.js +4 -2
  40. package/dist/engines/little-3d-engine/shapes/primitives/pyramid.d.ts +3 -2
  41. package/dist/engines/little-3d-engine/shapes/primitives/pyramid.js +4 -2
  42. package/dist/engines/little-3d-engine/shapes/primitives/quad.d.ts +3 -2
  43. package/dist/engines/little-3d-engine/shapes/primitives/quad.js +4 -2
  44. package/dist/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.d.ts +3 -2
  45. package/dist/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.js +4 -2
  46. package/dist/engines/little-3d-engine/shapes/primitives/spheres/icosphere.d.ts +3 -2
  47. package/dist/engines/little-3d-engine/shapes/primitives/spheres/icosphere.js +4 -2
  48. package/dist/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.d.ts +3 -2
  49. package/dist/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.js +4 -2
  50. package/dist/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.d.ts +3 -2
  51. package/dist/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.js +4 -2
  52. package/dist/engines/little-3d-engine/shapes/primitives/tetrahedron.d.ts +3 -2
  53. package/dist/engines/little-3d-engine/shapes/primitives/tetrahedron.js +4 -2
  54. package/dist/engines/little-3d-engine/textures/dynamic/star.d.ts +14 -2
  55. package/dist/engines/little-3d-engine/textures/dynamic/star.js +26 -11
  56. package/dist/prefabs/ghost-train.d.ts +5 -4
  57. package/dist/prefabs/ghost-train.js +17 -24
  58. package/dist/prefabs/rocket-launch.d.ts +7 -5
  59. package/dist/prefabs/rocket-launch.js +10 -53
  60. package/dist/umd/spinner.global.js +877 -167
  61. package/dist/umd/spinner.global.min.js +58 -17
  62. package/package.json +2 -2
@@ -136,7 +136,10 @@ function expandToTriangles(mesh) {
136
136
  const positions = new Float32Array(triangles * 9);
137
137
  const normals = new Float32Array(triangles * 9);
138
138
  const colors = new Float32Array(triangles * 9);
139
+ const emissives = new Float32Array(triangles * 9);
140
+ const speculars = new Float32Array(triangles * 12);
139
141
  let o = 0;
142
+ let so = 0;
140
143
  for (const face of mesh.faces) {
141
144
  const v0 = mesh.vertices[face.indices[0]];
142
145
  const v1 = mesh.vertices[face.indices[1]];
@@ -146,6 +149,15 @@ function expandToTriangles(mesh) {
146
149
  const cr = r / 255;
147
150
  const cg = g / 255;
148
151
  const cb = b / 255;
152
+ const emissive = face.material?.emissive;
153
+ const er = emissive ? emissive[0] : 0;
154
+ const eg = emissive ? emissive[1] : 0;
155
+ const eb = emissive ? emissive[2] : 0;
156
+ const specular = face.material?.specular;
157
+ const sr = specular ? specular[0] : 0;
158
+ const sg = specular ? specular[1] : 0;
159
+ const sb = specular ? specular[2] : 0;
160
+ const sn = specular ? face.material?.shininess ?? 32 : 1;
149
161
  for (let k = 1; k < face.indices.length - 1; k++) {
150
162
  const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
151
163
  for (const index of tri) {
@@ -159,36 +171,19 @@ function expandToTriangles(mesh) {
159
171
  colors[o] = cr;
160
172
  colors[o + 1] = cg;
161
173
  colors[o + 2] = cb;
174
+ emissives[o] = er;
175
+ emissives[o + 1] = eg;
176
+ emissives[o + 2] = eb;
177
+ speculars[so] = sr;
178
+ speculars[so + 1] = sg;
179
+ speculars[so + 2] = sb;
180
+ speculars[so + 3] = sn;
162
181
  o += 3;
182
+ so += 4;
163
183
  }
164
184
  }
165
185
  }
166
- return { positions, normals, colors, count: positions.length / 3 };
167
- }
168
- function midpoint(a, b) {
169
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
170
- }
171
- function sphereFromTriangles(seedVertices, seedFaces, size, detail, colors) {
172
- const radius = size / 2;
173
- let triangles = seedFaces.map((f) => f.map((i) => normalize(seedVertices[i])));
174
- const levels = Math.max(0, Math.floor(detail) - 1);
175
- for (let level = 0; level < levels; level++) {
176
- const next = [];
177
- for (const [a, b, c] of triangles) {
178
- const ab = normalize(midpoint(a, b));
179
- const bc = normalize(midpoint(b, c));
180
- const ca = normalize(midpoint(c, a));
181
- next.push([a, ab, ca], [b, bc, ab], [c, ca, bc], [ab, bc, ca]);
182
- }
183
- triangles = next;
184
- }
185
- const vertices = [];
186
- const faces = triangles.map((tri, i) => {
187
- const base = vertices.length;
188
- vertices.push(scale(tri[0], radius), scale(tri[1], radius), scale(tri[2], radius));
189
- return { indices: [base, base + 1, base + 2], color: colors[i % colors.length] };
190
- });
191
- return { vertices, faces };
186
+ return { positions, normals, colors, emissives, speculars, count: positions.length / 3 };
192
187
  }
193
188
  var init_geometry = __esm({
194
189
  "src/engines/little-3d-engine/core/geometry.ts"() {
@@ -201,13 +196,42 @@ var init_geometry = __esm({
201
196
  function clamp01(value) {
202
197
  return Math.min(1, Math.max(0, value));
203
198
  }
204
- function shadeColor(normal, color, light) {
199
+ function clamp255(value) {
200
+ return Math.round(Math.min(255, Math.max(0, value)));
201
+ }
202
+ function shade(normal, color, light, surface) {
205
203
  const lambert = Math.max(0, dot(normal, light.toLight));
206
204
  const brightness = clamp01(light.ambient + light.intensity * lambert);
207
- const [r, g, b] = parseColor(color);
208
- return `rgb(${Math.round(r * brightness)}, ${Math.round(g * brightness)}, ${Math.round(
209
- b * brightness
210
- )})`;
205
+ const [baseR, baseG, baseB] = parseColor(color);
206
+ let r = baseR * brightness;
207
+ let g = baseG * brightness;
208
+ let b = baseB * brightness;
209
+ const material = surface?.material;
210
+ const specular = material?.specular;
211
+ const viewDir = surface?.viewDir;
212
+ if (specular && viewDir && lambert > 0) {
213
+ const half = normalize({
214
+ x: light.toLight.x + viewDir.x,
215
+ y: light.toLight.y + viewDir.y,
216
+ z: light.toLight.z + viewDir.z
217
+ });
218
+ const shininess = material?.shininess ?? 32;
219
+ const highlight = Math.pow(Math.max(0, dot(normal, half)), shininess) * light.intensity * 255;
220
+ r += highlight * specular[0];
221
+ g += highlight * specular[1];
222
+ b += highlight * specular[2];
223
+ }
224
+ const emissive = material?.emissive;
225
+ if (emissive) {
226
+ r += emissive[0] * 255;
227
+ g += emissive[1] * 255;
228
+ b += emissive[2] * 255;
229
+ }
230
+ return [clamp255(r), clamp255(g), clamp255(b)];
231
+ }
232
+ function shadeColor(normal, color, light, surface) {
233
+ const [r, g, b] = shade(normal, color, light, surface);
234
+ return `rgb(${r}, ${g}, ${b})`;
211
235
  }
212
236
  var DEFAULTS2, Light;
213
237
  var init_light = __esm({
@@ -230,8 +254,8 @@ var init_light = __esm({
230
254
  };
231
255
  }
232
256
  /** Convenience wrapper around {@link shadeColor} using this light. */
233
- shade(normal, color) {
234
- return shadeColor(normal, color, this.params);
257
+ shade(normal, color, surface) {
258
+ return shadeColor(normal, color, this.params, surface);
235
259
  }
236
260
  };
237
261
  }
@@ -271,28 +295,51 @@ var init_webgl = __esm({
271
295
  in vec3 aPos;
272
296
  in vec3 aNormal;
273
297
  in vec3 aColor;
298
+ in vec3 aEmissive;
299
+ in vec4 aSpecular;
274
300
  uniform mat4 uViewProj;
275
301
  uniform mat4 uModel;
276
302
  out vec3 vNormal;
277
303
  out vec3 vColor;
304
+ out vec3 vEmissive;
305
+ out vec4 vSpecular;
306
+ out vec3 vWorldPos;
278
307
  void main() {
279
308
  vNormal = mat3(uModel) * aNormal;
280
309
  vColor = aColor;
281
- gl_Position = uViewProj * uModel * vec4(aPos, 1.0);
310
+ vEmissive = aEmissive;
311
+ vSpecular = aSpecular;
312
+ vec4 world = uModel * vec4(aPos, 1.0);
313
+ vWorldPos = world.xyz;
314
+ gl_Position = uViewProj * world;
282
315
  }`;
283
316
  FRAGMENT_SHADER = `#version 300 es
284
317
  precision mediump float;
285
318
  in vec3 vNormal;
286
319
  in vec3 vColor;
320
+ in vec3 vEmissive;
321
+ in vec4 vSpecular;
322
+ in vec3 vWorldPos;
287
323
  uniform vec3 uToLight;
324
+ uniform vec3 uEye;
288
325
  uniform float uIntensity;
289
326
  uniform float uAmbient;
290
327
  uniform float uOpacity;
291
328
  out vec4 fragColor;
292
329
  void main() {
293
- float lambert = max(dot(normalize(vNormal), normalize(uToLight)), 0.0);
330
+ vec3 normal = normalize(vNormal);
331
+ vec3 toLight = normalize(uToLight);
332
+ float lambert = max(dot(normal, toLight), 0.0);
294
333
  float brightness = clamp(uAmbient + uIntensity * lambert, 0.0, 1.0);
295
- fragColor = vec4(vColor * brightness, uOpacity);
334
+ vec3 lit = vColor * brightness;
335
+ if (lambert > 0.0) {
336
+ vec3 viewDir = normalize(uEye - vWorldPos);
337
+ vec3 halfVec = normalize(toLight + viewDir);
338
+ float highlight = pow(max(dot(normal, halfVec), 0.0), vSpecular.w) * uIntensity;
339
+ lit += highlight * vSpecular.xyz;
340
+ }
341
+ lit += vEmissive;
342
+ fragColor = vec4(lit, uOpacity);
296
343
  }`;
297
344
  WebGLRenderer = class {
298
345
  constructor(options = {}) {
@@ -313,9 +360,12 @@ void main() {
313
360
  aPos: gl.getAttribLocation(this.program, "aPos"),
314
361
  aNormal: gl.getAttribLocation(this.program, "aNormal"),
315
362
  aColor: gl.getAttribLocation(this.program, "aColor"),
363
+ aEmissive: gl.getAttribLocation(this.program, "aEmissive"),
364
+ aSpecular: gl.getAttribLocation(this.program, "aSpecular"),
316
365
  uViewProj: gl.getUniformLocation(this.program, "uViewProj"),
317
366
  uModel: gl.getUniformLocation(this.program, "uModel"),
318
367
  uToLight: gl.getUniformLocation(this.program, "uToLight"),
368
+ uEye: gl.getUniformLocation(this.program, "uEye"),
319
369
  uIntensity: gl.getUniformLocation(this.program, "uIntensity"),
320
370
  uAmbient: gl.getUniformLocation(this.program, "uAmbient"),
321
371
  uOpacity: gl.getUniformLocation(this.program, "uOpacity")
@@ -339,17 +389,19 @@ void main() {
339
389
  const data = expandToTriangles(mesh);
340
390
  const vao = gl.createVertexArray();
341
391
  gl.bindVertexArray(vao);
342
- const attribute = (location, array) => {
392
+ const attribute = (location, array, size = 3) => {
343
393
  if (location < 0) return;
344
394
  const buffer = gl.createBuffer();
345
395
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
346
396
  gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
347
397
  gl.enableVertexAttribArray(location);
348
- gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0);
398
+ gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
349
399
  };
350
400
  attribute(loc.aPos, data.positions);
351
401
  attribute(loc.aNormal, data.normals);
352
402
  attribute(loc.aColor, data.colors);
403
+ attribute(loc.aEmissive, data.emissives);
404
+ attribute(loc.aSpecular, data.speculars, 4);
353
405
  gl.bindVertexArray(null);
354
406
  const result = { vao, count: data.count };
355
407
  this.cache.set(mesh, result);
@@ -364,6 +416,7 @@ void main() {
364
416
  gl.useProgram(this.program);
365
417
  gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
366
418
  gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
419
+ gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
367
420
  gl.uniform1f(loc.uIntensity, frame.light.intensity);
368
421
  gl.uniform1f(loc.uAmbient, frame.light.ambient);
369
422
  gl.disable(gl.BLEND);
@@ -438,6 +491,7 @@ struct Uniforms {
438
491
  model: mat4x4<f32>,
439
492
  toLight: vec4<f32>,
440
493
  params: vec4<f32>,
494
+ eye: vec4<f32>,
441
495
  };
442
496
  @group(0) @binding(0) var<uniform> u: Uniforms;
443
497
 
@@ -445,23 +499,40 @@ struct VSOut {
445
499
  @builtin(position) position: vec4<f32>,
446
500
  @location(0) normal: vec3<f32>,
447
501
  @location(1) color: vec3<f32>,
502
+ @location(2) emissive: vec3<f32>,
503
+ @location(3) specular: vec4<f32>,
504
+ @location(4) worldPos: vec3<f32>,
448
505
  };
449
506
 
450
507
  @vertex
451
- fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
508
+ fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>, @location(3) emissive: vec3<f32>, @location(4) specular: vec4<f32>) -> VSOut {
452
509
  var out: VSOut;
453
510
  let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
454
511
  out.normal = m * normal;
455
512
  out.color = color;
456
- out.position = u.viewProj * u.model * vec4<f32>(pos, 1.0);
513
+ out.emissive = emissive;
514
+ out.specular = specular;
515
+ let world = u.model * vec4<f32>(pos, 1.0);
516
+ out.worldPos = world.xyz;
517
+ out.position = u.viewProj * world;
457
518
  return out;
458
519
  }
459
520
 
460
521
  @fragment
461
522
  fn fs(in: VSOut) -> @location(0) vec4<f32> {
462
- let lambert = max(dot(normalize(in.normal), normalize(u.toLight.xyz)), 0.0);
523
+ let normal = normalize(in.normal);
524
+ let toLight = normalize(u.toLight.xyz);
525
+ let lambert = max(dot(normal, toLight), 0.0);
463
526
  let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
464
- return vec4<f32>(in.color * brightness, u.params.z);
527
+ var lit = in.color * brightness;
528
+ if (lambert > 0.0) {
529
+ let viewDir = normalize(u.eye.xyz - in.worldPos);
530
+ let halfVec = normalize(toLight + viewDir);
531
+ let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
532
+ lit = lit + highlight * in.specular.xyz;
533
+ }
534
+ lit = lit + in.emissive;
535
+ return vec4<f32>(lit, u.params.z);
465
536
  }
466
537
  `;
467
538
  CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
@@ -502,13 +573,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
502
573
  {
503
574
  binding: 0,
504
575
  visibility: stage.VERTEX | stage.FRAGMENT,
505
- buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 160 }
576
+ buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
506
577
  }
507
578
  ]
508
579
  });
509
- const vertexBuffer = (location) => ({
510
- arrayStride: 12,
511
- attributes: [{ shaderLocation: location, offset: 0, format: "float32x3" }]
580
+ const vertexBuffer = (location, components = 3) => ({
581
+ arrayStride: components * 4,
582
+ attributes: [
583
+ { shaderLocation: location, offset: 0, format: `float32x${components}` }
584
+ ]
512
585
  });
513
586
  const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
514
587
  const blend = {
@@ -524,7 +597,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
524
597
  vertex: {
525
598
  module: module2,
526
599
  entryPoint: "vs",
527
- buffers: [vertexBuffer(0), vertexBuffer(1), vertexBuffer(2)]
600
+ buffers: [
601
+ vertexBuffer(0),
602
+ vertexBuffer(1),
603
+ vertexBuffer(2),
604
+ vertexBuffer(3),
605
+ vertexBuffer(4, 4)
606
+ ]
528
607
  },
529
608
  fragment: {
530
609
  module: module2,
@@ -577,6 +656,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
577
656
  position: upload(data.positions),
578
657
  normal: upload(data.normals),
579
658
  color: upload(data.colors),
659
+ emissive: upload(data.emissives),
660
+ specular: upload(data.speculars),
580
661
  count: data.count
581
662
  };
582
663
  this.cache.set(mesh, result);
@@ -628,7 +709,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
628
709
  const bindGroup = this.device.createBindGroup({
629
710
  layout,
630
711
  entries: [
631
- { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 160 } }
712
+ { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
632
713
  ]
633
714
  });
634
715
  draws.forEach((draw, i) => {
@@ -637,6 +718,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
637
718
  data.set(draw.item.model, 16);
638
719
  data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
639
720
  data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
721
+ data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
640
722
  this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
641
723
  });
642
724
  const encoder = this.device.createCommandEncoder();
@@ -663,6 +745,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
663
745
  pass.setVertexBuffer(0, mesh.position);
664
746
  pass.setVertexBuffer(1, mesh.normal);
665
747
  pass.setVertexBuffer(2, mesh.color);
748
+ pass.setVertexBuffer(3, mesh.emissive);
749
+ pass.setVertexBuffer(4, mesh.specular);
666
750
  pass.draw(mesh.count);
667
751
  });
668
752
  pass.end();
@@ -674,6 +758,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
674
758
  mesh.position.destroy?.();
675
759
  mesh.normal.destroy?.();
676
760
  mesh.color.destroy?.();
761
+ mesh.emissive.destroy?.();
762
+ mesh.specular.destroy?.();
677
763
  }
678
764
  this.cache.clear();
679
765
  this.uniformBuffer?.destroy?.();
@@ -756,9 +842,30 @@ var init_canvas2d = __esm({
756
842
  depth += dot(d, d);
757
843
  }
758
844
  depth /= face.indices.length;
845
+ let surface;
846
+ const material = face.material;
847
+ if (material) {
848
+ if (material.specular) {
849
+ let cx = 0;
850
+ let cy = 0;
851
+ let cz = 0;
852
+ for (const i of face.indices) {
853
+ cx += world[i].x;
854
+ cy += world[i].y;
855
+ cz += world[i].z;
856
+ }
857
+ const inv = 1 / face.indices.length;
858
+ const viewDir = normalize(
859
+ subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
860
+ );
861
+ surface = { material, viewDir };
862
+ } else {
863
+ surface = { material };
864
+ }
865
+ }
759
866
  polygons.push({
760
867
  points,
761
- color: shadeColor(normal, face.color, frame.light),
868
+ color: shadeColor(normal, face.color, frame.light, surface),
762
869
  depth,
763
870
  opacity: faceOpacity
764
871
  });
@@ -930,6 +1037,12 @@ init_light();
930
1037
  init_math();
931
1038
 
932
1039
  // src/engines/little-3d-engine/core/mesh.ts
1040
+ function attachMaterial(mesh, material) {
1041
+ if (material) {
1042
+ for (const face of mesh.faces) face.material = material;
1043
+ }
1044
+ return mesh;
1045
+ }
933
1046
  function transform(init) {
934
1047
  return {
935
1048
  position: init?.position ?? { x: 0, y: 0, z: 0 },
@@ -944,7 +1057,7 @@ init_light();
944
1057
 
945
1058
  // src/engines/little-3d-engine/shapes/primitives/cube.ts
946
1059
  var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
947
- function cube(size = 1, colors = DEFAULT_COLORS) {
1060
+ function cube(size = 1, colors = DEFAULT_COLORS, material) {
948
1061
  const h = size / 2;
949
1062
  const vertices = [
950
1063
  { x: -h, y: -h, z: h },
@@ -964,86 +1077,11 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
964
1077
  { indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
965
1078
  { indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
966
1079
  ];
967
- return { vertices, faces };
968
- }
969
-
970
- // src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
971
- var DEFAULT_COLORS2 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
972
- function tetrahedron(size = 1, colors = DEFAULT_COLORS2) {
973
- const s = size / 2;
974
- const vertices = [
975
- { x: s, y: s, z: s },
976
- { x: s, y: -s, z: -s },
977
- { x: -s, y: s, z: -s },
978
- { x: -s, y: -s, z: s }
979
- ];
980
- const faces = [
981
- { indices: [0, 1, 2], color: colors[0 % colors.length] },
982
- { indices: [0, 3, 1], color: colors[1 % colors.length] },
983
- { indices: [0, 2, 3], color: colors[2 % colors.length] },
984
- { indices: [1, 3, 2], color: colors[3 % colors.length] }
985
- ];
986
- return { vertices, faces };
987
- }
988
-
989
- // src/engines/little-3d-engine/shapes/primitives/octahedron.ts
990
- var DEFAULT_COLORS3 = [
991
- "#3b82f6",
992
- "#8b5cf6",
993
- "#ec4899",
994
- "#f59e0b",
995
- "#10b981",
996
- "#ef4444",
997
- "#06b6d4",
998
- "#eab308"
999
- ];
1000
- function octahedron(size = 1, colors = DEFAULT_COLORS3) {
1001
- const r = size / 2;
1002
- const vertices = [
1003
- { x: r, y: 0, z: 0 },
1004
- { x: -r, y: 0, z: 0 },
1005
- { x: 0, y: r, z: 0 },
1006
- { x: 0, y: -r, z: 0 },
1007
- { x: 0, y: 0, z: r },
1008
- { x: 0, y: 0, z: -r }
1009
- ];
1010
- const faces = [
1011
- { indices: [4, 0, 2], color: colors[0 % colors.length] },
1012
- { indices: [4, 2, 1], color: colors[1 % colors.length] },
1013
- { indices: [4, 1, 3], color: colors[2 % colors.length] },
1014
- { indices: [4, 3, 0], color: colors[3 % colors.length] },
1015
- { indices: [5, 2, 0], color: colors[4 % colors.length] },
1016
- { indices: [5, 1, 2], color: colors[5 % colors.length] },
1017
- { indices: [5, 3, 1], color: colors[6 % colors.length] },
1018
- { indices: [5, 0, 3], color: colors[7 % colors.length] }
1019
- ];
1020
- return { vertices, faces };
1021
- }
1022
-
1023
- // src/engines/little-3d-engine/shapes/primitives/pyramid.ts
1024
- var DEFAULT_COLORS4 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
1025
- function pyramid(size = 1, colors = DEFAULT_COLORS4) {
1026
- const h = size / 2;
1027
- const vertices = [
1028
- { x: -h, y: -h, z: h },
1029
- { x: h, y: -h, z: h },
1030
- { x: h, y: -h, z: -h },
1031
- { x: -h, y: -h, z: -h },
1032
- { x: 0, y: h, z: 0 }
1033
- ];
1034
- const faces = [
1035
- { indices: [0, 3, 2, 1], color: colors[0 % colors.length] },
1036
- { indices: [4, 0, 1], color: colors[1 % colors.length] },
1037
- { indices: [4, 1, 2], color: colors[2 % colors.length] },
1038
- { indices: [4, 2, 3], color: colors[3 % colors.length] },
1039
- { indices: [4, 3, 0], color: colors[4 % colors.length] }
1040
- ];
1041
- return { vertices, faces };
1080
+ return attachMaterial({ vertices, faces }, material);
1042
1081
  }
1043
1082
 
1044
1083
  // src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
1045
1084
  init_geometry();
1046
- var DEFAULT_COLORS5 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
1047
1085
  var T = (1 + Math.sqrt(5)) / 2;
1048
1086
  var SEED_VERTICES = [
1049
1087
  { x: -1, y: T, z: 0 },
@@ -1059,31 +1097,6 @@ var SEED_VERTICES = [
1059
1097
  { x: -T, y: 0, z: -1 },
1060
1098
  { x: -T, y: 0, z: 1 }
1061
1099
  ];
1062
- var SEED_FACES = [
1063
- [0, 11, 5],
1064
- [0, 5, 1],
1065
- [0, 1, 7],
1066
- [0, 7, 10],
1067
- [0, 10, 11],
1068
- [1, 5, 9],
1069
- [5, 11, 4],
1070
- [11, 10, 2],
1071
- [10, 7, 6],
1072
- [7, 1, 8],
1073
- [3, 9, 4],
1074
- [3, 4, 2],
1075
- [3, 2, 6],
1076
- [3, 6, 8],
1077
- [3, 8, 9],
1078
- [4, 9, 5],
1079
- [2, 4, 11],
1080
- [6, 2, 10],
1081
- [8, 6, 7],
1082
- [9, 8, 1]
1083
- ];
1084
- function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS5) {
1085
- return sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors);
1086
- }
1087
1100
 
1088
1101
  // src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
1089
1102
  init_geometry();
@@ -1263,6 +1276,7 @@ var FOV = 55 * Math.PI / 180;
1263
1276
  var TWO_PI = Math.PI * 2;
1264
1277
  var INTRO_MS = 900;
1265
1278
  var INTRO_STAGGER_MS = 60;
1279
+ var INTRO_DONE_MS = (COUNT - 1) * INTRO_STAGGER_MS + INTRO_MS;
1266
1280
  var GATE_DOCK = 0.35;
1267
1281
  var GATE_UNDOCK = 0.65;
1268
1282
  var EXIT_HURRY = 2.5;
@@ -1271,15 +1285,11 @@ var SPIN_MS = 380;
1271
1285
  var SPIN_STAGGER_MS = 80;
1272
1286
  var HOLD_MS = 1e3;
1273
1287
  var COLLAPSE_MS = 700;
1288
+ var COLLAPSE_SPREAD_MS = 500;
1274
1289
  var POP_MS = 170;
1275
1290
  var LABEL_FADE_MS = 600;
1276
- var DEFAULT_MESHES = [
1277
- () => cube(1, ["#60a5fa", "#3b82f6", "#2563eb", "#38bdf8", "#0ea5e9", "#1d4ed8"]),
1278
- () => tetrahedron(1, ["#f472b6", "#ec4899", "#db2777", "#f9a8d4"]),
1279
- () => octahedron(1, ["#34d399", "#10b981", "#059669", "#6ee7b7", "#a7f3d0", "#047857", "#4ade80", "#065f46"]),
1280
- () => pyramid(1, ["#fbbf24", "#f59e0b", "#d97706", "#fde68a", "#fcd34d"]),
1281
- () => icosphere(1, 1, ["#a78bfa", "#8b5cf6", "#7c3aed", "#c4b5fd"])
1282
- ];
1291
+ var CUBE_COLORS = ["#8397c6", "#7186b8", "#6176a8", "#93a6cf", "#556a9c", "#7a8fc0"];
1292
+ var DEFAULT_MESHES = [() => cube(1, CUBE_COLORS)];
1283
1293
  function clamp012(value) {
1284
1294
  return Math.max(0, Math.min(1, value));
1285
1295
  }
@@ -1307,6 +1317,9 @@ var GridAssemblyAnimation = class {
1307
1317
  this.dockedAt = new Array(COUNT).fill(Infinity);
1308
1318
  this.tumbleX = [];
1309
1319
  this.tumbleY = [];
1320
+ this.collapseDelay = [];
1321
+ this.popStarted = new Array(COUNT).fill(false);
1322
+ this.maxCollapseDelay = 0;
1310
1323
  this.fades = [];
1311
1324
  this.slots = [];
1312
1325
  this.aspect = 16 / 9;
@@ -1315,7 +1328,6 @@ var GridAssemblyAnimation = class {
1315
1328
  this.allDockedAt = Infinity;
1316
1329
  this.collapseAt = Infinity;
1317
1330
  this.lastNow = 0;
1318
- this.popFading = false;
1319
1331
  this.finished = false;
1320
1332
  const sources = options.meshes && options.meshes.length > 0 ? options.meshes : DEFAULT_MESHES;
1321
1333
  this.meshes = sources.map(resolveMesh);
@@ -1332,7 +1344,9 @@ var GridAssemblyAnimation = class {
1332
1344
  this.slots.push({ x: (col - 2) * spacing, y: (2 - row) * spacing, z: 0 });
1333
1345
  this.tumbleX.push(TWO_PI * hash01(i, 2) - Math.PI);
1334
1346
  this.tumbleY.push(TWO_PI * hash01(i, 4) - Math.PI);
1347
+ this.collapseDelay.push(hash01(i, 7) * COLLAPSE_SPREAD_MS);
1335
1348
  }
1349
+ this.maxCollapseDelay = Math.max(...this.collapseDelay);
1336
1350
  }
1337
1351
  mount(target) {
1338
1352
  if (!target.style.position) target.style.position = "relative";
@@ -1393,7 +1407,7 @@ var GridAssemblyAnimation = class {
1393
1407
  COLLAPSE_MS
1394
1408
  ));
1395
1409
  }
1396
- if (this.collapseAt !== Infinity && now >= this.collapseAt + COLLAPSE_MS + POP_MS) {
1410
+ if (this.collapseAt !== Infinity && now >= this.collapseAt + this.maxCollapseDelay + COLLAPSE_MS + POP_MS) {
1397
1411
  this.finished = true;
1398
1412
  }
1399
1413
  this.engine.render();
@@ -1410,7 +1424,8 @@ var GridAssemblyAnimation = class {
1410
1424
  }
1411
1425
  updateBlends(dt, progress, now) {
1412
1426
  const exiting = this.exitAt !== Infinity;
1413
- const want = exiting ? COUNT : Math.min(COUNT, Math.floor(progress * COUNT + 1e-9));
1427
+ const ringComplete = now - this.enterAt >= INTRO_DONE_MS;
1428
+ const want = !ringComplete ? 0 : exiting ? COUNT : Math.min(COUNT, Math.floor(progress * COUNT + 1e-9));
1414
1429
  const rate = dt / this.dockMs * (exiting ? EXIT_HURRY : 1);
1415
1430
  for (let i = 0; i < COUNT; i++) {
1416
1431
  const target = i < want ? 1 : 0;
@@ -1473,25 +1488,30 @@ var GridAssemblyAnimation = class {
1473
1488
  if (!this.captured) {
1474
1489
  this.captured = this.handles.map((handle) => ({ ...handle.transform.position }));
1475
1490
  }
1476
- const u = clamp012((now - this.collapseAt) / COLLAPSE_MS);
1477
- const pull = easeInCubic(u);
1478
1491
  for (let i = 0; i < COUNT; i++) {
1479
1492
  const transform2 = this.handles[i].transform;
1480
1493
  const from = this.captured[i];
1494
+ const local = now - this.collapseAt - this.collapseDelay[i];
1495
+ if (local <= 0) {
1496
+ transform2.position.x = from.x;
1497
+ transform2.position.y = from.y;
1498
+ transform2.position.z = from.z;
1499
+ transform2.scale = this.size;
1500
+ continue;
1501
+ }
1502
+ const pull = easeInCubic(clamp012(local / COLLAPSE_MS));
1481
1503
  transform2.position.x = from.x * (1 - pull);
1482
1504
  transform2.position.y = from.y * (1 - pull);
1483
1505
  transform2.position.z = from.z * (1 - pull);
1484
1506
  transform2.scale = this.size * (1 - 0.99 * pull);
1485
- }
1486
- if (u >= 1) {
1487
- if (!this.popFading) {
1488
- this.popFading = true;
1489
- for (let i = 0; i < COUNT; i++) this.handles[i].transparency = this.fades[i];
1490
- }
1491
- const v = clamp012((now - this.collapseAt - COLLAPSE_MS) / POP_MS);
1492
- for (let i = 0; i < COUNT; i++) {
1507
+ if (local >= COLLAPSE_MS) {
1508
+ if (!this.popStarted[i]) {
1509
+ this.popStarted[i] = true;
1510
+ this.handles[i].transparency = this.fades[i];
1511
+ }
1512
+ const v = clamp012((local - COLLAPSE_MS) / POP_MS);
1493
1513
  this.fades[i].opacity = 1 - v;
1494
- this.handles[i].transform.scale = v >= 1 ? 0 : this.size * 0.01 * (1 + 1.6 * Math.sin(Math.PI * v));
1514
+ transform2.scale = v >= 1 ? 0 : this.size * 0.01 * (1 + 1.6 * Math.sin(Math.PI * v));
1495
1515
  }
1496
1516
  }
1497
1517
  }