3d-spinner 0.9.2 → 0.9.3

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 (97) 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/grid-assembly.d.ts +67 -0
  8. package/dist/animations/grid-assembly.js +268 -0
  9. package/dist/animations/object-motion.d.ts +19 -7
  10. package/dist/animations/object-motion.js +38 -32
  11. package/dist/animations/particles.d.ts +110 -0
  12. package/dist/animations/particles.js +209 -0
  13. package/dist/cjs/animations/charged-orb.cjs +1334 -0
  14. package/dist/cjs/animations/grid-assembly.cjs +1498 -0
  15. package/dist/cjs/animations/object-motion.cjs +124 -43
  16. package/dist/cjs/animations/particles.cjs +1940 -0
  17. package/dist/cjs/animations/spin.cjs +4 -3
  18. package/dist/cjs/composite-animation.cjs +58 -0
  19. package/dist/cjs/engines/little-3d-engine/little-3d-engine.cjs +131 -22
  20. package/dist/cjs/engines/little-3d-engine/loaders/obj.cjs +30 -1
  21. package/dist/cjs/engines/little-3d-engine/renderers/canvas2d-textured.cjs +345 -0
  22. package/dist/cjs/engines/little-3d-engine/renderers/webgl-textured.cjs +528 -0
  23. package/dist/cjs/engines/little-3d-engine/renderers/webgpu-textured.cjs +712 -0
  24. package/dist/cjs/prefabs/prefabs.cjs +3450 -0
  25. package/dist/composite-animation.d.ts +18 -0
  26. package/dist/composite-animation.js +39 -0
  27. package/dist/engines/little-3d-engine/little-3d-engine.d.ts +17 -12
  28. package/dist/engines/little-3d-engine/little-3d-engine.js +13 -8
  29. package/dist/engines/little-3d-engine/loaders/obj.d.ts +12 -4
  30. package/dist/engines/little-3d-engine/loaders/obj.js +44 -5
  31. package/dist/engines/little-3d-engine/renderer.d.ts +7 -1
  32. package/dist/engines/little-3d-engine/renderer.js +2 -0
  33. package/dist/engines/little-3d-engine/renderers/canvas2d-textured.d.ts +20 -0
  34. package/dist/engines/little-3d-engine/renderers/canvas2d-textured.js +135 -0
  35. package/dist/engines/little-3d-engine/renderers/textured-helpers.d.ts +9 -0
  36. package/dist/engines/little-3d-engine/renderers/textured-helpers.js +35 -0
  37. package/dist/engines/little-3d-engine/renderers/webgl-textured.d.ts +43 -0
  38. package/dist/engines/little-3d-engine/renderers/webgl-textured.js +211 -0
  39. package/dist/engines/little-3d-engine/renderers/webgpu-textured.d.ts +45 -0
  40. package/dist/engines/little-3d-engine/renderers/webgpu-textured.js +300 -0
  41. package/dist/engines/little-3d-engine/renderers/webgpu.d.ts +11 -6
  42. package/dist/engines/little-3d-engine/shapes/complex/plane.d.ts +3 -0
  43. package/dist/engines/little-3d-engine/shapes/complex/plane.js +33 -0
  44. package/dist/engines/little-3d-engine/shapes/{cube.d.ts → primitives/cube.d.ts} +1 -1
  45. package/dist/engines/little-3d-engine/shapes/{octahedron.d.ts → primitives/octahedron.d.ts} +1 -1
  46. package/dist/engines/little-3d-engine/shapes/{pyramid.d.ts → primitives/pyramid.d.ts} +1 -1
  47. package/dist/engines/little-3d-engine/shapes/primitives/quad.d.ts +9 -0
  48. package/dist/engines/little-3d-engine/shapes/primitives/quad.js +18 -0
  49. package/dist/engines/little-3d-engine/shapes/{cube-sphere.d.ts → primitives/spheres/cube-sphere.d.ts} +1 -1
  50. package/dist/engines/little-3d-engine/shapes/{icosphere.d.ts → primitives/spheres/icosphere.d.ts} +1 -1
  51. package/dist/engines/little-3d-engine/shapes/{icosphere.js → primitives/spheres/icosphere.js} +1 -1
  52. package/dist/engines/little-3d-engine/shapes/{octa-sphere.d.ts → primitives/spheres/octa-sphere.d.ts} +1 -1
  53. package/dist/engines/little-3d-engine/shapes/{octa-sphere.js → primitives/spheres/octa-sphere.js} +1 -1
  54. package/dist/engines/little-3d-engine/shapes/{uv-sphere.d.ts → primitives/spheres/uv-sphere.d.ts} +1 -1
  55. package/dist/engines/little-3d-engine/shapes/{tetrahedron.d.ts → primitives/tetrahedron.d.ts} +1 -1
  56. package/dist/engines/little-3d-engine/textures/dynamic/canvas-texture.d.ts +1 -0
  57. package/dist/engines/little-3d-engine/textures/dynamic/canvas-texture.js +8 -0
  58. package/dist/engines/little-3d-engine/textures/dynamic/shine.d.ts +2 -0
  59. package/dist/engines/little-3d-engine/textures/dynamic/shine.js +14 -0
  60. package/dist/engines/little-3d-engine/textures/dynamic/star.d.ts +2 -0
  61. package/dist/engines/little-3d-engine/textures/dynamic/star.js +16 -0
  62. package/dist/engines/little-3d-engine/textures/dynamic/streak.d.ts +5 -0
  63. package/dist/engines/little-3d-engine/textures/dynamic/streak.js +20 -0
  64. package/dist/index.d.ts +2 -1
  65. package/dist/prefabs/charged-orb.d.ts +17 -0
  66. package/dist/prefabs/charged-orb.js +37 -0
  67. package/dist/prefabs/crystal-comet.d.ts +4 -0
  68. package/dist/prefabs/crystal-comet.js +41 -0
  69. package/dist/prefabs/ghost-train.d.ts +9 -0
  70. package/dist/prefabs/ghost-train.js +44 -0
  71. package/dist/prefabs/grid-assembly.d.ts +13 -0
  72. package/dist/prefabs/grid-assembly.js +15 -0
  73. package/dist/prefabs/monochrome-streak.d.ts +4 -0
  74. package/dist/prefabs/monochrome-streak.js +25 -0
  75. package/dist/prefabs/plane-star-trail.d.ts +4 -0
  76. package/dist/prefabs/plane-star-trail.js +40 -0
  77. package/dist/prefabs/prefabs.d.ts +10 -0
  78. package/dist/prefabs/prefabs.js +9 -0
  79. package/dist/prefabs/pulsing-starfield.d.ts +4 -0
  80. package/dist/prefabs/pulsing-starfield.js +27 -0
  81. package/dist/prefabs/rocket-launch.d.ts +8 -0
  82. package/dist/prefabs/rocket-launch.js +59 -0
  83. package/dist/prefabs/spinner.d.ts +7 -0
  84. package/dist/prefabs/spinner.js +17 -0
  85. package/dist/prefabs/star-swarm.d.ts +4 -0
  86. package/dist/prefabs/star-swarm.js +27 -0
  87. package/dist/prefabs/types.d.ts +46 -0
  88. package/dist/prefabs/types.js +1 -0
  89. package/dist/umd/spinner.global.js +3055 -1251
  90. package/dist/umd/spinner.global.min.js +60 -5
  91. package/package.json +42 -1
  92. /package/dist/engines/little-3d-engine/shapes/{cube.js → primitives/cube.js} +0 -0
  93. /package/dist/engines/little-3d-engine/shapes/{octahedron.js → primitives/octahedron.js} +0 -0
  94. /package/dist/engines/little-3d-engine/shapes/{pyramid.js → primitives/pyramid.js} +0 -0
  95. /package/dist/engines/little-3d-engine/shapes/{cube-sphere.js → primitives/spheres/cube-sphere.js} +0 -0
  96. /package/dist/engines/little-3d-engine/shapes/{uv-sphere.js → primitives/spheres/uv-sphere.js} +0 -0
  97. /package/dist/engines/little-3d-engine/shapes/{tetrahedron.js → primitives/tetrahedron.js} +0 -0
