toosoon-utils 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.
@@ -0,0 +1,172 @@
1
+ // *********************
2
+ // Maths
3
+ // *********************
4
+ export const EPSILON = 1e-10;
5
+ export const PI = Math.PI;
6
+ export const TWO_PI = Math.PI * 2;
7
+ export const HALF_PI = Math.PI / 2;
8
+ export const QUARTER_PI = Math.PI / 4;
9
+
10
+ // *********************
11
+ // Color
12
+ // *********************
13
+
14
+ // X11 colors
15
+ // -> https://www.w3.org/TR/css-color-3/#svg-color
16
+ export const W3CX11 = {
17
+ aliceblue: '#f0f8ff',
18
+ antiquewhite: '#faebd7',
19
+ aqua: '#00ffff',
20
+ aquamarine: '#7fffd4',
21
+ azure: '#f0ffff',
22
+ beige: '#f5f5dc',
23
+ bisque: '#ffe4c4',
24
+ black: '#000000',
25
+ blanchedalmond: '#ffebcd',
26
+ blue: '#0000ff',
27
+ blueviolet: '#8a2be2',
28
+ brown: '#a52a2a',
29
+ burlywood: '#deb887',
30
+ cadetblue: '#5f9ea0',
31
+ chartreuse: '#7fff00',
32
+ chocolate: '#d2691e',
33
+ coral: '#ff7f50',
34
+ cornflower: '#6495ed',
35
+ cornflowerblue: '#6495ed',
36
+ cornsilk: '#fff8dc',
37
+ crimson: '#dc143c',
38
+ cyan: '#00ffff',
39
+ darkblue: '#00008b',
40
+ darkcyan: '#008b8b',
41
+ darkgoldenrod: '#b8860b',
42
+ darkgray: '#a9a9a9',
43
+ darkgreen: '#006400',
44
+ darkgrey: '#a9a9a9',
45
+ darkkhaki: '#bdb76b',
46
+ darkmagenta: '#8b008b',
47
+ darkolivegreen: '#556b2f',
48
+ darkorange: '#ff8c00',
49
+ darkorchid: '#9932cc',
50
+ darkred: '#8b0000',
51
+ darksalmon: '#e9967a',
52
+ darkseagreen: '#8fbc8f',
53
+ darkslateblue: '#483d8b',
54
+ darkslategray: '#2f4f4f',
55
+ darkslategrey: '#2f4f4f',
56
+ darkturquoise: '#00ced1',
57
+ darkviolet: '#9400d3',
58
+ deeppink: '#ff1493',
59
+ deepskyblue: '#00bfff',
60
+ dimgray: '#696969',
61
+ dimgrey: '#696969',
62
+ dodgerblue: '#1e90ff',
63
+ firebrick: '#b22222',
64
+ floralwhite: '#fffaf0',
65
+ forestgreen: '#228b22',
66
+ fuchsia: '#ff00ff',
67
+ gainsboro: '#dcdcdc',
68
+ ghostwhite: '#f8f8ff',
69
+ gold: '#ffd700',
70
+ goldenrod: '#daa520',
71
+ gray: '#808080',
72
+ green: '#008000',
73
+ greenyellow: '#adff2f',
74
+ grey: '#808080',
75
+ honeydew: '#f0fff0',
76
+ hotpink: '#ff69b4',
77
+ indianred: '#cd5c5c',
78
+ indigo: '#4b0082',
79
+ ivory: '#fffff0',
80
+ khaki: '#f0e68c',
81
+ laserlemon: '#ffff54',
82
+ lavender: '#e6e6fa',
83
+ lavenderblush: '#fff0f5',
84
+ lawngreen: '#7cfc00',
85
+ lemonchiffon: '#fffacd',
86
+ lightblue: '#add8e6',
87
+ lightcoral: '#f08080',
88
+ lightcyan: '#e0ffff',
89
+ lightgoldenrod: '#fafad2',
90
+ lightgoldenrodyellow: '#fafad2',
91
+ lightgray: '#d3d3d3',
92
+ lightgreen: '#90ee90',
93
+ lightgrey: '#d3d3d3',
94
+ lightpink: '#ffb6c1',
95
+ lightsalmon: '#ffa07a',
96
+ lightseagreen: '#20b2aa',
97
+ lightskyblue: '#87cefa',
98
+ lightslategray: '#778899',
99
+ lightslategrey: '#778899',
100
+ lightsteelblue: '#b0c4de',
101
+ lightyellow: '#ffffe0',
102
+ lime: '#00ff00',
103
+ limegreen: '#32cd32',
104
+ linen: '#faf0e6',
105
+ magenta: '#ff00ff',
106
+ maroon: '#800000',
107
+ maroon2: '#7f0000',
108
+ maroon3: '#b03060',
109
+ mediumaquamarine: '#66cdaa',
110
+ mediumblue: '#0000cd',
111
+ mediumorchid: '#ba55d3',
112
+ mediumpurple: '#9370db',
113
+ mediumseagreen: '#3cb371',
114
+ mediumslateblue: '#7b68ee',
115
+ mediumspringgreen: '#00fa9a',
116
+ mediumturquoise: '#48d1cc',
117
+ mediumvioletred: '#c71585',
118
+ midnightblue: '#191970',
119
+ mintcream: '#f5fffa',
120
+ mistyrose: '#ffe4e1',
121
+ moccasin: '#ffe4b5',
122
+ navajowhite: '#ffdead',
123
+ navy: '#000080',
124
+ oldlace: '#fdf5e6',
125
+ olive: '#808000',
126
+ olivedrab: '#6b8e23',
127
+ orange: '#ffa500',
128
+ orangered: '#ff4500',
129
+ orchid: '#da70d6',
130
+ palegoldenrod: '#eee8aa',
131
+ palegreen: '#98fb98',
132
+ paleturquoise: '#afeeee',
133
+ palevioletred: '#db7093',
134
+ papayawhip: '#ffefd5',
135
+ peachpuff: '#ffdab9',
136
+ peru: '#cd853f',
137
+ pink: '#ffc0cb',
138
+ plum: '#dda0dd',
139
+ powderblue: '#b0e0e6',
140
+ purple: '#800080',
141
+ purple2: '#7f007f',
142
+ purple3: '#a020f0',
143
+ rebeccapurple: '#663399',
144
+ red: '#ff0000',
145
+ rosybrown: '#bc8f8f',
146
+ royalblue: '#4169e1',
147
+ saddlebrown: '#8b4513',
148
+ salmon: '#fa8072',
149
+ sandybrown: '#f4a460',
150
+ seagreen: '#2e8b57',
151
+ seashell: '#fff5ee',
152
+ sienna: '#a0522d',
153
+ silver: '#c0c0c0',
154
+ skyblue: '#87ceeb',
155
+ slateblue: '#6a5acd',
156
+ slategray: '#708090',
157
+ slategrey: '#708090',
158
+ snow: '#fffafa',
159
+ springgreen: '#00ff7f',
160
+ steelblue: '#4682b4',
161
+ tan: '#d2b48c',
162
+ teal: '#008080',
163
+ thistle: '#d8bfd8',
164
+ tomato: '#ff6347',
165
+ turquoise: '#40e0d0',
166
+ violet: '#ee82ee',
167
+ wheat: '#f5deb3',
168
+ white: '#ffffff',
169
+ whitesmoke: '#f5f5f5',
170
+ yellow: '#ffff00',
171
+ yellowgreen: '#9acd32'
172
+ };
package/src/dom.ts ADDED
@@ -0,0 +1,50 @@
1
+ const DOCUMENT_NODE_TYPE = 9;
2
+
3
+ /**
4
+ * Find the closest parent that matches a selector
5
+ *
6
+ * @param {Element} element Target element
7
+ * @param {(Element|string)} selector Selector or parent to match
8
+ * @returns {Element|null}
9
+ */
10
+ export function closest(element: Element, selector: Element | string): Element | null {
11
+ let current: Element | null = element;
12
+ while (current && current.nodeType !== DOCUMENT_NODE_TYPE) {
13
+ if ((typeof selector === 'string' && current.matches(selector)) || current === selector) {
14
+ return current;
15
+ }
16
+ current = element.parentNode as Element | null;
17
+ }
18
+ return current;
19
+ }
20
+
21
+ /**
22
+ * Create a canvas and 2d context
23
+ *
24
+ * @param {Number} width Width of the canvas
25
+ * @param {Number} height Height of the canvas
26
+ * @returns {{ canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D }}
27
+ */
28
+ export function createCanvas(
29
+ width: number,
30
+ height: number
31
+ ): { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D } {
32
+ const canvas = document.createElement('canvas');
33
+ canvas.width = width;
34
+ canvas.height = height;
35
+ const ctx = canvas.getContext('2d') ?? new CanvasRenderingContext2D();
36
+ return { canvas, ctx };
37
+ }
38
+
39
+ /**
40
+ * Inject CSS style in `document.head`
41
+ *
42
+ * @param {string} cssContent CSS style to inject
43
+ */
44
+ export function injectStyles(cssContent: string) {
45
+ const $style = document.createElement('style');
46
+ $style.innerHTML = cssContent;
47
+ const $before = document.querySelector('head link[rel=stylesheet], head style');
48
+ if ($before) document.head.insertBefore($style, $before);
49
+ else document.head.appendChild($style);
50
+ }
package/src/files.ts ADDED
@@ -0,0 +1,42 @@
1
+ type InputChangeEvent = Event & { target: (EventTarget & { files?: FileList }) | null };
2
+
3
+ /**
4
+ * Download a Blob object into user files
5
+ *
6
+ * @param {Blob} blob Blob object to download
7
+ * @param {string} filename Downloaded file name
8
+ */
9
+ export function download(blob: Blob, filename: string): void {
10
+ const link = document.createElement('a');
11
+ link.setAttribute('href', URL.createObjectURL(blob));
12
+ link.setAttribute('download', filename);
13
+
14
+ document.body.appendChild(link);
15
+ link.click();
16
+ document.body.removeChild(link);
17
+ }
18
+
19
+ /**
20
+ * Upload a file from user files
21
+ *
22
+ * @param {Function} onLoad Callback called once the file is loaded
23
+ * @param {string} [accept=''] Types the file input should accept
24
+ */
25
+ export function upload(onLoad: (dataUrl: string) => void, accept: string = ''): void {
26
+ const input = document.createElement('input');
27
+ input.setAttribute('type', 'file');
28
+ input.setAttribute('accept', accept);
29
+
30
+ input.addEventListener('change', (event: InputChangeEvent) => {
31
+ const file = event.target?.files?.[0];
32
+ if (file) {
33
+ const fileReader = new FileReader();
34
+ fileReader.addEventListener('load', () => onLoad(URL.createObjectURL(file)));
35
+ fileReader.readAsDataURL(file);
36
+ }
37
+ });
38
+
39
+ document.body.appendChild(input);
40
+ input.click();
41
+ document.body.removeChild(input);
42
+ }
@@ -0,0 +1,31 @@
1
+ import { Deferred } from './types';
2
+
3
+ /**
4
+ * No-op function
5
+ */
6
+ export const noop: () => void = () => {};
7
+
8
+ /**
9
+ * Promise wrapped setTimeout
10
+ *
11
+ * @param {number} timeout Time in ms
12
+ * @returns {Promise}
13
+ */
14
+ export function wait(timeout: number): Promise<void> {
15
+ return new Promise((resolve) => setTimeout(resolve, timeout));
16
+ }
17
+
18
+ /**
19
+ * Deferred promise implementation
20
+ *
21
+ * @returns {Deferred}
22
+ */
23
+ export function defer<T = void>(): Deferred<T> {
24
+ let resolve!: (value: T | PromiseLike<T>) => void;
25
+ let reject!: (reason?: unknown) => void;
26
+ const promise = new Promise<T>((_resolve, _reject) => {
27
+ resolve = _resolve;
28
+ reject = _reject;
29
+ });
30
+ return { promise, resolve, reject };
31
+ }
@@ -0,0 +1,160 @@
1
+ import { PI } from './constants';
2
+ import { Vector3 } from './types';
3
+
4
+ /**
5
+ * Convert a radians value into degrees
6
+ *
7
+ * @param {number} radians Angle in radians
8
+ * @returns {number} Angle in degrees
9
+ */
10
+ export function toDegrees(radians: number): number {
11
+ return (radians * 180) / PI;
12
+ }
13
+
14
+ /**
15
+ * Convert a degrees value into radians
16
+ *
17
+ * @param {number} degrees Angle in degrees
18
+ * @returns {number} Angle in radians
19
+ */
20
+ export function toRadians(degrees: number): number {
21
+ return (degrees * PI) / 180;
22
+ }
23
+
24
+ /**
25
+ * Calculate the angle from a point to another
26
+ *
27
+ * @param {number} x1 X value of the first point
28
+ * @param {number} y1 Y value of the first point
29
+ * @param {number} x2 X value of the second point
30
+ * @param {number} y2 Y value of the second point
31
+ * @returns {number} Angle
32
+ */
33
+ export function angle(x1: number, y1: number, x2: number, y2: number): number {
34
+ return Math.atan2(y2 - y1, x2 - x1);
35
+ }
36
+
37
+ /**
38
+ * Find the closest angle betwween to angles
39
+ *
40
+ * @param {number} source Source angle in radians
41
+ * @param {number} target Target angle in radians
42
+ * @returns {number} Closest angle
43
+ */
44
+ export function closestAngle(source: number, target: number): number {
45
+ const delta = target - source;
46
+ return delta > PI ? target - 2 * PI : target < -PI ? delta + 2 * PI : target;
47
+ }
48
+
49
+ /**
50
+ * Calculate the distance between two points
51
+ *
52
+ * @param {number} x1 X coord of the first point
53
+ * @param {number} y1 Y coord of the first point
54
+ * @param {number} x2 X coord of the second point
55
+ * @param {number} y2 Y coord of the second point
56
+ * @returns {number} Computed distance
57
+ */
58
+ export function distance(x1: number, y1: number, x2: number, y2: number): number {
59
+ const dx = x1 - x2;
60
+ const dy = y1 - y2;
61
+ return Math.sqrt(dx * dx + dy * dy);
62
+ }
63
+
64
+ /**
65
+ * Calculate the length of the diagonal of a rectangle
66
+ *
67
+ * @param {number} width Width of the rectangle
68
+ * @param {number} height Height of the rectangle
69
+ * @returns {number} Diagonal length
70
+ */
71
+ export function diagonal(width: number, height: number): number {
72
+ return Math.sqrt(width * width + height * height);
73
+ }
74
+
75
+ /**
76
+ * Convert radians to a 3D point on the surface of a unit sphere
77
+ *
78
+ * @param {number} radius Radius of the sphere
79
+ * @param {number} phi Polar angle from the y (up) axis : 0 to PI
80
+ * @param {number} theta Equator angle around the y (up) axis : 0 to 2 PI
81
+ * @param {Vector3} target Target vector
82
+ * @returns {Vector3}
83
+ */
84
+ export function radToSphere(
85
+ radius: number,
86
+ phi: number,
87
+ theta: number,
88
+ target: Vector3 = { x: 0, y: 0, z: 0 }
89
+ ): Vector3 {
90
+ target.x = radius * Math.sin(phi) * Math.sin(theta);
91
+ target.y = radius * Math.cos(phi);
92
+ target.z = radius * Math.sin(phi) * Math.cos(theta);
93
+ return target;
94
+ }
95
+
96
+ // *********************
97
+ // Fit
98
+ // *********************
99
+ interface FitInput {
100
+ width: number;
101
+ height: number;
102
+ }
103
+
104
+ interface FitOutput {
105
+ left: number;
106
+ top: number;
107
+ width: number;
108
+ height: number;
109
+ scale: number;
110
+ }
111
+
112
+ /**
113
+ * Make a target fit a container
114
+ *
115
+ * @param {FitInput} target Dimension of the target
116
+ * @param {FitInput} container Dimension of the container
117
+ * @param {string} mode Can be 'contain' | 'cover'
118
+ * @returns {FitOutput}
119
+ */
120
+ function fit(target: FitInput, container: FitInput, mode: 'contain' | 'cover'): FitOutput {
121
+ const ratioWidth = container.width / target.width;
122
+ const ratioHeight = container.height / target.height;
123
+
124
+ let scale: number;
125
+ if (mode === 'contain') {
126
+ scale = ratioWidth < ratioHeight ? ratioWidth : ratioHeight;
127
+ } else {
128
+ scale = ratioWidth > ratioHeight ? ratioWidth : ratioHeight;
129
+ }
130
+
131
+ return {
132
+ left: (container.width - target.width * scale) >> 1,
133
+ top: (container.height - target.height * scale) >> 1,
134
+ width: target.width * scale,
135
+ height: target.height * scale,
136
+ scale
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Make a target fit a container (cover mode)
142
+ *
143
+ * @param {FitInput} target Dimension of the target
144
+ * @param {FitInput} container Dimension of the container
145
+ * @returns {FitOutput}
146
+ */
147
+ export function cover(target: FitInput, container: FitInput): FitOutput {
148
+ return fit(target, container, 'cover');
149
+ }
150
+
151
+ /**
152
+ * Make a target fit a container (contain mode)
153
+ *
154
+ * @param {FitInput} target Dimension of the target
155
+ * @param {FitInput} container Dimension of the container
156
+ * @returns {FitOutput}
157
+ */
158
+ export function contain(target: FitInput, container: FitInput): FitOutput {
159
+ return fit(target, container, 'contain');
160
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './colors';
2
+ export * from './dom';
3
+ export * from './files';
4
+ export * from './functions';
5
+ export * from './geometry';
6
+ export * from './maths';
7
+ export * from './now';
8
+ export * from './random';
9
+ export * from './strings';
10
+
11
+ export * from './constants';
12
+ export * from './types';
package/src/maths.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Check if a number is even
3
+ *
4
+ * @param {number} value Value to check
5
+ * @returns {boolean} True if the given number is even, false otherwise
6
+ */
7
+ export function isEven(value: number): boolean {
8
+ return !(value & 1);
9
+ }
10
+
11
+ /**
12
+ * Check if a number is odd
13
+ *
14
+ * @param {number} value Value to check
15
+ * @returns {boolean} True if the given number is odd, false otherwise
16
+ */
17
+ export function isOdd(value: number): boolean {
18
+ return !!(value & 1);
19
+ }
20
+
21
+ /**
22
+ * Check if a number is a power of 2
23
+ *
24
+ * @param {number} value Value to check
25
+ * @returns {boolean} True if the given number is a power of 2, false otherwise
26
+ */
27
+ export function isPowerOf2(value: number): boolean {
28
+ return (value & (value - 1)) === 0;
29
+ }
30
+
31
+ /**
32
+ * Find smallest/closest power of 2 that fits a number
33
+ *
34
+ * @param {number} value Incoming value
35
+ * @param {string} [mode='ceil'] Can be 'floor' | 'ceil' | 'round'
36
+ * @returns {number} Power of 2
37
+ */
38
+ export function toPowerOf2(value: number, mode: 'floor' | 'ceil' | 'round' = 'ceil'): number {
39
+ return Math.pow(2, Math[mode](Math.log(value) / Math.log(2)));
40
+ }
41
+
42
+ /**
43
+ * Return the sign (positive or negative) of a number
44
+ *
45
+ * @param {number} number Value to check
46
+ * @returns {number} 1 if the given number is positive, -1 if it is negative, otherwise 0
47
+ */
48
+ export function sign(number: number): number {
49
+ if (number > 0) return 1;
50
+ else if (number < 0) return -1;
51
+ return 0;
52
+ }
53
+
54
+ /**
55
+ * Clamp a value between two bounds
56
+ *
57
+ * @param {number} value Value to clamp
58
+ * @param {number} [min=0] Minimum boundary
59
+ * @param {number} [max=1] Maximum boundary
60
+ * @returns {number} Clamped value
61
+ */
62
+ export function clamp(value: number, min: number = 0, max: number = 1): number {
63
+ return Math.min(max, Math.max(min, value));
64
+ }
65
+
66
+ /**
67
+ * Linear interpolation between two values (lerping)
68
+ *
69
+ * @param {number} value Normalized value to interpolate
70
+ * @param {number} min Minimum value
71
+ * @param {number} max Maximum value
72
+ * @returns {number} Lerped value
73
+ */
74
+ export function lerp(value: number, min: number, max: number): number {
75
+ return min + (max - min) * value;
76
+ }
77
+
78
+ /**
79
+ * Triangular interpolation between two values
80
+ *
81
+ * @param {number} value Normalized value to interpolate
82
+ * @param {number} min Minimum value
83
+ * @param {number} max Maximum value
84
+ * @param {number} target Triangle target value
85
+ * @returns {number} Interpolated value
86
+ */
87
+ export function triLerp(value: number, min: number, max: number, target: number): number {
88
+ const x = Math.pow(1 + Math.abs(target - max) / Math.abs(target - min), -1);
89
+ return value <= x ? min - (min - target) * (value / x) : target - (target - max) * ((value - x) / (1 - x));
90
+ }
91
+
92
+ /**
93
+ * Exponential interpolation between two values
94
+ *
95
+ * @param {number} value Normalized value to interpolate
96
+ * @param {number} min Minimum value
97
+ * @param {number} max Maximum value
98
+ * @returns {number} Interpolated value
99
+ */
100
+ export function expLerp(value: number, min: number, max: number): number {
101
+ return Math.pow(min, 1 - value) * Math.pow(max, value);
102
+ }
103
+
104
+ /**
105
+ * Normalize a value between two bounds
106
+ *
107
+ * @param {number} value Value to normalize
108
+ * @param {number} min Minimum boundary
109
+ * @param {number} max Maximum boundary
110
+ * @returns {number} Normalized value
111
+ */
112
+ export function normalize(value: number, min: number, max: number): number {
113
+ return (value - min) / (max - min);
114
+ }
115
+
116
+ /**
117
+ * Re-map a number from one range to another
118
+ *
119
+ * @param {number} value Value to re-map
120
+ * @param {number} currentMin Lower bound of the value's current range
121
+ * @param {number} currentMax Upper bound of the value's current range
122
+ * @param {number} targetMin Lower bound of the value's target range
123
+ * @param {number} targetMax Upper bound of the value's target range
124
+ * @returns {number} Re-mapped value
125
+ */
126
+ export function map(
127
+ value: number,
128
+ currentMin: number,
129
+ currentMax: number,
130
+ targetMin: number,
131
+ targetMax: number
132
+ ): number {
133
+ return ((value - currentMin) / (currentMax - currentMin)) * (targetMax - targetMin) + targetMin;
134
+ }
135
+
136
+ /**
137
+ * Round a number up to a nearest multiple
138
+ *
139
+ * @param {number} value Value to round
140
+ * @param {number} [multiple=1] Multiple
141
+ * @returns {number} Closest multiple
142
+ */
143
+ export function roundTo(value: number, multiple: number = 1): number {
144
+ if (multiple === 0) return value;
145
+ return Math.round(value / multiple) * multiple;
146
+ }
147
+
148
+ /**
149
+ * Modulo absolute a value based on a length
150
+ *
151
+ * @param {number} value Value to modulate
152
+ * @param {number} length Total length
153
+ * @returns {number} Modulated value
154
+ */
155
+ export function modAbs(value: number, length: number): number {
156
+ if (value < 0) {
157
+ return length + (value % length);
158
+ }
159
+ return value % length;
160
+ }
161
+
162
+ /**
163
+ * Move back and forth a value between 0 and length, so that it is never larger than length and never smaller than 0
164
+ *
165
+ * @param {number} value Value to modulate
166
+ * @param {number} length Total length
167
+ * @returns {number} PingPonged value
168
+ */
169
+ export function pingPong(value: number, length: number): number {
170
+ value = modAbs(value, length * 2);
171
+ return length - Math.abs(value - length);
172
+ }
173
+
174
+ /**
175
+ * Smooth a value using cubic Hermite interpolation
176
+ *
177
+ * @param {number} value Value to smooth
178
+ * @param {number} [min=0] Minimum boundary
179
+ * @param {number} [max=1] Maximum boundary
180
+ * @returns {number} Normalized smoothed value
181
+ */
182
+ export function smoothstep(value: number, min: number = 0, max: number = 1): number {
183
+ const x = clamp(normalize(value, min, max));
184
+ return x * x * (3 - 2 * x);
185
+ }
186
+
187
+ /**
188
+ * Re-map the [0, 1] interval into [0, 1] parabola, such that corners are remaped to 0 and the center to 1
189
+ * -> parabola(0) = parabola(1) = 0, and parabola(0.5) = 1
190
+ *
191
+ * @param {number} x Normalized coordinate on X axis
192
+ * @param {number} [power=1] Parabola power
193
+ * @returns {number} Normalized re-mapped value
194
+ */
195
+ export function parabola(x: number, power: number = 1): number {
196
+ return Math.pow(4 * x * (1 - x), power);
197
+ }
198
+
199
+ /**
200
+ * Return the sum of numbers
201
+ *
202
+ * @param {number[]} array Array of number
203
+ * @returns {number} Total sum
204
+ */
205
+ export function sum(array: number[]): number {
206
+ return array.reduce((previous, current) => previous + current);
207
+ }
208
+
209
+ /**
210
+ * Return the average of numbers
211
+ *
212
+ * @param {number[]} array Array of number
213
+ * @returns {number} Total average
214
+ */
215
+ export function average(array: number[]): number {
216
+ return sum(array) / array.length;
217
+ }
218
+
219
+ /**
220
+ * Smoothly interpolate a number toward another
221
+ *
222
+ * @param {number} value Value to interpolate
223
+ * @param {number} target Destination of the interpolation
224
+ * @param {number} damping A higher value will make the movement more sudden, and a lower value will make the movement more gradual
225
+ * @param {number} delta Delta time (in seconds)
226
+ * @returns {number} Interpolated number
227
+ */
228
+ export function damp(value: number, target: number, damping: number, delta: number): number {
229
+ return lerp(1 - Math.exp(-damping * delta), value, target);
230
+ }