micro-canvas-confetti-native 0.1.0 → 1.0.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.
Files changed (63) hide show
  1. package/README.md +158 -142
  2. package/lib/commonjs/api.js +3 -3
  3. package/lib/commonjs/api.js.map +1 -1
  4. package/lib/commonjs/coins.js.map +1 -1
  5. package/lib/commonjs/constants.js +6 -6
  6. package/lib/commonjs/constants.js.map +1 -1
  7. package/lib/commonjs/core.js.map +1 -1
  8. package/lib/commonjs/engine.js +3 -3
  9. package/lib/commonjs/engine.js.map +1 -1
  10. package/lib/commonjs/hooks/useReducedMotion.js +3 -3
  11. package/lib/commonjs/hooks/useReducedMotion.js.map +1 -1
  12. package/lib/commonjs/host/ParticleHost.js +3 -3
  13. package/lib/commonjs/host/ParticleHost.js.map +1 -1
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/physics/collect.js.map +1 -1
  16. package/lib/commonjs/physics/drag.js.map +1 -1
  17. package/lib/commonjs/physics/spawn.js.map +1 -1
  18. package/lib/commonjs/physics/wobble.js.map +1 -1
  19. package/lib/commonjs/sequence.js.map +1 -1
  20. package/lib/commonjs/types.js.map +1 -1
  21. package/lib/commonjs/utils/math.js.map +1 -1
  22. package/lib/commonjs/utils/validation.js +3 -3
  23. package/lib/commonjs/utils/validation.js.map +1 -1
  24. package/lib/module/api.js +3 -3
  25. package/lib/module/api.js.map +1 -1
  26. package/lib/module/coins.js.map +1 -1
  27. package/lib/module/constants.js +6 -6
  28. package/lib/module/constants.js.map +1 -1
  29. package/lib/module/core.js +3 -3
  30. package/lib/module/core.js.map +1 -1
  31. package/lib/module/engine.js +3 -3
  32. package/lib/module/engine.js.map +1 -1
  33. package/lib/module/hooks/useReducedMotion.js +3 -3
  34. package/lib/module/hooks/useReducedMotion.js.map +1 -1
  35. package/lib/module/host/ParticleHost.js +3 -3
  36. package/lib/module/host/ParticleHost.js.map +1 -1
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/physics/collect.js.map +1 -1
  39. package/lib/module/physics/drag.js.map +1 -1
  40. package/lib/module/physics/spawn.js.map +1 -1
  41. package/lib/module/physics/wobble.js.map +1 -1
  42. package/lib/module/sequence.js.map +1 -1
  43. package/lib/module/types.js.map +1 -1
  44. package/lib/module/utils/math.js.map +1 -1
  45. package/lib/module/utils/validation.js +3 -3
  46. package/lib/module/utils/validation.js.map +1 -1
  47. package/package.json +118 -118
  48. package/src/api.ts +78 -78
  49. package/src/coins.ts +8 -8
  50. package/src/constants.ts +187 -187
  51. package/src/core.ts +18 -18
  52. package/src/engine.ts +354 -354
  53. package/src/hooks/useReducedMotion.ts +26 -26
  54. package/src/host/ParticleHost.tsx +181 -181
  55. package/src/index.ts +33 -33
  56. package/src/physics/collect.ts +100 -100
  57. package/src/physics/drag.ts +15 -15
  58. package/src/physics/spawn.ts +139 -139
  59. package/src/physics/wobble.ts +11 -11
  60. package/src/sequence.ts +39 -39
  61. package/src/types.ts +243 -243
  62. package/src/utils/math.ts +49 -49
  63. package/src/utils/validation.ts +203 -203