@@ -0,0 +1,209 @@
1
+ import { animationLabelOpacity, mountAnimationLabel, } from "../animation-label.js";
2
+ import { Little3dEngine, quad, cross, normalize, } from "../engines/little-3d-engine/little-3d-engine.js";
3
+ const DEFAULT_COLORS = ["#fde047", "#fb923c", "#f472b6", "#60a5fa"];
4
+ const FADE_IN_END = 0.15;
5
+ const FADE_OUT_START = 0.6;
6
+ function rand01(seed, index, salt) {
7
+ let h = (seed ^ Math.imul(index + 1, 0x9e3779b9) ^ Math.imul(salt + 1, 0x85ebca6b)) >>> 0;
8
+ h = Math.imul(h ^ (h >>> 16), 0x45d9f3b);
9
+ h = Math.imul(h ^ (h >>> 16), 0x45d9f3b);
10
+ h ^= h >>> 16;
11
+ return (h >>> 0) / 4294967296;
12
+ }
13
+ function smoothstep(edge0, edge1, value) {
14
+ const x = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
15
+ return x * x * (3 - 2 * x);
16
+ }
17
+ function positiveFinite(value, name) {
18
+ if (!Number.isFinite(value) || value <= 0) {
19
+ throw new RangeError(`3d-spinner: ${name} must be a finite number greater than zero.`);
20
+ }
21
+ return value;
22
+ }
23
+ function emitBasis(direction) {
24
+ const d = normalize(direction);
25
+ const helper = Math.abs(d.y) < 0.99 ? { x: 0, y: 1, z: 0 } : { x: 1, y: 0, z: 0 };
26
+ const right = normalize(cross(helper, d));
27
+ return { d, right, up: cross(d, right) };
28
+ }
29
+ /** Create a {@link ParticleField} from the emission options. */
30
+ export function particleField(options = {}) {
31
+ const rate = positiveFinite(options.rate ?? 20, "rate");
32
+ const lifeMs = positiveFinite(options.lifeMs ?? 1800, "lifeMs");
33
+ const size = options.size ?? 0.16;
34
+ const speed = options.speed ?? 0.6;
35
+ const gravity = options.gravity;
36
+ const spread = options.spread ?? 0.5;
37
+ const peak = Math.max(0, Math.min(1, options.opacity ?? 0.9));
38
+ const spin = options.spin ?? 0.002;
39
+ const alignToMotion = options.alignToMotion ?? false;
40
+ const seed = options.seed ?? 1;
41
+ const basis = options.direction && emitBasis(options.direction);
42
+ const spawnGapMs = 1000 / rate;
43
+ const directionOf = (index) => {
44
+ const u = rand01(seed, index, 0);
45
+ const phi = 2 * Math.PI * rand01(seed, index, 1);
46
+ if (!basis) {
47
+ const z = 2 * u - 1;
48
+ const r = Math.sqrt(Math.max(0, 1 - z * z));
49
+ return { x: r * Math.cos(phi), y: r * Math.sin(phi), z };
50
+ }
51
+ const cos = 1 - u * (1 - Math.cos(spread));
52
+ const sin = Math.sqrt(Math.max(0, 1 - cos * cos));
53
+ const { d, right, up } = basis;
54
+ return {
55
+ x: d.x * cos + (right.x * Math.cos(phi) + up.x * Math.sin(phi)) * sin,
56
+ y: d.y * cos + (right.y * Math.cos(phi) + up.y * Math.sin(phi)) * sin,
57
+ z: d.z * cos + (right.z * Math.cos(phi) + up.z * Math.sin(phi)) * sin,
58
+ };
59
+ };
60
+ return {
61
+ maxLive: Math.ceil((lifeMs * rate) / 1000) + 1,
62
+ spawnGapMs,
63
+ lifeMs,
64
+ sample(index, t) {
65
+ if (index < 0)
66
+ return undefined;
67
+ const age = t - index * spawnGapMs;
68
+ if (age < 0 || age >= lifeMs)
69
+ return undefined;
70
+ const seconds = age / 1000;
71
+ const dir = directionOf(index);
72
+ const particleSpeed = speed * (0.6 + 0.8 * rand01(seed, index, 2));
73
+ const travel = particleSpeed * seconds;
74
+ const pull = gravity ? 0.5 * seconds * seconds : 0;
75
+ const life = age / lifeMs;
76
+ const roll = alignToMotion
77
+ ? Math.atan2(dir.y * particleSpeed + (gravity?.y ?? 0) * seconds, dir.x * particleSpeed + (gravity?.x ?? 0) * seconds)
78
+ : 2 * Math.PI * rand01(seed, index, 3)
79
+ + (2 * rand01(seed, index, 4) - 1) * spin * age;
80
+ return {
81
+ position: {
82
+ x: dir.x * travel + (gravity ? gravity.x * pull : 0),
83
+ y: dir.y * travel + (gravity ? gravity.y * pull : 0),
84
+ z: dir.z * travel + (gravity ? gravity.z * pull : 0),
85
+ },
86
+ roll,
87
+ size: size * (0.7 + 0.6 * rand01(seed, index, 5)),
88
+ opacity: peak * smoothstep(0, FADE_IN_END, life) * (1 - smoothstep(FADE_OUT_START, 1, life)),
89
+ };
90
+ },
91
+ };
92
+ }
93
+ /**
94
+ * A stream of camera-facing billboard particles: a burst, a fountain, drifting
95
+ * embers - shaped by the emission options. Particles fade in, drift under
96
+ * `gravity`, and fade out; the runner triggers the lifecycle: {@link enter}
97
+ * starts emission, {@link exit} stops it and lets the live particles die out
98
+ * as the outro.
99
+ */
100
+ export class ParticlesAnimation {
101
+ constructor(options = {}) {
102
+ this.handles = [];
103
+ this.fades = [];
104
+ this.enterAt = Infinity;
105
+ this.exitAt = Infinity;
106
+ this.finished = false;
107
+ this.field = particleField(options);
108
+ this.colors = options.colors ?? DEFAULT_COLORS;
109
+ this.backend = options.backend;
110
+ this.texture = options.texture;
111
+ this.labelContent = options.label;
112
+ this.fadeLabel = options.fadeLabel ?? true;
113
+ this.emitter = options.emitter;
114
+ this.outroMs = Math.max(0, options.outroMs ?? 0);
115
+ }
116
+ mount(target) {
117
+ if (!target.style.position)
118
+ target.style.position = "relative";
119
+ const meshes = this.colors.map((color) => quad(1, [color]));
120
+ const texture = this.texture;
121
+ const backend = texture
122
+ ? async (rendererOptions) => {
123
+ const renderer = this.backend === "webgpu"
124
+ ? new (await import("../engines/little-3d-engine/renderers/webgpu-textured.js")).WebGPUTexturedRenderer(rendererOptions)
125
+ : this.backend === "webgl"
126
+ ? new (await import("../engines/little-3d-engine/renderers/webgl-textured.js")).WebGLTexturedRenderer(rendererOptions)
127
+ : new (await import("../engines/little-3d-engine/renderers/canvas2d-textured.js")).Canvas2DTexturedRenderer(rendererOptions);
128
+ for (const mesh of meshes)
129
+ renderer.setTexture(mesh, texture);
130
+ return renderer;
131
+ }
132
+ : this.backend;
133
+ const engine = new Little3dEngine({
134
+ backend,
135
+ camera: { position: { x: 0, y: 0, z: 3 } },
136
+ light: { intensity: 0, ambient: 1 },
137
+ });
138
+ for (let slot = 0; slot < this.field.maxLive; slot++) {
139
+ const fade = { mode: "one-sided", opacity: 0 };
140
+ this.fades.push(fade);
141
+ this.handles.push(engine.add(meshes[slot % meshes.length], { scale: 0, transparency: fade }));
142
+ }
143
+ this.engine = engine;
144
+ engine.mount(target).catch((error) => {
145
+ target.textContent = error instanceof Error ? error.message : String(error);
146
+ });
147
+ this.label = mountAnimationLabel(target, this.labelContent);
148
+ if (this.fadeLabel)
149
+ this.label.setOpacity(0);
150
+ }
151
+ enter(now) {
152
+ if (this.enterAt === Infinity)
153
+ this.enterAt = now;
154
+ }
155
+ exit(now) {
156
+ if (this.exitAt === Infinity)
157
+ this.exitAt = now;
158
+ }
159
+ isFinished() {
160
+ return this.finished;
161
+ }
162
+ render(now, frame) {
163
+ if (!this.engine || !this.label)
164
+ return;
165
+ if (this.exitAt !== Infinity && now >= this.exitAt + this.outroMs + this.field.lifeMs)
166
+ this.finished = true;
167
+ for (const handle of this.handles)
168
+ handle.transform.scale = 0;
169
+ if (this.enterAt !== Infinity) {
170
+ const t = now - this.enterAt;
171
+ const gap = this.field.spawnGapMs;
172
+ let first = Math.max(0, Math.ceil((t - this.field.lifeMs) / gap));
173
+ let last = Math.floor(t / gap);
174
+ if (this.exitAt !== Infinity) {
175
+ last = Math.min(last, Math.floor((this.exitAt - this.enterAt + this.outroMs) / gap));
176
+ }
177
+ first = Math.max(first, last - this.field.maxLive + 1);
178
+ for (let index = first; index <= last; index++) {
179
+ const sample = this.field.sample(index, t);
180
+ if (!sample)
181
+ continue;
182
+ const slot = index % this.handles.length;
183
+ const transform = this.handles[slot].transform;
184
+ const origin = this.emitter?.positionAt(this.enterAt + index * gap);
185
+ transform.position.x = sample.position.x + (origin?.x ?? 0);
186
+ transform.position.y = sample.position.y + (origin?.y ?? 0);
187
+ transform.position.z = sample.position.z + (origin?.z ?? 0);
188
+ transform.rotation.z = sample.roll;
189
+ transform.scale = sample.size;
190
+ this.fades[slot].opacity = sample.opacity;
191
+ }
192
+ }
193
+ this.label.setText(frame.indeterminate
194
+ ? (typeof this.labelContent === "string" ? this.labelContent : "")
195
+ : `${Math.round(frame.progress * 100)}%`);
196
+ if (this.fadeLabel) {
197
+ this.label.setOpacity(animationLabelOpacity(now, this.enterAt, this.field.lifeMs * FADE_IN_END, this.exitAt, this.field.lifeMs));
198
+ }
199
+ this.engine.render();
200
+ }
201
+ destroy() {
202
+ this.label?.container.remove();
203
+ this.label = undefined;
204
+ this.engine?.destroy();
205
+ this.engine = undefined;
206
+ this.handles.length = 0;
207
+ this.fades.length = 0;
208
+ }
209
+ }