3d-spinner 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/animation.d.ts +28 -0
- package/dist/animation.js +1 -0
- package/dist/animations/object-motion.d.ts +96 -0
- package/dist/animations/object-motion.js +339 -0
- package/dist/animations/spin.d.ts +44 -0
- package/dist/animations/spin.js +108 -0
- package/dist/engines/little-3d-engine/core/camera.d.ts +26 -0
- package/dist/engines/little-3d-engine/core/camera.js +32 -0
- package/dist/engines/little-3d-engine/core/geometry.d.ts +27 -0
- package/dist/engines/little-3d-engine/core/geometry.js +87 -0
- package/dist/engines/little-3d-engine/core/light.d.ts +31 -0
- package/dist/engines/little-3d-engine/core/light.js +35 -0
- package/dist/engines/little-3d-engine/core/math.d.ts +50 -0
- package/dist/engines/little-3d-engine/core/math.js +109 -0
- package/dist/engines/little-3d-engine/core/mesh.d.ts +22 -0
- package/dist/engines/little-3d-engine/core/mesh.js +8 -0
- package/dist/engines/little-3d-engine/little-3d-engine.d.ts +75 -0
- package/dist/engines/little-3d-engine/little-3d-engine.js +167 -0
- package/dist/engines/little-3d-engine/loaders/obj.d.ts +24 -0
- package/dist/engines/little-3d-engine/loaders/obj.js +48 -0
- package/dist/engines/little-3d-engine/renderer.d.ts +43 -0
- package/dist/engines/little-3d-engine/renderer.js +16 -0
- package/dist/engines/little-3d-engine/renderers/canvas2d.d.ts +12 -0
- package/dist/engines/little-3d-engine/renderers/canvas2d.js +73 -0
- package/dist/engines/little-3d-engine/renderers/webgl.d.ts +15 -0
- package/dist/engines/little-3d-engine/renderers/webgl.js +146 -0
- package/dist/engines/little-3d-engine/renderers/webgpu.d.ts +24 -0
- package/dist/engines/little-3d-engine/renderers/webgpu.js +222 -0
- package/dist/engines/little-3d-engine/shapes/cube-sphere.d.ts +11 -0
- package/dist/engines/little-3d-engine/shapes/cube-sphere.js +50 -0
- package/dist/engines/little-3d-engine/shapes/cube.d.ts +9 -0
- package/dist/engines/little-3d-engine/shapes/cube.js +30 -0
- package/dist/engines/little-3d-engine/shapes/icosphere.d.ts +11 -0
- package/dist/engines/little-3d-engine/shapes/icosphere.js +51 -0
- package/dist/engines/little-3d-engine/shapes/octa-sphere.d.ts +10 -0
- package/dist/engines/little-3d-engine/shapes/octa-sphere.js +31 -0
- package/dist/engines/little-3d-engine/shapes/octahedron.d.ts +8 -0
- package/dist/engines/little-3d-engine/shapes/octahedron.js +38 -0
- package/dist/engines/little-3d-engine/shapes/pyramid.d.ts +9 -0
- package/dist/engines/little-3d-engine/shapes/pyramid.js +26 -0
- package/dist/engines/little-3d-engine/shapes/tetrahedron.d.ts +8 -0
- package/dist/engines/little-3d-engine/shapes/tetrahedron.js +23 -0
- package/dist/engines/little-3d-engine/shapes/uv-sphere.d.ts +10 -0
- package/dist/engines/little-3d-engine/shapes/uv-sphere.js +50 -0
- package/dist/engines/little-tween-engine/core/tweens.d.ts +39 -0
- package/dist/engines/little-tween-engine/core/tweens.js +231 -0
- package/dist/engines/little-tween-engine/little-tween-engine.d.ts +21 -0
- package/dist/engines/little-tween-engine/little-tween-engine.js +16 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +110 -0
- package/dist/motion/circle.d.ts +17 -0
- package/dist/motion/circle.js +20 -0
- package/dist/motion/controller.d.ts +13 -0
- package/dist/motion/controller.js +1 -0
- package/dist/motion/figure-eight.d.ts +13 -0
- package/dist/motion/figure-eight.js +23 -0
- package/dist/motion/motion.d.ts +5 -0
- package/dist/motion/motion.js +4 -0
- package/dist/motion/square.d.ts +17 -0
- package/dist/motion/square.js +39 -0
- package/dist/motion/transitions.d.ts +44 -0
- package/dist/motion/transitions.js +60 -0
- package/dist/motion/wander.d.ts +22 -0
- package/dist/motion/wander.js +50 -0
- package/dist/progress-animation.d.ts +55 -0
- package/dist/progress-animation.js +128 -0
- package/package.json +74 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
function input(value, overextend) {
|
|
2
|
+
if (Number.isNaN(value))
|
|
3
|
+
return 0;
|
|
4
|
+
if (overextend)
|
|
5
|
+
return value;
|
|
6
|
+
return Math.min(1, Math.max(0, value));
|
|
7
|
+
}
|
|
8
|
+
export function linear(value, overextend = false) {
|
|
9
|
+
return input(value, overextend);
|
|
10
|
+
}
|
|
11
|
+
export function quadratic(value, overextend = false) {
|
|
12
|
+
return easeInQuad(value, overextend);
|
|
13
|
+
}
|
|
14
|
+
export function cubic(value, overextend = false) {
|
|
15
|
+
return easeInCubic(value, overextend);
|
|
16
|
+
}
|
|
17
|
+
export function quartic(value, overextend = false) {
|
|
18
|
+
return easeInQuart(value, overextend);
|
|
19
|
+
}
|
|
20
|
+
export function quintic(value, overextend = false) {
|
|
21
|
+
return easeInQuint(value, overextend);
|
|
22
|
+
}
|
|
23
|
+
export function easeInSine(value, overextend = false) {
|
|
24
|
+
const x = input(value, overextend);
|
|
25
|
+
return 1 - Math.cos((x * Math.PI) / 2);
|
|
26
|
+
}
|
|
27
|
+
export function easeOutSine(value, overextend = false) {
|
|
28
|
+
const x = input(value, overextend);
|
|
29
|
+
return Math.sin((x * Math.PI) / 2);
|
|
30
|
+
}
|
|
31
|
+
export function easeInOutSine(value, overextend = false) {
|
|
32
|
+
const x = input(value, overextend);
|
|
33
|
+
return -(Math.cos(Math.PI * x) - 1) / 2;
|
|
34
|
+
}
|
|
35
|
+
export function easeInQuad(value, overextend = false) {
|
|
36
|
+
const x = input(value, overextend);
|
|
37
|
+
return x * x;
|
|
38
|
+
}
|
|
39
|
+
export function easeOutQuad(value, overextend = false) {
|
|
40
|
+
const x = input(value, overextend);
|
|
41
|
+
return 1 - (1 - x) * (1 - x);
|
|
42
|
+
}
|
|
43
|
+
export function easeInOutQuad(value, overextend = false) {
|
|
44
|
+
const x = input(value, overextend);
|
|
45
|
+
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
|
|
46
|
+
}
|
|
47
|
+
export function easeInCubic(value, overextend = false) {
|
|
48
|
+
const x = input(value, overextend);
|
|
49
|
+
return x * x * x;
|
|
50
|
+
}
|
|
51
|
+
export function easeOutCubic(value, overextend = false) {
|
|
52
|
+
const x = input(value, overextend);
|
|
53
|
+
return 1 - Math.pow(1 - x, 3);
|
|
54
|
+
}
|
|
55
|
+
export function easeInOutCubic(value, overextend = false) {
|
|
56
|
+
const x = input(value, overextend);
|
|
57
|
+
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
|
|
58
|
+
}
|
|
59
|
+
export function easeInQuart(value, overextend = false) {
|
|
60
|
+
const x = input(value, overextend);
|
|
61
|
+
return x * x * x * x;
|
|
62
|
+
}
|
|
63
|
+
export function easeOutQuart(value, overextend = false) {
|
|
64
|
+
const x = input(value, overextend);
|
|
65
|
+
return 1 - Math.pow(1 - x, 4);
|
|
66
|
+
}
|
|
67
|
+
export function easeInOutQuart(value, overextend = false) {
|
|
68
|
+
const x = input(value, overextend);
|
|
69
|
+
return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2;
|
|
70
|
+
}
|
|
71
|
+
export function easeInQuint(value, overextend = false) {
|
|
72
|
+
const x = input(value, overextend);
|
|
73
|
+
return x * x * x * x * x;
|
|
74
|
+
}
|
|
75
|
+
export function easeOutQuint(value, overextend = false) {
|
|
76
|
+
const x = input(value, overextend);
|
|
77
|
+
return 1 - Math.pow(1 - x, 5);
|
|
78
|
+
}
|
|
79
|
+
export function easeInOutQuint(value, overextend = false) {
|
|
80
|
+
const x = input(value, overextend);
|
|
81
|
+
return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2;
|
|
82
|
+
}
|
|
83
|
+
export function easeInExpo(value, overextend = false) {
|
|
84
|
+
const x = input(value, overextend);
|
|
85
|
+
return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
|
|
86
|
+
}
|
|
87
|
+
export function easeOutExpo(value, overextend = false) {
|
|
88
|
+
const x = input(value, overextend);
|
|
89
|
+
return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
|
|
90
|
+
}
|
|
91
|
+
export function easeInOutExpo(value, overextend = false) {
|
|
92
|
+
const x = input(value, overextend);
|
|
93
|
+
return x === 0
|
|
94
|
+
? 0
|
|
95
|
+
: x === 1
|
|
96
|
+
? 1
|
|
97
|
+
: x < 0.5
|
|
98
|
+
? Math.pow(2, 20 * x - 10) / 2
|
|
99
|
+
: (2 - Math.pow(2, -20 * x + 10)) / 2;
|
|
100
|
+
}
|
|
101
|
+
export function easeInCirc(value, overextend = false) {
|
|
102
|
+
const x = input(value, overextend);
|
|
103
|
+
return 1 - Math.sqrt(1 - Math.pow(x, 2));
|
|
104
|
+
}
|
|
105
|
+
export function easeOutCirc(value, overextend = false) {
|
|
106
|
+
const x = input(value, overextend);
|
|
107
|
+
return Math.sqrt(1 - Math.pow(x - 1, 2));
|
|
108
|
+
}
|
|
109
|
+
export function easeInOutCirc(value, overextend = false) {
|
|
110
|
+
const x = input(value, overextend);
|
|
111
|
+
return x < 0.5
|
|
112
|
+
? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2
|
|
113
|
+
: (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2;
|
|
114
|
+
}
|
|
115
|
+
export function easeInBack(value, overextend = false) {
|
|
116
|
+
const x = input(value, overextend);
|
|
117
|
+
const c1 = 1.70158;
|
|
118
|
+
const c3 = c1 + 1;
|
|
119
|
+
return c3 * x * x * x - c1 * x * x;
|
|
120
|
+
}
|
|
121
|
+
export function easeOutBack(value, overextend = false) {
|
|
122
|
+
const x = input(value, overextend);
|
|
123
|
+
const c1 = 1.70158;
|
|
124
|
+
const c3 = c1 + 1;
|
|
125
|
+
return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
|
|
126
|
+
}
|
|
127
|
+
export function easeInOutBack(value, overextend = false) {
|
|
128
|
+
const x = input(value, overextend);
|
|
129
|
+
const c1 = 1.70158;
|
|
130
|
+
const c2 = c1 * 1.525;
|
|
131
|
+
return x < 0.5
|
|
132
|
+
? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2
|
|
133
|
+
: (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
|
|
134
|
+
}
|
|
135
|
+
export function easeInElastic(value, overextend = false) {
|
|
136
|
+
const x = input(value, overextend);
|
|
137
|
+
const c4 = (2 * Math.PI) / 3;
|
|
138
|
+
return x === 0
|
|
139
|
+
? 0
|
|
140
|
+
: x === 1
|
|
141
|
+
? 1
|
|
142
|
+
: -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4);
|
|
143
|
+
}
|
|
144
|
+
export function easeOutElastic(value, overextend = false) {
|
|
145
|
+
const x = input(value, overextend);
|
|
146
|
+
const c4 = (2 * Math.PI) / 3;
|
|
147
|
+
return x === 0
|
|
148
|
+
? 0
|
|
149
|
+
: x === 1
|
|
150
|
+
? 1
|
|
151
|
+
: Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
|
|
152
|
+
}
|
|
153
|
+
export function easeInOutElastic(value, overextend = false) {
|
|
154
|
+
const x = input(value, overextend);
|
|
155
|
+
const c5 = (2 * Math.PI) / 4.5;
|
|
156
|
+
return x === 0
|
|
157
|
+
? 0
|
|
158
|
+
: x === 1
|
|
159
|
+
? 1
|
|
160
|
+
: x < 0.5
|
|
161
|
+
? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2
|
|
162
|
+
: (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1;
|
|
163
|
+
}
|
|
164
|
+
export function easeInBounce(value, overextend = false) {
|
|
165
|
+
const x = input(value, overextend);
|
|
166
|
+
return 1 - easeOutBounce(1 - x, true);
|
|
167
|
+
}
|
|
168
|
+
export function easeOutBounce(value, overextend = false) {
|
|
169
|
+
let x = input(value, overextend);
|
|
170
|
+
const n1 = 7.5625;
|
|
171
|
+
const d1 = 2.75;
|
|
172
|
+
if (x < 1 / d1) {
|
|
173
|
+
return n1 * x * x;
|
|
174
|
+
}
|
|
175
|
+
if (x < 2 / d1) {
|
|
176
|
+
x -= 1.5 / d1;
|
|
177
|
+
return n1 * x * x + 0.75;
|
|
178
|
+
}
|
|
179
|
+
if (x < 2.5 / d1) {
|
|
180
|
+
x -= 2.25 / d1;
|
|
181
|
+
return n1 * x * x + 0.9375;
|
|
182
|
+
}
|
|
183
|
+
x -= 2.625 / d1;
|
|
184
|
+
return n1 * x * x + 0.984375;
|
|
185
|
+
}
|
|
186
|
+
export function easeInOutBounce(value, overextend = false) {
|
|
187
|
+
const x = input(value, overextend);
|
|
188
|
+
return x < 0.5
|
|
189
|
+
? (1 - easeOutBounce(1 - 2 * x, true)) / 2
|
|
190
|
+
: (1 + easeOutBounce(2 * x - 1, true)) / 2;
|
|
191
|
+
}
|
|
192
|
+
export const easeTypes = {
|
|
193
|
+
linear,
|
|
194
|
+
quadratic,
|
|
195
|
+
cubic,
|
|
196
|
+
quartic,
|
|
197
|
+
quintic,
|
|
198
|
+
easeInSine,
|
|
199
|
+
easeOutSine,
|
|
200
|
+
easeInOutSine,
|
|
201
|
+
easeInQuad,
|
|
202
|
+
easeOutQuad,
|
|
203
|
+
easeInOutQuad,
|
|
204
|
+
easeInCubic,
|
|
205
|
+
easeOutCubic,
|
|
206
|
+
easeInOutCubic,
|
|
207
|
+
easeInQuart,
|
|
208
|
+
easeOutQuart,
|
|
209
|
+
easeInOutQuart,
|
|
210
|
+
easeInQuint,
|
|
211
|
+
easeOutQuint,
|
|
212
|
+
easeInOutQuint,
|
|
213
|
+
easeInExpo,
|
|
214
|
+
easeOutExpo,
|
|
215
|
+
easeInOutExpo,
|
|
216
|
+
easeInCirc,
|
|
217
|
+
easeOutCirc,
|
|
218
|
+
easeInOutCirc,
|
|
219
|
+
easeInBack,
|
|
220
|
+
easeOutBack,
|
|
221
|
+
easeInOutBack,
|
|
222
|
+
easeInElastic,
|
|
223
|
+
easeOutElastic,
|
|
224
|
+
easeInOutElastic,
|
|
225
|
+
easeInBounce,
|
|
226
|
+
easeOutBounce,
|
|
227
|
+
easeInOutBounce,
|
|
228
|
+
};
|
|
229
|
+
export function ease(type, value, overextend = false) {
|
|
230
|
+
return easeTypes[type](value, overextend);
|
|
231
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type EaseType } from "./core/tweens.js";
|
|
2
|
+
/** Options for {@link LittleTweenEngine}. */
|
|
3
|
+
export interface LittleTweenEngineOptions {
|
|
4
|
+
/** Ease curve used when one is not provided to {@link LittleTweenEngine.value}. */
|
|
5
|
+
type?: EaseType;
|
|
6
|
+
/** Allow input values outside 0..1. Default `false` clamps input to 0..1. */
|
|
7
|
+
overextend?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A small zero-dependency tween engine for mapping progress values through
|
|
11
|
+
* named easing curves.
|
|
12
|
+
*/
|
|
13
|
+
export declare class LittleTweenEngine {
|
|
14
|
+
private readonly type;
|
|
15
|
+
private readonly overextend;
|
|
16
|
+
constructor(options?: LittleTweenEngineOptions);
|
|
17
|
+
/** Map `value` through the selected ease type. */
|
|
18
|
+
value(value: number, type?: EaseType, overextend?: boolean): number;
|
|
19
|
+
}
|
|
20
|
+
export { ease, easeTypes, linear, quadratic, cubic, quartic, quintic, easeInSine, easeOutSine, easeInOutSine, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeInQuint, easeOutQuint, easeInOutQuint, easeInExpo, easeOutExpo, easeInOutExpo, easeInCirc, easeOutCirc, easeInOutCirc, easeInBack, easeOutBack, easeInOutBack, easeInElastic, easeOutElastic, easeInOutElastic, easeInBounce, easeOutBounce, easeInOutBounce, } from "./core/tweens.js";
|
|
21
|
+
export type { EaseFunction, EaseType } from "./core/tweens.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ease } from "./core/tweens.js";
|
|
2
|
+
/**
|
|
3
|
+
* A small zero-dependency tween engine for mapping progress values through
|
|
4
|
+
* named easing curves.
|
|
5
|
+
*/
|
|
6
|
+
export class LittleTweenEngine {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.type = options.type ?? "linear";
|
|
9
|
+
this.overextend = options.overextend ?? false;
|
|
10
|
+
}
|
|
11
|
+
/** Map `value` through the selected ease type. */
|
|
12
|
+
value(value, type = this.type, overextend = this.overextend) {
|
|
13
|
+
return ease(type, value, overextend);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export { ease, easeTypes, linear, quadratic, cubic, quartic, quintic, easeInSine, easeOutSine, easeInOutSine, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeInQuint, easeOutQuint, easeInOutQuint, easeInExpo, easeOutExpo, easeInOutExpo, easeInCirc, easeOutCirc, easeInOutCirc, easeInBack, easeOutBack, easeInOutBack, easeInElastic, easeOutElastic, easeInOutElastic, easeInBounce, easeOutBounce, easeInOutBounce, } from "./core/tweens.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SpinnerAnimation } from "./animation.js";
|
|
2
|
+
/** A spinner driven by real progress the caller reports via {@link Spinner.setProgress}. */
|
|
3
|
+
export interface ProgressSpinnerOptions {
|
|
4
|
+
type?: "progress";
|
|
5
|
+
/** The visual to play. */
|
|
6
|
+
animation: SpinnerAnimation;
|
|
7
|
+
/**
|
|
8
|
+
* Initial progress 0..1. A value above 0 plays the intro immediately; omit it
|
|
9
|
+
* to start idle until {@link Spinner.setProgress} is called.
|
|
10
|
+
*/
|
|
11
|
+
progress?: number;
|
|
12
|
+
/** Auto-complete (drive progress to 1, playing the outro) after this many ms. */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/** Auto-complete at this absolute time. If both are set, the earlier wins. */
|
|
15
|
+
until?: Date;
|
|
16
|
+
}
|
|
17
|
+
/** A self-driving spinner: it loops a synthetic progress on a timer until stopped. */
|
|
18
|
+
export interface IndeterminateSpinnerOptions {
|
|
19
|
+
type: "indeterminate";
|
|
20
|
+
/** The visual to play. */
|
|
21
|
+
animation: SpinnerAnimation;
|
|
22
|
+
/** `"bounce"` ramps 0->1->0; `"restart"` ramps 0->1 then repeats. Default `"bounce"`. */
|
|
23
|
+
loop?: "bounce" | "restart";
|
|
24
|
+
/** Milliseconds for one 0->1 sweep. Must be finite and greater than zero. Default `2000`. */
|
|
25
|
+
periodMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export type SpinnerOptions = ProgressSpinnerOptions | IndeterminateSpinnerOptions;
|
|
28
|
+
export interface Spinner {
|
|
29
|
+
/** Set the progress target (0..1). No-op for an indeterminate spinner. */
|
|
30
|
+
setProgress(target: number): void;
|
|
31
|
+
/** Play the outro, then stop animating (keeps the injected DOM in place). */
|
|
32
|
+
stop(): void;
|
|
33
|
+
/** Stop immediately and remove the injected DOM (no outro). Safe to call more than once. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
}
|
|
36
|
+
export declare function createSpinner(target: HTMLElement, options: SpinnerOptions): Spinner;
|
|
37
|
+
export type { SpinnerAnimation, AnimationFrame } from "./animation.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
function clamp01(value) {
|
|
2
|
+
if (Number.isNaN(value))
|
|
3
|
+
return 0;
|
|
4
|
+
return Math.min(1, Math.max(0, value));
|
|
5
|
+
}
|
|
6
|
+
function lerp(from, to, t) {
|
|
7
|
+
return from + (to - from) * t;
|
|
8
|
+
}
|
|
9
|
+
export function createSpinner(target, options) {
|
|
10
|
+
if (!(target instanceof HTMLElement)) {
|
|
11
|
+
throw new Error("3d-spinner: createSpinner requires a target HTMLElement.");
|
|
12
|
+
}
|
|
13
|
+
const { animation } = options;
|
|
14
|
+
const indeterminate = options.type === "indeterminate";
|
|
15
|
+
if (indeterminate &&
|
|
16
|
+
options.periodMs !== undefined &&
|
|
17
|
+
(!Number.isFinite(options.periodMs) || options.periodMs <= 0)) {
|
|
18
|
+
throw new RangeError("3d-spinner: periodMs must be a finite number greater than zero.");
|
|
19
|
+
}
|
|
20
|
+
animation.mount(target);
|
|
21
|
+
const start = performance.now();
|
|
22
|
+
let rafId = 0;
|
|
23
|
+
let stopped = false;
|
|
24
|
+
let destroyed = false;
|
|
25
|
+
let entered = false;
|
|
26
|
+
let exiting = false;
|
|
27
|
+
// Progress source (determinate only).
|
|
28
|
+
let current = 0;
|
|
29
|
+
let targetProgress = 0;
|
|
30
|
+
let deadline = Infinity;
|
|
31
|
+
if (!indeterminate) {
|
|
32
|
+
const opts = options;
|
|
33
|
+
if (typeof opts.progress === "number") {
|
|
34
|
+
current = clamp01(opts.progress);
|
|
35
|
+
targetProgress = current;
|
|
36
|
+
}
|
|
37
|
+
if (typeof opts.timeout === "number")
|
|
38
|
+
deadline = Math.min(deadline, start + opts.timeout);
|
|
39
|
+
if (opts.until instanceof Date)
|
|
40
|
+
deadline = Math.min(deadline, opts.until.getTime());
|
|
41
|
+
}
|
|
42
|
+
function computeProgress(now) {
|
|
43
|
+
if (!indeterminate) {
|
|
44
|
+
if (now >= deadline)
|
|
45
|
+
targetProgress = 1;
|
|
46
|
+
current = lerp(current, targetProgress, 0.12);
|
|
47
|
+
if (Math.abs(targetProgress - current) < 0.0005)
|
|
48
|
+
current = targetProgress;
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
const opts = options;
|
|
52
|
+
const period = opts.periodMs ?? 2000;
|
|
53
|
+
const t = (now - start) / period;
|
|
54
|
+
if ((opts.loop ?? "bounce") === "restart")
|
|
55
|
+
return t - Math.floor(t);
|
|
56
|
+
const phase = t - 2 * Math.floor(t / 2); // 0..2
|
|
57
|
+
return phase <= 1 ? phase : 2 - phase; // triangle 0..1..0
|
|
58
|
+
}
|
|
59
|
+
function frame(now) {
|
|
60
|
+
if (stopped)
|
|
61
|
+
return;
|
|
62
|
+
const progress = computeProgress(now);
|
|
63
|
+
if (!entered && (indeterminate || progress > 0)) {
|
|
64
|
+
animation.enter(now);
|
|
65
|
+
entered = true;
|
|
66
|
+
}
|
|
67
|
+
if (!exiting && entered && !indeterminate && progress >= 1 && targetProgress >= 1) {
|
|
68
|
+
animation.exit(now);
|
|
69
|
+
exiting = true;
|
|
70
|
+
}
|
|
71
|
+
const target = indeterminate ? progress : targetProgress;
|
|
72
|
+
animation.render(now, { progress, targetProgress: target, indeterminate });
|
|
73
|
+
if (exiting && animation.isFinished()) {
|
|
74
|
+
halt();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
rafId = requestAnimationFrame(frame);
|
|
78
|
+
}
|
|
79
|
+
function halt() {
|
|
80
|
+
if (stopped)
|
|
81
|
+
return;
|
|
82
|
+
stopped = true;
|
|
83
|
+
if (rafId)
|
|
84
|
+
cancelAnimationFrame(rafId);
|
|
85
|
+
rafId = 0;
|
|
86
|
+
}
|
|
87
|
+
function setProgress(value) {
|
|
88
|
+
if (!indeterminate)
|
|
89
|
+
targetProgress = clamp01(value);
|
|
90
|
+
}
|
|
91
|
+
function stop() {
|
|
92
|
+
if (stopped || exiting)
|
|
93
|
+
return;
|
|
94
|
+
if (!entered) {
|
|
95
|
+
halt();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
animation.exit(performance.now());
|
|
99
|
+
exiting = true;
|
|
100
|
+
}
|
|
101
|
+
function destroy() {
|
|
102
|
+
if (destroyed)
|
|
103
|
+
return;
|
|
104
|
+
destroyed = true;
|
|
105
|
+
halt();
|
|
106
|
+
animation.destroy();
|
|
107
|
+
}
|
|
108
|
+
rafId = requestAnimationFrame(frame);
|
|
109
|
+
return { setProgress, stop, destroy };
|
|
110
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MotionController } from "./controller.js";
|
|
2
|
+
/** Options for {@link circleMotion}. */
|
|
3
|
+
export interface CircleMotionOptions {
|
|
4
|
+
/** Circle radius in scene units. Default `1.3`. */
|
|
5
|
+
radius?: number;
|
|
6
|
+
/** Milliseconds for one full revolution. Default `3000`. */
|
|
7
|
+
periodMs?: number;
|
|
8
|
+
/** Tilt of the circle's plane about the X axis, radians. `0` faces the camera. Default `0.5`. */
|
|
9
|
+
tilt?: number;
|
|
10
|
+
/** Travel direction: `1` counter-clockwise, `-1` clockwise. Default `1`. */
|
|
11
|
+
direction?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Orbits a circular path, nose following the tangent. `tilt` leans the circle
|
|
15
|
+
* away from the camera so it reads as a 3D orbit rather than a flat ring.
|
|
16
|
+
*/
|
|
17
|
+
export declare function circleMotion(options?: CircleMotionOptions): MotionController;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orbits a circular path, nose following the tangent. `tilt` leans the circle
|
|
3
|
+
* away from the camera so it reads as a 3D orbit rather than a flat ring.
|
|
4
|
+
*/
|
|
5
|
+
export function circleMotion(options = {}) {
|
|
6
|
+
const radius = options.radius ?? 1.3;
|
|
7
|
+
const periodMs = options.periodMs ?? 3000;
|
|
8
|
+
const tilt = options.tilt ?? 0.5;
|
|
9
|
+
const direction = options.direction ?? 1;
|
|
10
|
+
const cosTilt = Math.cos(tilt);
|
|
11
|
+
const sinTilt = Math.sin(tilt);
|
|
12
|
+
return {
|
|
13
|
+
positionAt(t) {
|
|
14
|
+
const angle = ((direction * t) / periodMs) * Math.PI * 2;
|
|
15
|
+
const x = radius * Math.cos(angle);
|
|
16
|
+
const flatY = radius * Math.sin(angle);
|
|
17
|
+
return { x, y: flatY * cosTilt, z: flatY * sinTilt };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Vec3 } from "../engines/little-3d-engine/little-3d-engine.js";
|
|
2
|
+
/**
|
|
3
|
+
* Describes *how* an object moves: a pure function from time to a position in
|
|
4
|
+
* scene units. The animation that consumes it derives orientation from the path
|
|
5
|
+
* tangent, so a controller only has to say where the object is at time `t`.
|
|
6
|
+
*
|
|
7
|
+
* `positionAt` must be a pure function of `t` (same input -> same output) so the
|
|
8
|
+
* path is reproducible and can be sampled at offset times.
|
|
9
|
+
*/
|
|
10
|
+
export interface MotionController {
|
|
11
|
+
/** Position in scene units at time `t` (milliseconds). */
|
|
12
|
+
positionAt(t: number): Vec3;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MotionController } from "./controller.js";
|
|
2
|
+
/** Options for {@link figureEightMotion}. */
|
|
3
|
+
export interface FigureEightMotionOptions {
|
|
4
|
+
/** Overall scale of the figure-8 in scene units. Default `1`. */
|
|
5
|
+
size?: number;
|
|
6
|
+
/** Milliseconds for one full lap. Default `3600`. */
|
|
7
|
+
periodMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Flies a seamless 3D figure-8, the same lemniscate path the flight animation
|
|
11
|
+
* loops on. Nose follows the tangent.
|
|
12
|
+
*/
|
|
13
|
+
export declare function figureEightMotion(options?: FigureEightMotionOptions): MotionController;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Figure-8 (lemniscate) amplitudes. A closed, periodic curve so it repeats
|
|
2
|
+
// seamlessly; the z term gives it depth so it reads as 3D rather than flat.
|
|
3
|
+
const LOOP_X = 1.5;
|
|
4
|
+
const LOOP_Y = 1.0;
|
|
5
|
+
const LOOP_Z = 1.05;
|
|
6
|
+
/**
|
|
7
|
+
* Flies a seamless 3D figure-8, the same lemniscate path the flight animation
|
|
8
|
+
* loops on. Nose follows the tangent.
|
|
9
|
+
*/
|
|
10
|
+
export function figureEightMotion(options = {}) {
|
|
11
|
+
const size = options.size ?? 1;
|
|
12
|
+
const periodMs = options.periodMs ?? 3600;
|
|
13
|
+
return {
|
|
14
|
+
positionAt(t) {
|
|
15
|
+
const a = (t / periodMs) * Math.PI * 2;
|
|
16
|
+
return {
|
|
17
|
+
x: size * LOOP_X * Math.sin(a),
|
|
18
|
+
y: size * LOOP_Y * Math.sin(a) * Math.cos(a),
|
|
19
|
+
z: size * LOOP_Z * Math.cos(a),
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { MotionController } from "./controller.js";
|
|
2
|
+
export { circleMotion, type CircleMotionOptions } from "./circle.js";
|
|
3
|
+
export { squareMotion, type SquareMotionOptions } from "./square.js";
|
|
4
|
+
export { figureEightMotion, type FigureEightMotionOptions } from "./figure-eight.js";
|
|
5
|
+
export { wanderMotion, type WanderMotionOptions, type WanderBounds, } from "./wander.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MotionController } from "./controller.js";
|
|
2
|
+
/** Options for {@link squareMotion}. */
|
|
3
|
+
export interface SquareMotionOptions {
|
|
4
|
+
/** Side length of the square in scene units. Default `2.4`. */
|
|
5
|
+
size?: number;
|
|
6
|
+
/** Milliseconds for one full lap of the perimeter. Default `4000`. */
|
|
7
|
+
periodMs?: number;
|
|
8
|
+
/** Tilt of the square's plane about the X axis, radians. `0` faces the camera. Default `0.45`. */
|
|
9
|
+
tilt?: number;
|
|
10
|
+
/** Travel direction: `1` counter-clockwise, `-1` clockwise. Default `1`. */
|
|
11
|
+
direction?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Travels the perimeter of a square at constant speed, turning sharply at each
|
|
15
|
+
* corner. `tilt` leans the square away from the camera for a 3D read.
|
|
16
|
+
*/
|
|
17
|
+
export declare function squareMotion(options?: SquareMotionOptions): MotionController;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Travels the perimeter of a square at constant speed, turning sharply at each
|
|
3
|
+
* corner. `tilt` leans the square away from the camera for a 3D read.
|
|
4
|
+
*/
|
|
5
|
+
export function squareMotion(options = {}) {
|
|
6
|
+
const half = (options.size ?? 2.4) / 2;
|
|
7
|
+
const periodMs = options.periodMs ?? 4000;
|
|
8
|
+
const tilt = options.tilt ?? 0.45;
|
|
9
|
+
const direction = options.direction ?? 1;
|
|
10
|
+
const cosTilt = Math.cos(tilt);
|
|
11
|
+
const sinTilt = Math.sin(tilt);
|
|
12
|
+
return {
|
|
13
|
+
positionAt(t) {
|
|
14
|
+
const lap = (direction * t) / periodMs;
|
|
15
|
+
const s = (lap - Math.floor(lap)) * 4; // 0..4 around the four edges
|
|
16
|
+
const edge = Math.floor(s);
|
|
17
|
+
const f = s - edge;
|
|
18
|
+
let flatX;
|
|
19
|
+
let flatY;
|
|
20
|
+
if (edge === 0) {
|
|
21
|
+
flatX = -half + 2 * half * f;
|
|
22
|
+
flatY = -half;
|
|
23
|
+
}
|
|
24
|
+
else if (edge === 1) {
|
|
25
|
+
flatX = half;
|
|
26
|
+
flatY = -half + 2 * half * f;
|
|
27
|
+
}
|
|
28
|
+
else if (edge === 2) {
|
|
29
|
+
flatX = half - 2 * half * f;
|
|
30
|
+
flatY = half;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
flatX = -half;
|
|
34
|
+
flatY = half - 2 * half * f;
|
|
35
|
+
}
|
|
36
|
+
return { x: flatX, y: flatY * cosTilt, z: flatY * sinTilt };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Vec3 } from "../engines/little-3d-engine/little-3d-engine.js";
|
|
2
|
+
export type ObjectMotionTransitionPhase = "intro" | "outro";
|
|
3
|
+
export interface ObjectMotionTransitionInput {
|
|
4
|
+
/** How far through the transition we are, from `0` to `1`. */
|
|
5
|
+
delta: number;
|
|
6
|
+
/** Intro endpoint or outro startpoint where the real motion path hands off. */
|
|
7
|
+
position: Vec3;
|
|
8
|
+
/** Unit direction of travel at the handoff point, when available. */
|
|
9
|
+
direction?: Vec3;
|
|
10
|
+
/** Velocity at the handoff point in scene units per millisecond, when available. */
|
|
11
|
+
velocity?: Vec3;
|
|
12
|
+
/** Full-size scale for the object, when relevant. */
|
|
13
|
+
size?: number;
|
|
14
|
+
/** Transition duration in milliseconds. */
|
|
15
|
+
durationMs: number;
|
|
16
|
+
/** Elapsed transition time in milliseconds. */
|
|
17
|
+
elapsedMs: number;
|
|
18
|
+
/** Whether this is running before or after the real motion path. */
|
|
19
|
+
phase: ObjectMotionTransitionPhase;
|
|
20
|
+
}
|
|
21
|
+
export interface ObjectMotionTransitionOutput {
|
|
22
|
+
/** Override position for this frame. */
|
|
23
|
+
position?: Vec3;
|
|
24
|
+
/** Override scale for this frame. */
|
|
25
|
+
size?: number;
|
|
26
|
+
/** Override engine Euler orientation for this frame. */
|
|
27
|
+
orientation?: Vec3;
|
|
28
|
+
}
|
|
29
|
+
export type ObjectMotionTransition = (input: ObjectMotionTransitionInput) => ObjectMotionTransitionOutput;
|
|
30
|
+
export interface ObjectMotionTransitionWithDuration {
|
|
31
|
+
transition: ObjectMotionTransition;
|
|
32
|
+
durationMs?: number;
|
|
33
|
+
}
|
|
34
|
+
export type ObjectMotionTransitionConfig = ObjectMotionTransition | ObjectMotionTransitionWithDuration;
|
|
35
|
+
export interface DirectionTransitionOptions {
|
|
36
|
+
/** Direction to travel in. Defaults to the path direction at the handoff point. */
|
|
37
|
+
direction?: Vec3;
|
|
38
|
+
/** Distance to travel when no useful velocity is supplied. Default `3.5`. */
|
|
39
|
+
distance?: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function enterFromObjectDirection(options?: DirectionTransitionOptions): ObjectMotionTransition;
|
|
42
|
+
export declare function leaveInObjectDirection(options?: DirectionTransitionOptions): ObjectMotionTransition;
|
|
43
|
+
export declare function grow(): ObjectMotionTransition;
|
|
44
|
+
export declare function shrink(): ObjectMotionTransition;
|