package/src/types.ts CHANGED
@@ -1,243 +1,243 @@
1
- import type { ReactElement } from "react";
2
-
3
- /** Shape drawn for a single confetti piece. */
4
- export type ParticleShape = "rect" | "circle";
5
-
6
- /** What each piece looks like: colored confetti or a coin image. */
7
- export type ParticleAppearance = "confetti" | "coin";
8
-
9
- /** Built-in coin artwork. Omit to use `h-keystone`, or pass your own image. */
10
- export type CoinType = "h-keystone";
11
-
12
- /**
13
- * One node per coin, called with the piece index when that piece is created.
14
- * Use this for an SVG the app already renders. The host moves a wrapper view;
15
- * the node should fill that box (`width` and `height` `"100%"`).
16
- */
17
- export type RenderCoinFn = (index: number) => ReactElement;
18
-
19
- /**
20
- * Image for a custom coin — anything React Native `<Image source>` accepts
21
- * (`require("./coin.png")`, `{ uri }`). Kept loose so the core stays RN-free.
22
- */
23
- export type CoinSource = number | { readonly uri: string } | object;
24
-
25
- /** Named burst style with tuned defaults. */
26
- export type ConfettiPreset = "celebration" | "subtle" | "cannon" | "spark";
27
-
28
- /** How long a burst stays visible before pieces fade out. */
29
- export type ConfettiDuration = "short" | "normal" | "long";
30
-
31
- /** Screen point in window coordinates (dp). */
32
- export interface ConfettiOrigin {
33
- readonly x: number;
34
- readonly y: number;
35
- }
36
-
37
- /** Hook to override properties on a piece as it is created. */
38
- export type CreateParticleFn = (
39
- index: number,
40
- defaults: Readonly<Partial<Particle>>,
41
- ) => Partial<Particle>;
42
-
43
- /** Look of the pieces, shared by bursts and collect flights. */
44
- export interface AppearanceOptions {
45
- /** `"confetti"` (default) or `"coin"`. */
46
- readonly appearance?: ParticleAppearance;
47
- /** Built-in coin. Defaults to `h-keystone`. Ignored when `coinSource` or `renderCoin` is set. */
48
- readonly coinType?: CoinType;
49
- /** Custom bitmap. Anything `<Image source>` accepts (PNG, WebP). Overrides `coinType`. */
50
- readonly coinSource?: CoinSource;
51
- /**
52
- * Custom node per coin, for an SVG (or any view) the app owns.
53
- * Overrides `coinSource`. A heavy tree copied once per coin costs more than one shared PNG.
54
- */
55
- readonly renderCoin?: RenderCoinFn;
56
- readonly colors?: readonly string[];
57
- readonly shapes?: readonly ParticleShape[];
58
- /** Size multiplier (0.2–3). */
59
- readonly scalar?: number;
60
- /** Do nothing when the OS "Reduce Motion" setting is on. Default `true`. */
61
- readonly disableForReducedMotion?: boolean;
62
- }
63
-
64
- /** Options for a physics burst. */
65
- export interface ConfettiOptions extends AppearanceOptions {
66
- readonly particleCount?: number;
67
- /** Defaults to the center of the host. */
68
- readonly origin?: ConfettiOrigin;
69
- readonly angle?: number;
70
- readonly spread?: number;
71
- readonly startVelocity?: number;
72
- readonly gravity?: number;
73
- readonly drag?: number;
74
- readonly decay?: number;
75
- readonly duration?: ConfettiDuration;
76
- readonly rotationSpeed?: number;
77
- readonly wobbleSpeed?: number;
78
- readonly ticks?: number;
79
- readonly preset?: ConfettiPreset;
80
- /** Max distance from the origin. 0 = no limit. */
81
- readonly burstRadius?: number;
82
- readonly createParticle?: CreateParticleFn;
83
- }
84
-
85
- /** Options for a collect flight: pieces start big at `origin` and shrink into `target`. */
86
- export interface CollectOptions extends AppearanceOptions {
87
- /** Where pieces fly to, e.g. the center of a coins label (see `measureCenter`). */
88
- readonly target: ConfettiOrigin;
89
- /** Defaults to the center of the host. */
90
- readonly origin?: ConfettiOrigin;
91
- readonly particleCount?: number;
92
- /** Average flight time per piece in ms. */
93
- readonly flightMs?: number;
94
- /** Average delay between pieces in ms. */
95
- readonly staggerMs?: number;
96
- /** Called as each piece lands — handy for ticking a counter up. */
97
- readonly onArrive?: (index: number, total: number) => void;
98
- /**
99
- * Read each frame so the flight follows a moving badge (scroll, layout).
100
- * Return window coordinates. `target` is the starting aim.
101
- */
102
- readonly trackTarget?: () => ConfettiOrigin | null;
103
- }
104
-
105
- /** A single burst piece and its motion state. */
106
- export interface Particle {
107
- x: number;
108
- y: number;
109
- vx: number;
110
- vy: number;
111
- width: number;
112
- height: number;
113
- color: string;
114
- shape: ParticleShape;
115
- rotation: number;
116
- rotationSpeed: number;
117
- wobblePhase: number;
118
- wobbleSpeed: number;
119
- opacity: number;
120
- ticks: number;
121
- ticksMax: number;
122
- /** Burst origin used for reach-radius containment. */
123
- spawnX: number;
124
- spawnY: number;
125
- /** Max distance from spawn point. 0 = no limit. */
126
- burstRadius: number;
127
- }
128
-
129
- /** Internal simulation parameters used during integration. */
130
- export interface PhysicsConfig {
131
- readonly gravity: number;
132
- readonly drag: number;
133
- readonly rotationDrag: number;
134
- readonly wobbleDrag: number;
135
- readonly minFlatness: number;
136
- readonly skewFactor: number;
137
- readonly decay: number;
138
- }
139
-
140
- /** Host size in dp. */
141
- export interface Viewport {
142
- readonly width: number;
143
- readonly height: number;
144
- }
145
-
146
- /** Fully validated look, ready for spawn and render. */
147
- export interface ResolvedAppearance {
148
- readonly appearance: ParticleAppearance;
149
- readonly coinType: CoinType;
150
- readonly coinSource?: CoinSource;
151
- readonly renderCoin?: RenderCoinFn;
152
- readonly colors: readonly string[];
153
- readonly shapes: readonly ParticleShape[];
154
- readonly scalar: number;
155
- readonly disableForReducedMotion: boolean;
156
- }
157
-
158
- /** Fully validated burst options ready for spawn and render. */
159
- export interface ResolvedConfettiOptions extends ResolvedAppearance {
160
- readonly particleCount: number;
161
- readonly origin: ConfettiOrigin;
162
- readonly angle: number;
163
- readonly spread: number;
164
- readonly startVelocity: number;
165
- readonly gravity: number;
166
- readonly drag: number;
167
- readonly decay: number;
168
- readonly duration: ConfettiDuration;
169
- readonly rotationSpeed: number;
170
- readonly wobbleSpeed: number;
171
- readonly ticks: number;
172
- readonly burstRadius: number;
173
- readonly physics: PhysicsConfig;
174
- readonly createParticle?: CreateParticleFn;
175
- }
176
-
177
- /** Fully validated collect options. */
178
- export interface ResolvedCollectOptions extends ResolvedAppearance {
179
- readonly particleCount: number;
180
- readonly origin: ConfettiOrigin;
181
- readonly target: ConfettiOrigin;
182
- readonly flightMs: number;
183
- readonly staggerMs: number;
184
- readonly onArrive?: (index: number, total: number) => void;
185
- }
186
-
187
- /** One piece of a collect flight along a quadratic bezier. */
188
- export interface CollectPiece {
189
- readonly startX: number;
190
- readonly startY: number;
191
- controlX: number;
192
- controlY: number;
193
- endX: number;
194
- endY: number;
195
- /** ms after the flight starts before this piece moves. */
196
- readonly delayMs: number;
197
- readonly flightMs: number;
198
- readonly size: number;
199
- readonly color: string;
200
- readonly shape: ParticleShape;
201
- readonly wobblePhase: number;
202
- readonly wobbleSpeed: number;
203
- }
204
-
205
- /** Where a collect piece is drawn at one moment. */
206
- export interface CollectFrame {
207
- readonly x: number;
208
- readonly y: number;
209
- readonly scale: number;
210
- readonly scaleX: number;
211
- readonly opacity: number;
212
- readonly done: boolean;
213
- }
214
-
215
- /** Handle returned from a burst, with reset and status helpers. */
216
- export interface ConfettiHandle {
217
- readonly reset: () => void;
218
- readonly isActive: () => boolean;
219
- }
220
-
221
- /** Callable confetti API with promise and collect helpers attached. */
222
- export type ConfettiFn = {
223
- (options?: ConfettiOptions): ConfettiHandle;
224
- promise(options?: ConfettiOptions): Promise<void>;
225
- collect(options: CollectOptions): Promise<void>;
226
- };
227
-
228
- /** One step in a timed sequence of bursts. */
229
- export interface ConfettiSequenceStep {
230
- readonly delay: number;
231
- readonly options?: ConfettiOptions;
232
- }
233
-
234
- /** Handle for a running sequence — cancel early or await completion. */
235
- export type ConfettiSequenceHandle = {
236
- readonly cancel: () => void;
237
- readonly promise: Promise<void>;
238
- };
239
-
240
- /** Runs bursts on a schedule. */
241
- export type ConfettiSequenceFn = (
242
- steps: readonly ConfettiSequenceStep[],
243
- ) => ConfettiSequenceHandle;
1
+ import type { ReactElement } from "react";
2
+
3
+ /** Shape drawn for a single confetti piece. */
4
+ export type ParticleShape = "rect" | "circle";
5
+
6
+ /** What each piece looks like: colored confetti or a coin image. */
7
+ export type ParticleAppearance = "confetti" | "coin";
8
+
9
+ /** Built-in coin artwork. Omit to use `h-keystone`, or pass your own image. */
10
+ export type CoinType = "h-keystone";
11
+
12
+ /**
13
+ * One node per coin, called with the piece index when that piece is created.
14
+ * Use this for an SVG the app already renders. The host moves a wrapper view;
15
+ * the node should fill that box (`width` and `height` `"100%"`).
16
+ */
17
+ export type RenderCoinFn = (index: number) => ReactElement;
18
+
19
+ /**
20
+ * Image for a custom coin — anything React Native `<Image source>` accepts
21
+ * (`require("./coin.png")`, `{ uri }`). Kept loose so the core stays RN-free.
22
+ */
23
+ export type CoinSource = number | { readonly uri: string } | object;
24
+
25
+ /** Named burst style with tuned defaults. */
26
+ export type ConfettiPreset = "celebration" | "subtle" | "cannon" | "spark";
27
+
28
+ /** How long a burst stays visible before pieces fade out. */
29
+ export type ConfettiDuration = "short" | "normal" | "long";
30
+
31
+ /** Screen point in window coordinates (dp). */
32
+ export interface ConfettiOrigin {
33
+ readonly x: number;
34
+ readonly y: number;
35
+ }
36
+
37
+ /** Hook to override properties on a piece as it is created. */
38
+ export type CreateParticleFn = (
39
+ index: number,
40
+ defaults: Readonly<Partial<Particle>>,
41
+ ) => Partial<Particle>;
42
+
43
+ /** Look of the pieces, shared by bursts and collect flights. */
44
+ export interface AppearanceOptions {
45
+ /** `"confetti"` (default) or `"coin"`. */
46
+ readonly appearance?: ParticleAppearance;
47
+ /** Built-in coin. Defaults to `h-keystone`. Ignored when `coinSource` or `renderCoin` is set. */
48
+ readonly coinType?: CoinType;
49
+ /** Custom bitmap. Anything `<Image source>` accepts (PNG, WebP). Overrides `coinType`. */
50
+ readonly coinSource?: CoinSource;
51
+ /**
52
+ * Custom node per coin, for an SVG (or any view) the app owns.
53
+ * Overrides `coinSource`. A heavy tree copied once per coin costs more than one shared PNG.
54
+ */
55
+ readonly renderCoin?: RenderCoinFn;
56
+ readonly colors?: readonly string[];
57
+ readonly shapes?: readonly ParticleShape[];
58
+ /** Size multiplier (0.2–3). */
59
+ readonly scalar?: number;
60
+ /** Do nothing when the OS "Reduce Motion" setting is on. Default `true`. */
61
+ readonly disableForReducedMotion?: boolean;
62
+ }
63
+
64
+ /** Options for a physics burst. */
65
+ export interface ConfettiOptions extends AppearanceOptions {
66
+ readonly particleCount?: number;
67
+ /** Defaults to the center of the host. */
68
+ readonly origin?: ConfettiOrigin;
69
+ readonly angle?: number;
70
+ readonly spread?: number;
71
+ readonly startVelocity?: number;
72
+ readonly gravity?: number;
73
+ readonly drag?: number;
74
+ readonly decay?: number;
75
+ readonly duration?: ConfettiDuration;
76
+ readonly rotationSpeed?: number;
77
+ readonly wobbleSpeed?: number;
78
+ readonly ticks?: number;
79
+ readonly preset?: ConfettiPreset;
80
+ /** Max distance from the origin. 0 = no limit. */
81
+ readonly burstRadius?: number;
82
+ readonly createParticle?: CreateParticleFn;
83
+ }
84
+
85
+ /** Options for a collect flight: pieces start big at `origin` and shrink into `target`. */
86
+ export interface CollectOptions extends AppearanceOptions {
87
+ /** Where pieces fly to, e.g. the center of a coins label (see `measureCenter`). */
88
+ readonly target: ConfettiOrigin;
89
+ /** Defaults to the center of the host. */
90
+ readonly origin?: ConfettiOrigin;
91
+ readonly particleCount?: number;
92
+ /** Average flight time per piece in ms. */
93
+ readonly flightMs?: number;
94
+ /** Average delay between pieces in ms. */
95
+ readonly staggerMs?: number;
96
+ /** Called as each piece lands — handy for ticking a counter up. */
97
+ readonly onArrive?: (index: number, total: number) => void;
98
+ /**
99
+ * Read each frame so the flight follows a moving badge (scroll, layout).
100
+ * Return window coordinates. `target` is the starting aim.
101
+ */
102
+ readonly trackTarget?: () => ConfettiOrigin | null;
103
+ }
104
+
105
+ /** A single burst piece and its motion state. */
106
+ export interface Particle {
107
+ x: number;
108
+ y: number;
109
+ vx: number;
110
+ vy: number;
111
+ width: number;
112
+ height: number;
113
+ color: string;
114
+ shape: ParticleShape;
115
+ rotation: number;
116
+ rotationSpeed: number;
117
+ wobblePhase: number;
118
+ wobbleSpeed: number;
119
+ opacity: number;
120
+ ticks: number;
121
+ ticksMax: number;
122
+ /** Burst origin used for reach-radius containment. */
123
+ spawnX: number;
124
+ spawnY: number;
125
+ /** Max distance from spawn point. 0 = no limit. */
126
+ burstRadius: number;
127
+ }
128
+
129
+ /** Internal simulation parameters used during integration. */
130
+ export interface PhysicsConfig {
131
+ readonly gravity: number;
132
+ readonly drag: number;
133
+ readonly rotationDrag: number;
134
+ readonly wobbleDrag: number;
135
+ readonly minFlatness: number;
136
+ readonly skewFactor: number;
137
+ readonly decay: number;
138
+ }
139
+
140
+ /** Host size in dp. */
141
+ export interface Viewport {
142
+ readonly width: number;
143
+ readonly height: number;
144
+ }
145
+
146
+ /** Fully validated look, ready for spawn and render. */
147
+ export interface ResolvedAppearance {
148
+ readonly appearance: ParticleAppearance;
149
+ readonly coinType: CoinType;
150
+ readonly coinSource?: CoinSource;
151
+ readonly renderCoin?: RenderCoinFn;
152
+ readonly colors: readonly string[];
153
+ readonly shapes: readonly ParticleShape[];
154
+ readonly scalar: number;
155
+ readonly disableForReducedMotion: boolean;
156
+ }
157
+
158
+ /** Fully validated burst options ready for spawn and render. */
159
+ export interface ResolvedConfettiOptions extends ResolvedAppearance {
160
+ readonly particleCount: number;
161
+ readonly origin: ConfettiOrigin;
162
+ readonly angle: number;
163
+ readonly spread: number;
164
+ readonly startVelocity: number;
165
+ readonly gravity: number;
166
+ readonly drag: number;
167
+ readonly decay: number;
168
+ readonly duration: ConfettiDuration;
169
+ readonly rotationSpeed: number;
170
+ readonly wobbleSpeed: number;
171
+ readonly ticks: number;
172
+ readonly burstRadius: number;
173
+ readonly physics: PhysicsConfig;
174
+ readonly createParticle?: CreateParticleFn;
175
+ }
176
+
177
+ /** Fully validated collect options. */
178
+ export interface ResolvedCollectOptions extends ResolvedAppearance {
179
+ readonly particleCount: number;
180
+ readonly origin: ConfettiOrigin;
181
+ readonly target: ConfettiOrigin;
182
+ readonly flightMs: number;
183
+ readonly staggerMs: number;
184
+ readonly onArrive?: (index: number, total: number) => void;
185
+ }
186
+
187
+ /** One piece of a collect flight along a quadratic bezier. */
188
+ export interface CollectPiece {
189
+ readonly startX: number;
190
+ readonly startY: number;
191
+ controlX: number;
192
+ controlY: number;
193
+ endX: number;
194
+ endY: number;
195
+ /** ms after the flight starts before this piece moves. */
196
+ readonly delayMs: number;
197
+ readonly flightMs: number;
198
+ readonly size: number;
199
+ readonly color: string;
200
+ readonly shape: ParticleShape;
201
+ readonly wobblePhase: number;
202
+ readonly wobbleSpeed: number;
203
+ }
204
+
205
+ /** Where a collect piece is drawn at one moment. */
206
+ export interface CollectFrame {
207
+ readonly x: number;
208
+ readonly y: number;
209
+ readonly scale: number;
210
+ readonly scaleX: number;
211
+ readonly opacity: number;
212
+ readonly done: boolean;
213
+ }
214
+
215
+ /** Handle returned from a burst, with reset and status helpers. */
216
+ export interface ConfettiHandle {
217
+ readonly reset: () => void;
218
+ readonly isActive: () => boolean;
219
+ }
220
+
221
+ /** Callable confetti API with promise and collect helpers attached. */
222
+ export type ConfettiFn = {
223
+ (options?: ConfettiOptions): ConfettiHandle;
224
+ promise(options?: ConfettiOptions): Promise<void>;
225
+ collect(options: CollectOptions): Promise<void>;
226
+ };
227
+
228
+ /** One step in a timed sequence of bursts. */
229
+ export interface ConfettiSequenceStep {
230
+ readonly delay: number;
231
+ readonly options?: ConfettiOptions;
232
+ }
233
+
234
+ /** Handle for a running sequence — cancel early or await completion. */
235
+ export type ConfettiSequenceHandle = {
236
+ readonly cancel: () => void;
237
+ readonly promise: Promise<void>;
238
+ };
239
+
240
+ /** Runs bursts on a schedule. */
241
+ export type ConfettiSequenceFn = (
242
+ steps: readonly ConfettiSequenceStep[],
243
+ ) => ConfettiSequenceHandle;
package/src/utils/math.ts CHANGED
@@ -1,49 +1,49 @@
1
- import { DEFAULT_FRAME_SECONDS, MAX_FRAME_SECONDS } from "../constants";
2
-
3
- /** Clamps a number to the given range. */
4
- export function clamp(value: number, min: number, max: number): number {
5
- if (value < min) return min;
6
- if (value > max) return max;
7
- return value;
8
- }
9
-
10
- /** Returns true when the value is a finite number. */
11
- export function isFiniteNumber(value: unknown): value is number {
12
- return typeof value === "number" && Number.isFinite(value);
13
- }
14
-
15
- /** Converts degrees to radians. */
16
- export function degreesToRadians(degrees: number): number {
17
- return (degrees * Math.PI) / 180;
18
- }
19
-
20
- /** Computes frame delta time in seconds, capped for stability. */
21
- export function computeDeltaSeconds(lastTimestamp: number, timestamp: number): number {
22
- if (lastTimestamp <= 0) return DEFAULT_FRAME_SECONDS;
23
- return clamp((timestamp - lastTimestamp) / 1000, 0.001, MAX_FRAME_SECONDS);
24
- }
25
-
26
- /** Returns a random float between min and max, inclusive of min. */
27
- export function randomInRange(min: number, max: number): number {
28
- return min + Math.random() * (max - min);
29
- }
30
-
31
- /** Picks a random item from a non-empty array. */
32
- export function pickRandom<T>(items: readonly T[]): T {
33
- return items[Math.floor(Math.random() * items.length)] as T;
34
- }
35
-
36
- /** Linear interpolation from `a` to `b`. */
37
- export function lerp(a: number, b: number, t: number): number {
38
- return a + (b - a) * t;
39
- }
40
-
41
- /** Ease-in-out cubic on 0–1. */
42
- export function easeInOutCubic(t: number): number {
43
- return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
44
- }
45
-
46
- /** Ease-in quad on 0–1. */
47
- export function easeInQuad(t: number): number {
48
- return t * t;
49
- }
1
+ import { DEFAULT_FRAME_SECONDS, MAX_FRAME_SECONDS } from "../constants";
2
+
3
+ /** Clamps a number to the given range. */
4
+ export function clamp(value: number, min: number, max: number): number {
5
+ if (value < min) return min;
6
+ if (value > max) return max;
7
+ return value;
8
+ }
9
+
10
+ /** Returns true when the value is a finite number. */
11
+ export function isFiniteNumber(value: unknown): value is number {
12
+ return typeof value === "number" && Number.isFinite(value);
13
+ }
14
+
15
+ /** Converts degrees to radians. */
16
+ export function degreesToRadians(degrees: number): number {
17
+ return (degrees * Math.PI) / 180;
18
+ }
19
+
20
+ /** Computes frame delta time in seconds, capped for stability. */
21
+ export function computeDeltaSeconds(lastTimestamp: number, timestamp: number): number {
22
+ if (lastTimestamp <= 0) return DEFAULT_FRAME_SECONDS;
23
+ return clamp((timestamp - lastTimestamp) / 1000, 0.001, MAX_FRAME_SECONDS);
24
+ }
25
+
26
+ /** Returns a random float between min and max, inclusive of min. */
27
+ export function randomInRange(min: number, max: number): number {
28
+ return min + Math.random() * (max - min);
29
+ }
30
+
31
+ /** Picks a random item from a non-empty array. */
32
+ export function pickRandom<T>(items: readonly T[]): T {
33
+ return items[Math.floor(Math.random() * items.length)] as T;
34
+ }
35
+
36
+ /** Linear interpolation from `a` to `b`. */
37
+ export function lerp(a: number, b: number, t: number): number {
38
+ return a + (b - a) * t;
39
+ }
40
+
41
+ /** Ease-in-out cubic on 0–1. */
42
+ export function easeInOutCubic(t: number): number {
43
+ return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
44
+ }
45
+
46
+ /** Ease-in quad on 0–1. */
47
+ export function easeInQuad(t: number): number {
48
+ return t * t;
49
+ }