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
@@ -139,7 +139,10 @@ function expandToTriangles(mesh) {
139
139
  const positions = new Float32Array(triangles * 9);
140
140
  const normals = new Float32Array(triangles * 9);
141
141
  const colors = new Float32Array(triangles * 9);
142
+ const emissives = new Float32Array(triangles * 9);
143
+ const speculars = new Float32Array(triangles * 12);
142
144
  let o = 0;
145
+ let so = 0;
143
146
  for (const face of mesh.faces) {
144
147
  const v0 = mesh.vertices[face.indices[0]];
145
148
  const v1 = mesh.vertices[face.indices[1]];
@@ -149,6 +152,15 @@ function expandToTriangles(mesh) {
149
152
  const cr = r / 255;
150
153
  const cg = g / 255;
151
154
  const cb = b / 255;
155
+ const emissive = face.material?.emissive;
156
+ const er = emissive ? emissive[0] : 0;
157
+ const eg = emissive ? emissive[1] : 0;
158
+ const eb = emissive ? emissive[2] : 0;
159
+ const specular = face.material?.specular;
160
+ const sr = specular ? specular[0] : 0;
161
+ const sg = specular ? specular[1] : 0;
162
+ const sb = specular ? specular[2] : 0;
163
+ const sn = specular ? face.material?.shininess ?? 32 : 1;
152
164
  for (let k = 1; k < face.indices.length - 1; k++) {
153
165
  const tri = [face.indices[0], face.indices[k], face.indices[k + 1]];
154
166
  for (const index of tri) {
@@ -162,11 +174,19 @@ function expandToTriangles(mesh) {
162
174
  colors[o] = cr;
163
175
  colors[o + 1] = cg;
164
176
  colors[o + 2] = cb;
177
+ emissives[o] = er;
178
+ emissives[o + 1] = eg;
179
+ emissives[o + 2] = eb;
180
+ speculars[so] = sr;
181
+ speculars[so + 1] = sg;
182
+ speculars[so + 2] = sb;
183
+ speculars[so + 3] = sn;
165
184
  o += 3;
185
+ so += 4;
166
186
  }
167
187
  }
168
188
  }
169
- return { positions, normals, colors, count: positions.length / 3 };
189
+ return { positions, normals, colors, emissives, speculars, count: positions.length / 3 };
170
190
  }
171
191
  function midpoint(a, b) {
172
192
  return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
@@ -204,13 +224,42 @@ var init_geometry = __esm({
204
224
  function clamp01(value) {
205
225
  return Math.min(1, Math.max(0, value));
206
226
  }
207
- function shadeColor(normal, color, light) {
227
+ function clamp255(value) {
228
+ return Math.round(Math.min(255, Math.max(0, value)));
229
+ }
230
+ function shade(normal, color, light, surface) {
208
231
  const lambert = Math.max(0, dot(normal, light.toLight));
209
232
  const brightness = clamp01(light.ambient + light.intensity * lambert);
210
- const [r, g, b] = parseColor(color);
211
- return `rgb(${Math.round(r * brightness)}, ${Math.round(g * brightness)}, ${Math.round(
212
- b * brightness
213
- )})`;
233
+ const [baseR, baseG, baseB] = parseColor(color);
234
+ let r = baseR * brightness;
235
+ let g = baseG * brightness;
236
+ let b = baseB * brightness;
237
+ const material = surface?.material;
238
+ const specular = material?.specular;
239
+ const viewDir = surface?.viewDir;
240
+ if (specular && viewDir && lambert > 0) {
241
+ const half = normalize({
242
+ x: light.toLight.x + viewDir.x,
243
+ y: light.toLight.y + viewDir.y,
244
+ z: light.toLight.z + viewDir.z
245
+ });
246
+ const shininess = material?.shininess ?? 32;
247
+ const highlight = Math.pow(Math.max(0, dot(normal, half)), shininess) * light.intensity * 255;
248
+ r += highlight * specular[0];
249
+ g += highlight * specular[1];
250
+ b += highlight * specular[2];
251
+ }
252
+ const emissive = material?.emissive;
253
+ if (emissive) {
254
+ r += emissive[0] * 255;
255
+ g += emissive[1] * 255;
256
+ b += emissive[2] * 255;
257
+ }
258
+ return [clamp255(r), clamp255(g), clamp255(b)];
259
+ }
260
+ function shadeColor(normal, color, light, surface) {
261
+ const [r, g, b] = shade(normal, color, light, surface);
262
+ return `rgb(${r}, ${g}, ${b})`;
214
263
  }
215
264
  var DEFAULTS2, Light;
216
265
  var init_light = __esm({
@@ -233,8 +282,8 @@ var init_light = __esm({
233
282
  };
234
283
  }
235
284
  /** Convenience wrapper around {@link shadeColor} using this light. */
236
- shade(normal, color) {
237
- return shadeColor(normal, color, this.params);
285
+ shade(normal, color, surface) {
286
+ return shadeColor(normal, color, this.params, surface);
238
287
  }
239
288
  };
240
289
  }
@@ -274,28 +323,51 @@ var init_webgl = __esm({
274
323
  in vec3 aPos;
275
324
  in vec3 aNormal;
276
325
  in vec3 aColor;
326
+ in vec3 aEmissive;
327
+ in vec4 aSpecular;
277
328
  uniform mat4 uViewProj;
278
329
  uniform mat4 uModel;
279
330
  out vec3 vNormal;
280
331
  out vec3 vColor;
332
+ out vec3 vEmissive;
333
+ out vec4 vSpecular;
334
+ out vec3 vWorldPos;
281
335
  void main() {
282
336
  vNormal = mat3(uModel) * aNormal;
283
337
  vColor = aColor;
284
- gl_Position = uViewProj * uModel * vec4(aPos, 1.0);
338
+ vEmissive = aEmissive;
339
+ vSpecular = aSpecular;
340
+ vec4 world = uModel * vec4(aPos, 1.0);
341
+ vWorldPos = world.xyz;
342
+ gl_Position = uViewProj * world;
285
343
  }`;
286
344
  FRAGMENT_SHADER = `#version 300 es
287
345
  precision mediump float;
288
346
  in vec3 vNormal;
289
347
  in vec3 vColor;
348
+ in vec3 vEmissive;
349
+ in vec4 vSpecular;
350
+ in vec3 vWorldPos;
290
351
  uniform vec3 uToLight;
352
+ uniform vec3 uEye;
291
353
  uniform float uIntensity;
292
354
  uniform float uAmbient;
293
355
  uniform float uOpacity;
294
356
  out vec4 fragColor;
295
357
  void main() {
296
- float lambert = max(dot(normalize(vNormal), normalize(uToLight)), 0.0);
358
+ vec3 normal = normalize(vNormal);
359
+ vec3 toLight = normalize(uToLight);
360
+ float lambert = max(dot(normal, toLight), 0.0);
297
361
  float brightness = clamp(uAmbient + uIntensity * lambert, 0.0, 1.0);
298
- fragColor = vec4(vColor * brightness, uOpacity);
362
+ vec3 lit = vColor * brightness;
363
+ if (lambert > 0.0) {
364
+ vec3 viewDir = normalize(uEye - vWorldPos);
365
+ vec3 halfVec = normalize(toLight + viewDir);
366
+ float highlight = pow(max(dot(normal, halfVec), 0.0), vSpecular.w) * uIntensity;
367
+ lit += highlight * vSpecular.xyz;
368
+ }
369
+ lit += vEmissive;
370
+ fragColor = vec4(lit, uOpacity);
299
371
  }`;
300
372
  WebGLRenderer = class {
301
373
  constructor(options = {}) {
@@ -316,9 +388,12 @@ void main() {
316
388
  aPos: gl.getAttribLocation(this.program, "aPos"),
317
389
  aNormal: gl.getAttribLocation(this.program, "aNormal"),
318
390
  aColor: gl.getAttribLocation(this.program, "aColor"),
391
+ aEmissive: gl.getAttribLocation(this.program, "aEmissive"),
392
+ aSpecular: gl.getAttribLocation(this.program, "aSpecular"),
319
393
  uViewProj: gl.getUniformLocation(this.program, "uViewProj"),
320
394
  uModel: gl.getUniformLocation(this.program, "uModel"),
321
395
  uToLight: gl.getUniformLocation(this.program, "uToLight"),
396
+ uEye: gl.getUniformLocation(this.program, "uEye"),
322
397
  uIntensity: gl.getUniformLocation(this.program, "uIntensity"),
323
398
  uAmbient: gl.getUniformLocation(this.program, "uAmbient"),
324
399
  uOpacity: gl.getUniformLocation(this.program, "uOpacity")
@@ -342,17 +417,19 @@ void main() {
342
417
  const data = expandToTriangles(mesh);
343
418
  const vao = gl.createVertexArray();
344
419
  gl.bindVertexArray(vao);
345
- const attribute = (location, array) => {
420
+ const attribute = (location, array, size = 3) => {
346
421
  if (location < 0) return;
347
422
  const buffer = gl.createBuffer();
348
423
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
349
424
  gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
350
425
  gl.enableVertexAttribArray(location);
351
- gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0);
426
+ gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
352
427
  };
353
428
  attribute(loc.aPos, data.positions);
354
429
  attribute(loc.aNormal, data.normals);
355
430
  attribute(loc.aColor, data.colors);
431
+ attribute(loc.aEmissive, data.emissives);
432
+ attribute(loc.aSpecular, data.speculars, 4);
356
433
  gl.bindVertexArray(null);
357
434
  const result = { vao, count: data.count };
358
435
  this.cache.set(mesh, result);
@@ -367,6 +444,7 @@ void main() {
367
444
  gl.useProgram(this.program);
368
445
  gl.uniformMatrix4fv(loc.uViewProj, false, new Float32Array(frame.viewProjection));
369
446
  gl.uniform3f(loc.uToLight, frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z);
447
+ gl.uniform3f(loc.uEye, frame.eye.x, frame.eye.y, frame.eye.z);
370
448
  gl.uniform1f(loc.uIntensity, frame.light.intensity);
371
449
  gl.uniform1f(loc.uAmbient, frame.light.ambient);
372
450
  gl.disable(gl.BLEND);
@@ -441,6 +519,7 @@ struct Uniforms {
441
519
  model: mat4x4<f32>,
442
520
  toLight: vec4<f32>,
443
521
  params: vec4<f32>,
522
+ eye: vec4<f32>,
444
523
  };
445
524
  @group(0) @binding(0) var<uniform> u: Uniforms;
446
525
 
@@ -448,23 +527,40 @@ struct VSOut {
448
527
  @builtin(position) position: vec4<f32>,
449
528
  @location(0) normal: vec3<f32>,
450
529
  @location(1) color: vec3<f32>,
530
+ @location(2) emissive: vec3<f32>,
531
+ @location(3) specular: vec4<f32>,
532
+ @location(4) worldPos: vec3<f32>,
451
533
  };
452
534
 
453
535
  @vertex
454
- fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
536
+ 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 {
455
537
  var out: VSOut;
456
538
  let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
457
539
  out.normal = m * normal;
458
540
  out.color = color;
459
- out.position = u.viewProj * u.model * vec4<f32>(pos, 1.0);
541
+ out.emissive = emissive;
542
+ out.specular = specular;
543
+ let world = u.model * vec4<f32>(pos, 1.0);
544
+ out.worldPos = world.xyz;
545
+ out.position = u.viewProj * world;
460
546
  return out;
461
547
  }
462
548
 
463
549
  @fragment
464
550
  fn fs(in: VSOut) -> @location(0) vec4<f32> {
465
- let lambert = max(dot(normalize(in.normal), normalize(u.toLight.xyz)), 0.0);
551
+ let normal = normalize(in.normal);
552
+ let toLight = normalize(u.toLight.xyz);
553
+ let lambert = max(dot(normal, toLight), 0.0);
466
554
  let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
467
- return vec4<f32>(in.color * brightness, u.params.z);
555
+ var lit = in.color * brightness;
556
+ if (lambert > 0.0) {
557
+ let viewDir = normalize(u.eye.xyz - in.worldPos);
558
+ let halfVec = normalize(toLight + viewDir);
559
+ let highlight = pow(max(dot(normal, halfVec), 0.0), in.specular.w) * u.params.x;
560
+ lit = lit + highlight * in.specular.xyz;
561
+ }
562
+ lit = lit + in.emissive;
563
+ return vec4<f32>(lit, u.params.z);
468
564
  }
469
565
  `;
470
566
  CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
@@ -505,13 +601,15 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
505
601
  {
506
602
  binding: 0,
507
603
  visibility: stage.VERTEX | stage.FRAGMENT,
508
- buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 160 }
604
+ buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 176 }
509
605
  }
510
606
  ]
511
607
  });
512
- const vertexBuffer = (location) => ({
513
- arrayStride: 12,
514
- attributes: [{ shaderLocation: location, offset: 0, format: "float32x3" }]
608
+ const vertexBuffer = (location, components = 3) => ({
609
+ arrayStride: components * 4,
610
+ attributes: [
611
+ { shaderLocation: location, offset: 0, format: `float32x${components}` }
612
+ ]
515
613
  });
516
614
  const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
517
615
  const blend = {
@@ -527,7 +625,13 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
527
625
  vertex: {
528
626
  module: module2,
529
627
  entryPoint: "vs",
530
- buffers: [vertexBuffer(0), vertexBuffer(1), vertexBuffer(2)]
628
+ buffers: [
629
+ vertexBuffer(0),
630
+ vertexBuffer(1),
631
+ vertexBuffer(2),
632
+ vertexBuffer(3),
633
+ vertexBuffer(4, 4)
634
+ ]
531
635
  },
532
636
  fragment: {
533
637
  module: module2,
@@ -580,6 +684,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
580
684
  position: upload(data.positions),
581
685
  normal: upload(data.normals),
582
686
  color: upload(data.colors),
687
+ emissive: upload(data.emissives),
688
+ specular: upload(data.speculars),
583
689
  count: data.count
584
690
  };
585
691
  this.cache.set(mesh, result);
@@ -631,7 +737,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
631
737
  const bindGroup = this.device.createBindGroup({
632
738
  layout,
633
739
  entries: [
634
- { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 160 } }
740
+ { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 176 } }
635
741
  ]
636
742
  });
637
743
  draws.forEach((draw, i) => {
@@ -640,6 +746,7 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
640
746
  data.set(draw.item.model, 16);
641
747
  data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
642
748
  data.set([frame.light.intensity, frame.light.ambient, draw.opacity, 0], 36);
749
+ data.set([frame.eye.x, frame.eye.y, frame.eye.z, 0], 40);
643
750
  this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
644
751
  });
645
752
  const encoder = this.device.createCommandEncoder();
@@ -666,6 +773,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
666
773
  pass.setVertexBuffer(0, mesh.position);
667
774
  pass.setVertexBuffer(1, mesh.normal);
668
775
  pass.setVertexBuffer(2, mesh.color);
776
+ pass.setVertexBuffer(3, mesh.emissive);
777
+ pass.setVertexBuffer(4, mesh.specular);
669
778
  pass.draw(mesh.count);
670
779
  });
671
780
  pass.end();
@@ -677,6 +786,8 @@ fn fs(in: VSOut) -> @location(0) vec4<f32> {
677
786
  mesh.position.destroy?.();
678
787
  mesh.normal.destroy?.();
679
788
  mesh.color.destroy?.();
789
+ mesh.emissive.destroy?.();
790
+ mesh.specular.destroy?.();
680
791
  }
681
792
  this.cache.clear();
682
793
  this.uniformBuffer?.destroy?.();
@@ -759,9 +870,30 @@ var init_canvas2d = __esm({
759
870
  depth += dot(d, d);
760
871
  }
761
872
  depth /= face.indices.length;
873
+ let surface;
874
+ const material = face.material;
875
+ if (material) {
876
+ if (material.specular) {
877
+ let cx = 0;
878
+ let cy = 0;
879
+ let cz = 0;
880
+ for (const i of face.indices) {
881
+ cx += world[i].x;
882
+ cy += world[i].y;
883
+ cz += world[i].z;
884
+ }
885
+ const inv = 1 / face.indices.length;
886
+ const viewDir = normalize(
887
+ subtract(frame.eye, { x: cx * inv, y: cy * inv, z: cz * inv })
888
+ );
889
+ surface = { material, viewDir };
890
+ } else {
891
+ surface = { material };
892
+ }
893
+ }
762
894
  polygons.push({
763
895
  points,
764
- color: shadeColor(normal, face.color, frame.light),
896
+ color: shadeColor(normal, face.color, frame.light, surface),
765
897
  depth,
766
898
  opacity: faceOpacity
767
899
  });
@@ -852,6 +984,7 @@ __export(little_3d_engine_exports, {
852
984
  Camera: () => Camera,
853
985
  Light: () => Light,
854
986
  Little3dEngine: () => Little3dEngine,
987
+ attachMaterial: () => attachMaterial,
855
988
  cross: () => cross,
856
989
  cube: () => cube,
857
990
  cubeSphere: () => cubeSphere,
@@ -915,6 +1048,12 @@ init_light();
915
1048
  init_math();
916
1049
 
917
1050
  // src/engines/little-3d-engine/core/mesh.ts
1051
+ function attachMaterial(mesh, material) {
1052
+ if (material) {
1053
+ for (const face of mesh.faces) face.material = material;
1054
+ }
1055
+ return mesh;
1056
+ }
918
1057
  function transform(init) {
919
1058
  return {
920
1059
  position: init?.position ?? { x: 0, y: 0, z: 0 },
@@ -929,7 +1068,7 @@ init_light();
929
1068
 
930
1069
  // src/engines/little-3d-engine/shapes/primitives/cube.ts
931
1070
  var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
932
- function cube(size = 1, colors = DEFAULT_COLORS) {
1071
+ function cube(size = 1, colors = DEFAULT_COLORS, material) {
933
1072
  const h = size / 2;
934
1073
  const vertices = [
935
1074
  { x: -h, y: -h, z: h },
@@ -949,12 +1088,12 @@ function cube(size = 1, colors = DEFAULT_COLORS) {
949
1088
  { indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
950
1089
  { indices: [4, 0, 3, 7], color: colors[5 % colors.length] }
951
1090
  ];
952
- return { vertices, faces };
1091
+ return attachMaterial({ vertices, faces }, material);
953
1092
  }
954
1093
 
955
1094
  // src/engines/little-3d-engine/shapes/primitives/quad.ts
956
1095
  var DEFAULT_COLORS2 = ["#3b82f6"];
957
- function quad(size = 1, colors = DEFAULT_COLORS2) {
1096
+ function quad(size = 1, colors = DEFAULT_COLORS2, material) {
958
1097
  const s = size / 2;
959
1098
  const vertices = [
960
1099
  { x: -s, y: -s, z: 0 },
@@ -962,12 +1101,15 @@ function quad(size = 1, colors = DEFAULT_COLORS2) {
962
1101
  { x: s, y: s, z: 0 },
963
1102
  { x: -s, y: s, z: 0 }
964
1103
  ];
965
- return { vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] };
1104
+ return attachMaterial(
1105
+ { vertices, faces: [{ indices: [0, 1, 2, 3], color: colors[0] }] },
1106
+ material
1107
+ );
966
1108
  }
967
1109
 
968
1110
  // src/engines/little-3d-engine/shapes/primitives/tetrahedron.ts
969
1111
  var DEFAULT_COLORS3 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
970
- function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
1112
+ function tetrahedron(size = 1, colors = DEFAULT_COLORS3, material) {
971
1113
  const s = size / 2;
972
1114
  const vertices = [
973
1115
  { x: s, y: s, z: s },
@@ -981,7 +1123,7 @@ function tetrahedron(size = 1, colors = DEFAULT_COLORS3) {
981
1123
  { indices: [0, 2, 3], color: colors[2 % colors.length] },
982
1124
  { indices: [1, 3, 2], color: colors[3 % colors.length] }
983
1125
  ];
984
- return { vertices, faces };
1126
+ return attachMaterial({ vertices, faces }, material);
985
1127
  }
986
1128
 
987
1129
  // src/engines/little-3d-engine/shapes/primitives/octahedron.ts
@@ -995,7 +1137,7 @@ var DEFAULT_COLORS4 = [
995
1137
  "#06b6d4",
996
1138
  "#eab308"
997
1139
  ];
998
- function octahedron(size = 1, colors = DEFAULT_COLORS4) {
1140
+ function octahedron(size = 1, colors = DEFAULT_COLORS4, material) {
999
1141
  const r = size / 2;
1000
1142
  const vertices = [
1001
1143
  { x: r, y: 0, z: 0 },
@@ -1015,12 +1157,12 @@ function octahedron(size = 1, colors = DEFAULT_COLORS4) {
1015
1157
  { indices: [5, 3, 1], color: colors[6 % colors.length] },
1016
1158
  { indices: [5, 0, 3], color: colors[7 % colors.length] }
1017
1159
  ];
1018
- return { vertices, faces };
1160
+ return attachMaterial({ vertices, faces }, material);
1019
1161
  }
1020
1162
 
1021
1163
  // src/engines/little-3d-engine/shapes/primitives/pyramid.ts
1022
1164
  var DEFAULT_COLORS5 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
1023
- function pyramid(size = 1, colors = DEFAULT_COLORS5) {
1165
+ function pyramid(size = 1, colors = DEFAULT_COLORS5, material) {
1024
1166
  const h = size / 2;
1025
1167
  const vertices = [
1026
1168
  { x: -h, y: -h, z: h },
@@ -1036,12 +1178,12 @@ function pyramid(size = 1, colors = DEFAULT_COLORS5) {
1036
1178
  { indices: [4, 2, 3], color: colors[3 % colors.length] },
1037
1179
  { indices: [4, 3, 0], color: colors[4 % colors.length] }
1038
1180
  ];
1039
- return { vertices, faces };
1181
+ return attachMaterial({ vertices, faces }, material);
1040
1182
  }
1041
1183
 
1042
1184
  // src/engines/little-3d-engine/shapes/primitives/spheres/uv-sphere.ts
1043
1185
  var DEFAULT_COLORS6 = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
1044
- function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
1186
+ function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6, material) {
1045
1187
  const r = size / 2;
1046
1188
  const d = Math.max(1, Math.floor(detail));
1047
1189
  const slices = Math.max(4, d * 4);
@@ -1080,7 +1222,7 @@ function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS6) {
1080
1222
  color: color()
1081
1223
  });
1082
1224
  }
1083
- return { vertices, faces };
1225
+ return attachMaterial({ vertices, faces }, material);
1084
1226
  }
1085
1227
 
1086
1228
  // src/engines/little-3d-engine/shapes/primitives/spheres/icosphere.ts
@@ -1123,8 +1265,11 @@ var SEED_FACES = [
1123
1265
  [8, 6, 7],
1124
1266
  [9, 8, 1]
1125
1267
  ];
1126
- function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7) {
1127
- return sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors);
1268
+ function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS7, material) {
1269
+ return attachMaterial(
1270
+ sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors),
1271
+ material
1272
+ );
1128
1273
  }
1129
1274
 
1130
1275
  // src/engines/little-3d-engine/shapes/primitives/spheres/octa-sphere.ts
@@ -1148,8 +1293,11 @@ var SEED_FACES2 = [
1148
1293
  [5, 3, 1],
1149
1294
  [5, 0, 3]
1150
1295
  ];
1151
- function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8) {
1152
- return sphereFromTriangles(SEED_VERTICES2, SEED_FACES2, size, detail, colors);
1296
+ function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS8, material) {
1297
+ return attachMaterial(
1298
+ sphereFromTriangles(SEED_VERTICES2, SEED_FACES2, size, detail, colors),
1299
+ material
1300
+ );
1153
1301
  }
1154
1302
 
1155
1303
  // src/engines/little-3d-engine/shapes/primitives/spheres/cube-sphere.ts
@@ -1162,7 +1310,7 @@ var CUBE_FACES = [
1162
1310
  { normal: [0, 1, 0], right: [1, 0, 0], up: [0, 0, -1] },
1163
1311
  { normal: [0, -1, 0], right: [1, 0, 0], up: [0, 0, 1] }
1164
1312
  ];
1165
- function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
1313
+ function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9, material) {
1166
1314
  const r = size / 2;
1167
1315
  const n = Math.max(1, Math.floor(detail));
1168
1316
  const vertices = [];
@@ -1191,13 +1339,13 @@ function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS9) {
1191
1339
  }
1192
1340
  }
1193
1341
  }
1194
- return { vertices, faces };
1342
+ return attachMaterial({ vertices, faces }, material);
1195
1343
  }
1196
1344
 
1197
1345
  // src/engines/little-3d-engine/shapes/complex/plane.ts
1198
1346
  var DEFAULT_COLORS10 = ["#e0f2fe", "#7dd3fc", "#38bdf8", "#f8fafc"];
1199
- function planeMesh(colors = DEFAULT_COLORS10) {
1200
- return {
1347
+ function planeMesh(colors = DEFAULT_COLORS10, material) {
1348
+ return attachMaterial({
1201
1349
  vertices: [
1202
1350
  { x: 0.9, y: 0, z: 0 },
1203
1351
  { x: -0.2, y: 0, z: 0.82 },
@@ -1225,7 +1373,7 @@ function planeMesh(colors = DEFAULT_COLORS10) {
1225
1373
  { indices: [3, 6, 8], color: colors[0] ?? DEFAULT_COLORS10[0] },
1226
1374
  { indices: [3, 8, 6], color: colors[1] ?? DEFAULT_COLORS10[1] }
1227
1375
  ]
1228
- };
1376
+ }, material);
1229
1377
  }
1230
1378
 
1231
1379
  // src/engines/little-3d-engine/textures/dynamic/canvas-texture.ts
@@ -1238,18 +1386,30 @@ function canvasTexture(draw, size = 96) {
1238
1386
  }
1239
1387
 
1240
1388
  // src/engines/little-3d-engine/textures/dynamic/star.ts
1241
- function starTexture() {
1389
+ function drawStar(ctx) {
1390
+ ctx.save();
1391
+ ctx.translate(48, 48);
1392
+ ctx.fillStyle = "#fff";
1393
+ ctx.beginPath();
1394
+ for (let index = 0; index < 10; index++) {
1395
+ const radius = index % 2 === 0 ? 43 : 16;
1396
+ const angle = index * Math.PI / 5 - Math.PI / 2;
1397
+ ctx.lineTo(radius * Math.cos(angle), radius * Math.sin(angle));
1398
+ }
1399
+ ctx.closePath();
1400
+ ctx.fill();
1401
+ ctx.restore();
1402
+ }
1403
+ function starTexture(options = {}) {
1404
+ const glow = Math.max(0, options.glow ?? 0);
1242
1405
  return canvasTexture((ctx) => {
1243
- ctx.translate(48, 48);
1244
- ctx.fillStyle = "#fff";
1245
- ctx.beginPath();
1246
- for (let index = 0; index < 10; index++) {
1247
- const radius = index % 2 === 0 ? 43 : 16;
1248
- const angle = index * Math.PI / 5 - Math.PI / 2;
1249
- ctx.lineTo(radius * Math.cos(angle), radius * Math.sin(angle));
1406
+ if (glow > 0) {
1407
+ ctx.save();
1408
+ ctx.filter = `blur(${glow}px)`;
1409
+ drawStar(ctx);
1410
+ ctx.restore();
1250
1411
  }
1251
- ctx.closePath();
1252
- ctx.fill();
1412
+ drawStar(ctx);
1253
1413
  });
1254
1414
  }
1255
1415
 
@@ -24,28 +24,64 @@ __export(obj_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(obj_exports);
26
26
  var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
27
+ function clamp01(value) {
28
+ return Math.min(1, Math.max(0, value));
29
+ }
27
30
  function channelToHex(value) {
28
31
  const channel = Number.parseFloat(value);
29
32
  if (!Number.isFinite(channel)) return void 0;
30
- return Math.round(Math.min(1, Math.max(0, channel)) * 255).toString(16).padStart(2, "0");
33
+ return Math.round(clamp01(channel) * 255).toString(16).padStart(2, "0");
31
34
  }
32
- function parseMtlColors(text) {
33
- const colors = /* @__PURE__ */ new Map();
34
- let material;
35
+ function parseRgb(parts) {
36
+ const channels = parts.slice(1, 4).map(Number.parseFloat);
37
+ if (channels.length !== 3 || !channels.every(Number.isFinite)) return void 0;
38
+ return [clamp01(channels[0]), clamp01(channels[1]), clamp01(channels[2])];
39
+ }
40
+ function toMaterial(surface) {
41
+ const material = {};
42
+ if (surface.specular) material.specular = surface.specular;
43
+ if (surface.shininess !== void 0) material.shininess = surface.shininess;
44
+ if (surface.emissive) material.emissive = surface.emissive;
45
+ return Object.keys(material).length > 0 ? material : void 0;
46
+ }
47
+ function parseMtl(text) {
48
+ const materials = /* @__PURE__ */ new Map();
49
+ const surfaces = /* @__PURE__ */ new Map();
50
+ let name;
35
51
  for (const line of text.split("\n")) {
36
52
  const trimmed = line.trim();
37
53
  if (trimmed === "" || trimmed.startsWith("#")) continue;
38
54
  const parts = trimmed.split(/\s+/);
39
- if (parts[0] === "newmtl") {
40
- material = parts.slice(1).join(" ");
41
- } else if (parts[0] === "Kd" && material) {
55
+ const keyword = parts[0];
56
+ if (keyword === "newmtl") {
57
+ name = parts.slice(1).join(" ");
58
+ if (name && !materials.has(name)) {
59
+ materials.set(name, {});
60
+ surfaces.set(name, {});
61
+ }
62
+ continue;
63
+ }
64
+ if (!name) continue;
65
+ const entry = materials.get(name);
66
+ const surface = surfaces.get(name);
67
+ if (keyword === "Kd") {
42
68
  const channels = parts.slice(1, 4).map(channelToHex);
43
69
  if (channels.length === 3 && channels.every((channel) => channel !== void 0)) {
44
- colors.set(material, `#${channels.join("")}`);
70
+ entry.color = `#${channels.join("")}`;
45
71
  }
72
+ } else if (keyword === "Ks") {
73
+ surface.specular = parseRgb(parts);
74
+ } else if (keyword === "Ns") {
75
+ const ns = Number.parseFloat(parts[1]);
76
+ if (Number.isFinite(ns)) surface.shininess = Math.max(0, ns);
77
+ } else if (keyword === "Ke") {
78
+ surface.emissive = parseRgb(parts);
46
79
  }
47
80
  }
48
- return colors;
81
+ for (const [key, surface] of surfaces) {
82
+ materials.get(key).material = toMaterial(surface);
83
+ }
84
+ return materials;
49
85
  }
50
86
  function resolveIndex(token, vertexCount) {
51
87
  const n = parseInt(token, 10);
@@ -53,7 +89,7 @@ function resolveIndex(token, vertexCount) {
53
89
  }
54
90
  function parseObj(text, options = {}) {
55
91
  const colors = options.colors ?? DEFAULT_COLORS;
56
- const materialColors = options.useMtlColors && options.mtl ? parseMtlColors(options.mtl) : void 0;
92
+ const materials = options.useMtlColors && options.mtl ? parseMtl(options.mtl) : void 0;
57
93
  const vertices = [];
58
94
  const faces = [];
59
95
  let material;
@@ -77,9 +113,11 @@ function parseObj(text, options = {}) {
77
113
  indices.push(resolveIndex(vertexToken, vertices.length));
78
114
  }
79
115
  if (indices.length >= 3) {
80
- const mtlColor = material ? materialColors?.get(material) : void 0;
81
- const color = mtlColor ?? (materialColors ? colors[0] ?? "#888888" : colors[faces.length % colors.length]);
82
- faces.push({ indices, color });
116
+ const entry = material ? materials?.get(material) : void 0;
117
+ const color = entry?.color ?? (materials ? colors[0] ?? "#888888" : colors[faces.length % colors.length]);
118
+ const face = { indices, color };
119
+ if (entry?.material) face.material = entry.material;
120
+ faces.push(face);
83
121
  }
84
122
  }
85
123
  }