littlejsengine 1.19.3 → 1.20.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/FAQ.md +841 -820
- package/README.md +29 -25
- package/dist/littlejs.d.ts +2455 -935
- package/dist/littlejs.esm.js +30344 -21110
- package/dist/littlejs.esm.min.js +4 -3
- package/dist/littlejs.js +30259 -21052
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +26567 -20455
- package/package.json +10 -5
- package/plugins/audioEffects.js +28 -24
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -332
- package/plugins/math3d.js +58 -17
- package/plugins/medalSystem.js +326 -280
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +15 -0
- package/plugins/postProcess.js +56 -13
- package/plugins/render3d.js +1643 -1577
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +496 -458
- package/plugins/threejs.js +183 -163
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +263 -145
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +159 -159
- package/src/engine.js +191 -75
- package/src/engineAudio.js +229 -125
- package/src/engineBuild.mjs +289 -256
- package/src/engineDebug.js +912 -742
- package/src/engineDraw.js +295 -193
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +12 -0
- package/src/engineInput.js +1462 -1293
- package/src/engineMath.js +1274 -1252
- package/src/engineObject.js +194 -80
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +77 -29
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +19 -13
- package/src/engineWebGL.js +239 -82
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/src/engineMath.js
CHANGED
|
@@ -1,1253 +1,1275 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS Math Classes and Functions
|
|
3
|
-
* - Comprehensive math utilities for game development
|
|
4
|
-
* - Vector2 class for 2D positions, directions, and math operations
|
|
5
|
-
* - Color class for RGBA colors with interpolation and manipulation
|
|
6
|
-
* - RandomGenerator for seeded pseudo-random number generation
|
|
7
|
-
* - Math shortcuts (PI, abs, floor, ceil, min, max, sin, cos, etc.)
|
|
8
|
-
* - Interpolation functions (lerp, smoothStep, percent)
|
|
9
|
-
* - Clamping, wrapping, and modulo operations
|
|
10
|
-
* - Angle utilities with wrap-around support
|
|
11
|
-
* - Collision detection (overlapping, intersection, line tests)
|
|
12
|
-
* - Random number generation and seeding
|
|
13
|
-
* - Type checking utilities
|
|
14
|
-
* @namespace Math
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
/** The value of PI
|
|
20
|
-
* @type {number}
|
|
21
|
-
* @default Math.PI
|
|
22
|
-
* @memberof Math */
|
|
23
|
-
const PI = Math.PI;
|
|
24
|
-
|
|
25
|
-
/** Returns absolute value of value passed in
|
|
26
|
-
* @param {number} x
|
|
27
|
-
* @return {number}
|
|
28
|
-
* @memberof Math */
|
|
29
|
-
const abs = Math.abs;
|
|
30
|
-
|
|
31
|
-
/** Returns floored value of value passed in
|
|
32
|
-
* @param {number} x
|
|
33
|
-
* @return {number}
|
|
34
|
-
* @memberof Math */
|
|
35
|
-
const floor = Math.floor;
|
|
36
|
-
|
|
37
|
-
/** Returns ceiled value of value passed in
|
|
38
|
-
* @param {number} x
|
|
39
|
-
* @return {number}
|
|
40
|
-
* @memberof Math */
|
|
41
|
-
const ceil = Math.ceil;
|
|
42
|
-
|
|
43
|
-
/** Returns rounded value passed in
|
|
44
|
-
* @param {number} x
|
|
45
|
-
* @return {number}
|
|
46
|
-
* @memberof Math */
|
|
47
|
-
const round = Math.round;
|
|
48
|
-
|
|
49
|
-
/** Returns lowest value passed in
|
|
50
|
-
* @param {...number} values
|
|
51
|
-
* @return {number}
|
|
52
|
-
* @memberof Math */
|
|
53
|
-
const min = Math.min;
|
|
54
|
-
|
|
55
|
-
/** Returns highest value passed in
|
|
56
|
-
* @param {...number} values
|
|
57
|
-
* @return {number}
|
|
58
|
-
* @memberof Math */
|
|
59
|
-
const max = Math.max;
|
|
60
|
-
|
|
61
|
-
/** Returns the sign of value passed in
|
|
62
|
-
* @param {number} x
|
|
63
|
-
* @return {number}
|
|
64
|
-
* @memberof Math */
|
|
65
|
-
const sign = (x) => Math.sign(x);
|
|
66
|
-
|
|
67
|
-
/** Returns hypotenuse of values passed in
|
|
68
|
-
* @param {...number} values
|
|
69
|
-
* @return {number}
|
|
70
|
-
* @memberof Math */
|
|
71
|
-
const hypot = (...values) => Math.hypot(...values);
|
|
72
|
-
|
|
73
|
-
/** Returns log2 of value passed in
|
|
74
|
-
* @param {number} x
|
|
75
|
-
* @return {number}
|
|
76
|
-
* @memberof Math */
|
|
77
|
-
const log2 = (x) => Math.log2(x);
|
|
78
|
-
|
|
79
|
-
/** Returns sin of value passed in
|
|
80
|
-
* @param {number} x
|
|
81
|
-
* @return {number}
|
|
82
|
-
* @memberof Math */
|
|
83
|
-
const sin = Math.sin;
|
|
84
|
-
|
|
85
|
-
/** Returns cos of value passed in
|
|
86
|
-
* @param {number} x
|
|
87
|
-
* @return {number}
|
|
88
|
-
* @memberof Math */
|
|
89
|
-
const cos = Math.cos;
|
|
90
|
-
|
|
91
|
-
/** Returns tan of value passed in
|
|
92
|
-
* @param {number} x
|
|
93
|
-
* @return {number}
|
|
94
|
-
* @memberof Math */
|
|
95
|
-
const tan = Math.tan;
|
|
96
|
-
|
|
97
|
-
/** Returns atan2 of values passed in
|
|
98
|
-
* @param {number} y
|
|
99
|
-
* @param {number} x
|
|
100
|
-
* @return {number}
|
|
101
|
-
* @memberof Math */
|
|
102
|
-
const atan2 = Math.atan2;
|
|
103
|
-
|
|
104
|
-
/** Returns first
|
|
105
|
-
* @param {number} dividend
|
|
106
|
-
* @param {number} [divisor]
|
|
107
|
-
* @return {number}
|
|
108
|
-
* @memberof Math */
|
|
109
|
-
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
110
|
-
|
|
111
|
-
/** Clamps the value between max and min
|
|
112
|
-
* @param {number} value
|
|
113
|
-
* @param {number} [min]
|
|
114
|
-
* @param {number} [max]
|
|
115
|
-
* @return {number}
|
|
116
|
-
* @memberof Math */
|
|
117
|
-
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
118
|
-
|
|
119
|
-
/** Returns what percentage the value is between valueA and valueB
|
|
120
|
-
* @param {number} value
|
|
121
|
-
* @param {number} valueA
|
|
122
|
-
* @param {number} valueB
|
|
123
|
-
* @return {number}
|
|
124
|
-
* @memberof Math */
|
|
125
|
-
function percent(value, valueA, valueB)
|
|
126
|
-
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
127
|
-
|
|
128
|
-
/** Linearly interpolates between values passed in using percent
|
|
129
|
-
* @param {number} valueA
|
|
130
|
-
* @param {number} valueB
|
|
131
|
-
* @param {number} percent
|
|
132
|
-
* @return {number}
|
|
133
|
-
* @memberof Math */
|
|
134
|
-
function lerp(valueA, valueB, percent)
|
|
135
|
-
{ return valueA + clamp(percent) * (valueB-valueA); }
|
|
136
|
-
|
|
137
|
-
/** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
|
|
138
|
-
* A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
|
|
139
|
-
* @param {number} value
|
|
140
|
-
* @param {number} percentA
|
|
141
|
-
* @param {number} percentB
|
|
142
|
-
* @param {number} lerpA
|
|
143
|
-
* @param {number} lerpB
|
|
144
|
-
* @return {number}
|
|
145
|
-
* @memberof Math */
|
|
146
|
-
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
147
|
-
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
148
|
-
|
|
149
|
-
/** Returns signed wrapped distance between the two values passed in
|
|
150
|
-
* @param {number} valueA
|
|
151
|
-
* @param {number} valueB
|
|
152
|
-
* @param {number} [wrapSize]
|
|
153
|
-
* @return {number}
|
|
154
|
-
* @memberof Math */
|
|
155
|
-
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
156
|
-
{
|
|
157
|
-
ASSERT(wrapSize > 0, 'distanceWrap wrapSize must be > 0');
|
|
158
|
-
const d = (valueA - valueB) % wrapSize;
|
|
159
|
-
return d*2 % wrapSize - d;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** Linearly interpolates between values passed in with wrapping
|
|
163
|
-
* @param {number} valueA
|
|
164
|
-
* @param {number} valueB
|
|
165
|
-
* @param {number} percent
|
|
166
|
-
* @param {number} [wrapSize]
|
|
167
|
-
* @return {number}
|
|
168
|
-
* @memberof Math */
|
|
169
|
-
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
170
|
-
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
171
|
-
|
|
172
|
-
/** Returns signed wrapped distance between the two angles passed in
|
|
173
|
-
* @param {number} angleA
|
|
174
|
-
* @param {number} angleB
|
|
175
|
-
* @return {number}
|
|
176
|
-
* @memberof Math */
|
|
177
|
-
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
178
|
-
|
|
179
|
-
/** Linearly interpolates between the angles passed in with wrapping
|
|
180
|
-
* @param {number} angleA
|
|
181
|
-
* @param {number} angleB
|
|
182
|
-
* @param {number} percent
|
|
183
|
-
* @return {number}
|
|
184
|
-
* @memberof Math */
|
|
185
|
-
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
186
|
-
|
|
187
|
-
/** Applies smoothstep function to the percentage value
|
|
188
|
-
* @param {number} percent
|
|
189
|
-
* @return {number}
|
|
190
|
-
* @memberof Math */
|
|
191
|
-
function smoothStep(percent)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
*
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* @
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
* @
|
|
212
|
-
* @
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
* @
|
|
229
|
-
* @
|
|
230
|
-
|
|
231
|
-
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* @
|
|
276
|
-
* @
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
* @
|
|
293
|
-
* @
|
|
294
|
-
|
|
295
|
-
{
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
*
|
|
313
|
-
* @
|
|
314
|
-
* @
|
|
315
|
-
|
|
316
|
-
{
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
*
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* @param {number} [
|
|
338
|
-
* @
|
|
339
|
-
* @
|
|
340
|
-
|
|
341
|
-
{
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
value = 2 * phase - 1;
|
|
351
|
-
else //
|
|
352
|
-
value =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
*
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
* @
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
*
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
*
|
|
393
|
-
* @
|
|
394
|
-
* @
|
|
395
|
-
|
|
396
|
-
{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
//
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
//
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
// distance along the ray
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (hitPosFloor.
|
|
446
|
-
hitPos.
|
|
447
|
-
else if (hitPosFloor.
|
|
448
|
-
hitPos.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
* @
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
*
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
*
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
* @
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
* @
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
* @
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
* @
|
|
511
|
-
*
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
{
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
*
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
* @
|
|
529
|
-
|
|
530
|
-
{
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* @
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
* @
|
|
591
|
-
|
|
592
|
-
{
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
* @return {number} */
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
/** Returns a random
|
|
612
|
-
* @
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
{
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
{
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
*
|
|
678
|
-
* @
|
|
679
|
-
* @
|
|
680
|
-
*
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* @
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
*
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
*
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
{
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
* @
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
/** Returns the
|
|
800
|
-
* @
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
* @param {
|
|
806
|
-
* @return {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/** Returns
|
|
817
|
-
* @
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
return this;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
* @
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
{ return new Vector2(
|
|
872
|
-
|
|
873
|
-
/** Returns
|
|
874
|
-
* @return {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
/** Returns a
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
ASSERT_NUMBER_VALID(
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
/**
|
|
936
|
-
*
|
|
937
|
-
* @param {
|
|
938
|
-
* @
|
|
939
|
-
* @
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
*
|
|
948
|
-
* @
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
* @param {number} [
|
|
980
|
-
* @
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
this.
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
/** Sets
|
|
997
|
-
* @param {number} [
|
|
998
|
-
* @
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
*
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
* @param {
|
|
1038
|
-
* @return {Color} */
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
* @return {Color} */
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
/** Returns a
|
|
1047
|
-
* @param {Color}
|
|
1048
|
-
* @
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
* @
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
* @
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
*
|
|
1197
|
-
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Math Classes and Functions
|
|
3
|
+
* - Comprehensive math utilities for game development
|
|
4
|
+
* - Vector2 class for 2D positions, directions, and math operations
|
|
5
|
+
* - Color class for RGBA colors with interpolation and manipulation
|
|
6
|
+
* - RandomGenerator for seeded pseudo-random number generation
|
|
7
|
+
* - Math shortcuts (PI, abs, floor, ceil, min, max, sin, cos, etc.)
|
|
8
|
+
* - Interpolation functions (lerp, smoothStep, percent)
|
|
9
|
+
* - Clamping, wrapping, and modulo operations
|
|
10
|
+
* - Angle utilities with wrap-around support
|
|
11
|
+
* - Collision detection (overlapping, intersection, line tests)
|
|
12
|
+
* - Random number generation and seeding
|
|
13
|
+
* - Type checking utilities
|
|
14
|
+
* @namespace Math
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
/** The value of PI
|
|
20
|
+
* @type {number}
|
|
21
|
+
* @default Math.PI
|
|
22
|
+
* @memberof Math */
|
|
23
|
+
const PI = Math.PI;
|
|
24
|
+
|
|
25
|
+
/** Returns absolute value of value passed in
|
|
26
|
+
* @param {number} x
|
|
27
|
+
* @return {number}
|
|
28
|
+
* @memberof Math */
|
|
29
|
+
const abs = Math.abs;
|
|
30
|
+
|
|
31
|
+
/** Returns floored value of value passed in
|
|
32
|
+
* @param {number} x
|
|
33
|
+
* @return {number}
|
|
34
|
+
* @memberof Math */
|
|
35
|
+
const floor = Math.floor;
|
|
36
|
+
|
|
37
|
+
/** Returns ceiled value of value passed in
|
|
38
|
+
* @param {number} x
|
|
39
|
+
* @return {number}
|
|
40
|
+
* @memberof Math */
|
|
41
|
+
const ceil = Math.ceil;
|
|
42
|
+
|
|
43
|
+
/** Returns rounded value passed in
|
|
44
|
+
* @param {number} x
|
|
45
|
+
* @return {number}
|
|
46
|
+
* @memberof Math */
|
|
47
|
+
const round = Math.round;
|
|
48
|
+
|
|
49
|
+
/** Returns lowest value passed in
|
|
50
|
+
* @param {...number} values
|
|
51
|
+
* @return {number}
|
|
52
|
+
* @memberof Math */
|
|
53
|
+
const min = Math.min;
|
|
54
|
+
|
|
55
|
+
/** Returns highest value passed in
|
|
56
|
+
* @param {...number} values
|
|
57
|
+
* @return {number}
|
|
58
|
+
* @memberof Math */
|
|
59
|
+
const max = Math.max;
|
|
60
|
+
|
|
61
|
+
/** Returns the sign of value passed in
|
|
62
|
+
* @param {number} x
|
|
63
|
+
* @return {number}
|
|
64
|
+
* @memberof Math */
|
|
65
|
+
const sign = (x) => Math.sign(x);
|
|
66
|
+
|
|
67
|
+
/** Returns hypotenuse of values passed in
|
|
68
|
+
* @param {...number} values
|
|
69
|
+
* @return {number}
|
|
70
|
+
* @memberof Math */
|
|
71
|
+
const hypot = (...values) => Math.hypot(...values);
|
|
72
|
+
|
|
73
|
+
/** Returns log2 of value passed in
|
|
74
|
+
* @param {number} x
|
|
75
|
+
* @return {number}
|
|
76
|
+
* @memberof Math */
|
|
77
|
+
const log2 = (x) => Math.log2(x);
|
|
78
|
+
|
|
79
|
+
/** Returns sin of value passed in
|
|
80
|
+
* @param {number} x
|
|
81
|
+
* @return {number}
|
|
82
|
+
* @memberof Math */
|
|
83
|
+
const sin = Math.sin;
|
|
84
|
+
|
|
85
|
+
/** Returns cos of value passed in
|
|
86
|
+
* @param {number} x
|
|
87
|
+
* @return {number}
|
|
88
|
+
* @memberof Math */
|
|
89
|
+
const cos = Math.cos;
|
|
90
|
+
|
|
91
|
+
/** Returns tan of value passed in
|
|
92
|
+
* @param {number} x
|
|
93
|
+
* @return {number}
|
|
94
|
+
* @memberof Math */
|
|
95
|
+
const tan = Math.tan;
|
|
96
|
+
|
|
97
|
+
/** Returns atan2 of values passed in
|
|
98
|
+
* @param {number} y
|
|
99
|
+
* @param {number} x
|
|
100
|
+
* @return {number}
|
|
101
|
+
* @memberof Math */
|
|
102
|
+
const atan2 = Math.atan2;
|
|
103
|
+
|
|
104
|
+
/** Returns first param modulo the second param, but adjusted so negative numbers work as expected
|
|
105
|
+
* @param {number} dividend
|
|
106
|
+
* @param {number} [divisor]
|
|
107
|
+
* @return {number}
|
|
108
|
+
* @memberof Math */
|
|
109
|
+
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
110
|
+
|
|
111
|
+
/** Clamps the value between max and min
|
|
112
|
+
* @param {number} value
|
|
113
|
+
* @param {number} [min]
|
|
114
|
+
* @param {number} [max]
|
|
115
|
+
* @return {number}
|
|
116
|
+
* @memberof Math */
|
|
117
|
+
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
118
|
+
|
|
119
|
+
/** Returns what percentage the value is between valueA and valueB, clamped to 0-1
|
|
120
|
+
* @param {number} value
|
|
121
|
+
* @param {number} valueA
|
|
122
|
+
* @param {number} valueB
|
|
123
|
+
* @return {number}
|
|
124
|
+
* @memberof Math */
|
|
125
|
+
function percent(value, valueA, valueB)
|
|
126
|
+
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
127
|
+
|
|
128
|
+
/** Linearly interpolates between values passed in using percent, percent is clamped to 0-1
|
|
129
|
+
* @param {number} valueA
|
|
130
|
+
* @param {number} valueB
|
|
131
|
+
* @param {number} percent
|
|
132
|
+
* @return {number}
|
|
133
|
+
* @memberof Math */
|
|
134
|
+
function lerp(valueA, valueB, percent)
|
|
135
|
+
{ return valueA + clamp(percent) * (valueB-valueA); }
|
|
136
|
+
|
|
137
|
+
/** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
|
|
138
|
+
* A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
|
|
139
|
+
* @param {number} value
|
|
140
|
+
* @param {number} percentA
|
|
141
|
+
* @param {number} percentB
|
|
142
|
+
* @param {number} lerpA
|
|
143
|
+
* @param {number} lerpB
|
|
144
|
+
* @return {number}
|
|
145
|
+
* @memberof Math */
|
|
146
|
+
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
147
|
+
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
148
|
+
|
|
149
|
+
/** Returns signed wrapped distance between the two values passed in
|
|
150
|
+
* @param {number} valueA
|
|
151
|
+
* @param {number} valueB
|
|
152
|
+
* @param {number} [wrapSize]
|
|
153
|
+
* @return {number}
|
|
154
|
+
* @memberof Math */
|
|
155
|
+
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
156
|
+
{
|
|
157
|
+
ASSERT(wrapSize > 0, 'distanceWrap wrapSize must be > 0');
|
|
158
|
+
const d = (valueA - valueB) % wrapSize;
|
|
159
|
+
return d*2 % wrapSize - d;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Linearly interpolates between values passed in with wrapping, percent is clamped to 0-1
|
|
163
|
+
* @param {number} valueA
|
|
164
|
+
* @param {number} valueB
|
|
165
|
+
* @param {number} percent
|
|
166
|
+
* @param {number} [wrapSize]
|
|
167
|
+
* @return {number}
|
|
168
|
+
* @memberof Math */
|
|
169
|
+
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
170
|
+
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
171
|
+
|
|
172
|
+
/** Returns signed wrapped distance between the two angles passed in
|
|
173
|
+
* @param {number} angleA
|
|
174
|
+
* @param {number} angleB
|
|
175
|
+
* @return {number}
|
|
176
|
+
* @memberof Math */
|
|
177
|
+
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
178
|
+
|
|
179
|
+
/** Linearly interpolates between the angles passed in with wrapping, percent is clamped to 0-1
|
|
180
|
+
* @param {number} angleA
|
|
181
|
+
* @param {number} angleB
|
|
182
|
+
* @param {number} percent
|
|
183
|
+
* @return {number}
|
|
184
|
+
* @memberof Math */
|
|
185
|
+
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
186
|
+
|
|
187
|
+
/** Applies smoothstep function to the percentage value, clamped between 0 and 1
|
|
188
|
+
* @param {number} percent
|
|
189
|
+
* @return {number}
|
|
190
|
+
* @memberof Math */
|
|
191
|
+
function smoothStep(percent)
|
|
192
|
+
{
|
|
193
|
+
percent = clamp(percent);
|
|
194
|
+
return percent * percent * (3 - 2 * percent);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Checks if the value passed in is a power of two
|
|
198
|
+
* @param {number} value
|
|
199
|
+
* @return {boolean}
|
|
200
|
+
* @memberof Math */
|
|
201
|
+
function isPowerOfTwo(value) { return value > 0 && value % 1 === 0 && 2**round(log2(value)) === value; } // any size, not only 32 bits
|
|
202
|
+
|
|
203
|
+
/** Returns the nearest power of two not less than the value
|
|
204
|
+
* @param {number} value
|
|
205
|
+
* @return {number}
|
|
206
|
+
* @memberof Math */
|
|
207
|
+
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
208
|
+
|
|
209
|
+
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
210
|
+
* this can be used for simple collision detection between objects
|
|
211
|
+
* @param {Vector2} posA - Center of box A
|
|
212
|
+
* @param {Vector2} sizeA - Size of box A
|
|
213
|
+
* @param {Vector2} posB - Center of box B
|
|
214
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
215
|
+
* @return {boolean} - True if overlapping
|
|
216
|
+
* @memberof Math */
|
|
217
|
+
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
218
|
+
{
|
|
219
|
+
const dx = (posA.x - posB.x)*2;
|
|
220
|
+
const dy = (posA.y - posB.y)*2;
|
|
221
|
+
const sx = sizeA.x + sizeB.x;
|
|
222
|
+
const sy = sizeA.y + sizeB.y;
|
|
223
|
+
// symmetric so isOverlapping(A,B) === isOverlapping(B,A) at touching edges
|
|
224
|
+
return abs(dx) < sx && abs(dy) < sy;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Returns true if a line segment is intersecting an axis aligned box
|
|
228
|
+
* @param {Vector2} start - Start of raycast
|
|
229
|
+
* @param {Vector2} end - End of raycast
|
|
230
|
+
* @param {Vector2} pos - Center of box
|
|
231
|
+
* @param {Vector2} size - Size of box
|
|
232
|
+
* @return {boolean} - True if intersecting
|
|
233
|
+
* @memberof Math */
|
|
234
|
+
function isIntersecting(start, end, pos, size)
|
|
235
|
+
{
|
|
236
|
+
// Liang-Barsky algorithm
|
|
237
|
+
const boxMin = pos.subtract(size.scale(.5));
|
|
238
|
+
const boxMax = boxMin.add(size);
|
|
239
|
+
const delta = end.subtract(start);
|
|
240
|
+
const a = start.subtract(boxMin);
|
|
241
|
+
const b = start.subtract(boxMax);
|
|
242
|
+
const p = [-delta.x, delta.x, -delta.y, delta.y];
|
|
243
|
+
const q = [a.x, -b.x, a.y, -b.y];
|
|
244
|
+
let tMin = 0, tMax = 1;
|
|
245
|
+
for (let i = 4; i--;)
|
|
246
|
+
{
|
|
247
|
+
if (p[i])
|
|
248
|
+
{
|
|
249
|
+
const t = q[i] / p[i];
|
|
250
|
+
if (p[i] < 0)
|
|
251
|
+
{
|
|
252
|
+
if (t > tMax) return false;
|
|
253
|
+
tMin = max(t, tMin);
|
|
254
|
+
}
|
|
255
|
+
else
|
|
256
|
+
{
|
|
257
|
+
if (t < tMin) return false;
|
|
258
|
+
tMax = min(t, tMax);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (q[i] < 0)
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
269
|
+
// Collision helpers, none of them change anything that is passed in
|
|
270
|
+
// Boxes are axis aligned and centered on pos with a full size, like drawRect
|
|
271
|
+
// Each returns how far to move the first shape to get it clear, or undefined when they are not touching
|
|
272
|
+
// isOverlapping answers whether two boxes touch, these answer how far out
|
|
273
|
+
|
|
274
|
+
/** Returns the vector to move circle A by so it no longer overlaps circle B, or undefined
|
|
275
|
+
* @param {Vector2} posA - Center of circle A
|
|
276
|
+
* @param {number} radiusA
|
|
277
|
+
* @param {Vector2} posB - Center of circle B
|
|
278
|
+
* @param {number} radiusB
|
|
279
|
+
* @return {Vector2|undefined}
|
|
280
|
+
* @memberof Math */
|
|
281
|
+
function collideCircleCircle(posA, radiusA, posB, radiusB)
|
|
282
|
+
{
|
|
283
|
+
const d = posA.subtract(posB);
|
|
284
|
+
const r = radiusA + radiusB;
|
|
285
|
+
const dist = d.length();
|
|
286
|
+
if (dist >= r)
|
|
287
|
+
return undefined;
|
|
288
|
+
return d.normalize(r - dist); // coincident centers normalize to straight up
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Returns the vector to move a circle out of an axis aligned box, or undefined
|
|
292
|
+
* @param {Vector2} pos - Center of the circle
|
|
293
|
+
* @param {number} radius
|
|
294
|
+
* @param {Vector2} boxPos - Center of the box
|
|
295
|
+
* @param {Vector2} boxSize - Full size of the box
|
|
296
|
+
* @return {Vector2|undefined}
|
|
297
|
+
* @memberof Math */
|
|
298
|
+
function collideCircleBox(pos, radius, boxPos, boxSize)
|
|
299
|
+
{
|
|
300
|
+
const h = boxSize.scale(.5);
|
|
301
|
+
const closest = vec2(clamp(pos.x, boxPos.x - h.x, boxPos.x + h.x), clamp(pos.y, boxPos.y - h.y, boxPos.y + h.y));
|
|
302
|
+
const d = pos.subtract(closest), distSq = d.lengthSquared();
|
|
303
|
+
if (distSq)
|
|
304
|
+
return distSq >= radius*radius ? undefined : d.normalize(radius - distSq**.5);
|
|
305
|
+
|
|
306
|
+
// center is inside the box, push out along the axis of least penetration
|
|
307
|
+
const offset = pos.subtract(boxPos);
|
|
308
|
+
return pushOutAxis(offset, h.x - abs(offset.x), h.y - abs(offset.y), radius);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Returns the vector to move box A by so it no longer overlaps box B, the shortest way out, or undefined
|
|
312
|
+
* - isOverlapping is the yes or no version of this
|
|
313
|
+
* @param {Vector2} posA - Center of box A
|
|
314
|
+
* @param {Vector2} sizeA - Full size of box A
|
|
315
|
+
* @param {Vector2} posB - Center of box B
|
|
316
|
+
* @param {Vector2} sizeB - Full size of box B
|
|
317
|
+
* @return {Vector2|undefined}
|
|
318
|
+
* @memberof Math */
|
|
319
|
+
function collideBoxBox(posA, sizeA, posB, sizeB)
|
|
320
|
+
{
|
|
321
|
+
const d = posA.subtract(posB);
|
|
322
|
+
const overlapX = (sizeA.x + sizeB.x)/2 - abs(d.x);
|
|
323
|
+
const overlapY = (sizeA.y + sizeB.y)/2 - abs(d.y);
|
|
324
|
+
if (overlapX <= 0 || overlapY <= 0)
|
|
325
|
+
return undefined;
|
|
326
|
+
return pushOutAxis(d, overlapX, overlapY);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// the axis with the smallest penetration, pointing the way d does, with extra distance added
|
|
330
|
+
function pushOutAxis(d, penX, penY, extra=0)
|
|
331
|
+
{
|
|
332
|
+
const s = (v)=> v >= 0 ? 1 : -1; // sign() gives 0 on a tie, which would be no push
|
|
333
|
+
return penX <= penY ? vec2(s(d.x)*(penX + extra), 0) : vec2(0, s(d.y)*(penY + extra));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
337
|
+
* @param {number} [frequency] - Frequency of the wave in Hz
|
|
338
|
+
* @param {number} [amplitude] - Amplitude (max height) of the wave
|
|
339
|
+
* @param {number} [t=time] - Value to use for time of the wave
|
|
340
|
+
* @param {number} [offset] - Value to use for time offset of the wave
|
|
341
|
+
* @param {number} [type] - Wave type: 0=sine, 1=triangle, 2=square, 3=sawtooth
|
|
342
|
+
* @return {number} - Value waving between 0 and amplitude
|
|
343
|
+
* @memberof Math */
|
|
344
|
+
function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
345
|
+
{
|
|
346
|
+
const phase = mod(offset + t*frequency, 1);
|
|
347
|
+
let value;
|
|
348
|
+
|
|
349
|
+
if (type === 1) // triangle, from 0 up and back like the others
|
|
350
|
+
value = 1 - 2 * abs(2 * phase - 1);
|
|
351
|
+
else if (type === 2) // square
|
|
352
|
+
value = phase < .5 ? -1 : 1;
|
|
353
|
+
else if (type === 3) // sawtooth
|
|
354
|
+
value = 2 * phase - 1;
|
|
355
|
+
else // sine
|
|
356
|
+
value = -cos(phase * 2*PI);
|
|
357
|
+
return amplitude/2 * (value + 1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
362
|
+
* @param {any} n
|
|
363
|
+
* @return {boolean}
|
|
364
|
+
* @memberof Math */
|
|
365
|
+
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if a value is stringifiable — i.e. it has a toString that returns
|
|
369
|
+
* a string. Use this for ASSERTs and inputs that will be coerced to text;
|
|
370
|
+
* use `typeof x === 'string'` inline if you need strict-string semantics.
|
|
371
|
+
* - Returns true for strings, numbers, and most objects
|
|
372
|
+
* - Returns false for null and undefined
|
|
373
|
+
* @param {any} s
|
|
374
|
+
* @return {boolean}
|
|
375
|
+
* @memberof Math */
|
|
376
|
+
function isStringLike(s) { return s != null && typeof s.toString === 'function' && typeof s.toString() === 'string'; }
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Check if object is an array
|
|
380
|
+
* @param {any} a
|
|
381
|
+
* @return {a is Array<any>}
|
|
382
|
+
* @memberof Math */
|
|
383
|
+
function isArray(a) { return Array.isArray(a); }
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @callback LineTestFunction - Checks if a position is colliding
|
|
387
|
+
* @param {Vector2} pos
|
|
388
|
+
* @memberof Math
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Casts a ray and returns position of the first collision found, or undefined if none are found
|
|
393
|
+
* @param {Vector2} posStart
|
|
394
|
+
* @param {Vector2} posEnd
|
|
395
|
+
* @param {LineTestFunction} testFunction - Check if colliding
|
|
396
|
+
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
397
|
+
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
398
|
+
* @memberof Math */
|
|
399
|
+
function lineTest(posStart, posEnd, testFunction, normal)
|
|
400
|
+
{
|
|
401
|
+
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
402
|
+
ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
|
|
403
|
+
ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
|
|
404
|
+
ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
|
|
405
|
+
|
|
406
|
+
// get ray direction and length
|
|
407
|
+
const dx = posEnd.x - posStart.x;
|
|
408
|
+
const dy = posEnd.y - posStart.y;
|
|
409
|
+
const totalLength = (dx*dx + dy*dy)**.5;
|
|
410
|
+
if (!totalLength) return;
|
|
411
|
+
|
|
412
|
+
// current integer cell we are in
|
|
413
|
+
const pos = posStart.floor();
|
|
414
|
+
|
|
415
|
+
// normalize ray direction
|
|
416
|
+
const dirX = dx / totalLength;
|
|
417
|
+
const dirY = dy / totalLength;
|
|
418
|
+
|
|
419
|
+
// step direction in grid
|
|
420
|
+
const stepX = sign(dirX);
|
|
421
|
+
const stepY = sign(dirY);
|
|
422
|
+
|
|
423
|
+
// distance along the ray to cross one full cell in X or Y
|
|
424
|
+
const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
|
|
425
|
+
const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
|
|
426
|
+
|
|
427
|
+
// distance along the ray from start to the first grid boundary
|
|
428
|
+
const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
|
|
429
|
+
const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
|
|
430
|
+
const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
|
|
431
|
+
const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
|
|
432
|
+
|
|
433
|
+
// use line drawing algorithm to test for collisions
|
|
434
|
+
let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
|
|
435
|
+
while (t < totalLength)
|
|
436
|
+
{
|
|
437
|
+
if (testFunction(pos))
|
|
438
|
+
{
|
|
439
|
+
// set hit point
|
|
440
|
+
const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
|
|
441
|
+
|
|
442
|
+
// ensure result is inside the tile
|
|
443
|
+
const e = 1e-9;
|
|
444
|
+
const hitPosFloor = hitPos.floor();
|
|
445
|
+
if (hitPosFloor.x < pos.x)
|
|
446
|
+
hitPos.x = pos.x;
|
|
447
|
+
else if (hitPosFloor.x > pos.x)
|
|
448
|
+
hitPos.x = pos.x + 1 - e;
|
|
449
|
+
if (hitPosFloor.y < pos.y)
|
|
450
|
+
hitPos.y = pos.y;
|
|
451
|
+
else if (hitPosFloor.y > pos.y)
|
|
452
|
+
hitPos.y = pos.y + 1 - e;
|
|
453
|
+
|
|
454
|
+
// set normal
|
|
455
|
+
if (normal)
|
|
456
|
+
wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
|
|
457
|
+
return hitPos;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// advance to the next grid boundary
|
|
461
|
+
if (wasX = tX < tY)
|
|
462
|
+
{
|
|
463
|
+
pos.x += stepX;
|
|
464
|
+
t = tX;
|
|
465
|
+
tX += tDeltaX;
|
|
466
|
+
}
|
|
467
|
+
else
|
|
468
|
+
{
|
|
469
|
+
pos.y += stepY;
|
|
470
|
+
t = tY;
|
|
471
|
+
tY += tDeltaY;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
477
|
+
|
|
478
|
+
/** Random global functions
|
|
479
|
+
* @namespace Random */
|
|
480
|
+
|
|
481
|
+
/** Returns a random value between the two values passed in
|
|
482
|
+
* @param {number} [valueA]
|
|
483
|
+
* @param {number} [valueB]
|
|
484
|
+
* @return {number}
|
|
485
|
+
* @memberof Random */
|
|
486
|
+
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
487
|
+
|
|
488
|
+
/** Returns a floored random value between the two values passed in
|
|
489
|
+
* The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
|
|
490
|
+
* @param {number} valueA
|
|
491
|
+
* @param {number} [valueB]
|
|
492
|
+
* @return {number}
|
|
493
|
+
* @memberof Random */
|
|
494
|
+
function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
|
|
495
|
+
|
|
496
|
+
/** Randomly returns true or false given the chance of true passed in
|
|
497
|
+
* @param {number} [chance]
|
|
498
|
+
* @return {boolean}
|
|
499
|
+
* @memberof Random */
|
|
500
|
+
function randBool(chance=.5) { return rand() < chance; }
|
|
501
|
+
|
|
502
|
+
/** Randomly returns either -1 or 1
|
|
503
|
+
* @return {number}
|
|
504
|
+
* @memberof Random */
|
|
505
|
+
function randSign() { return randInt(2) * 2 - 1; }
|
|
506
|
+
|
|
507
|
+
/** Returns a random Vector2 with the passed in length
|
|
508
|
+
* @param {number} [length]
|
|
509
|
+
* @return {Vector2}
|
|
510
|
+
* @memberof Random */
|
|
511
|
+
function randVec2(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
512
|
+
|
|
513
|
+
/** Returns a random Vector2 within a circular shape
|
|
514
|
+
* @param {number} [radius]
|
|
515
|
+
* @param {number} [minRadius]
|
|
516
|
+
* @return {Vector2}
|
|
517
|
+
* @memberof Random */
|
|
518
|
+
function randInCircle(radius=1, minRadius=0)
|
|
519
|
+
{
|
|
520
|
+
// r is uniform in area ⇒ r² uniform in [minRadius², radius²]
|
|
521
|
+
// (the squared inner bound is what makes minRadius the actual exclusion edge)
|
|
522
|
+
if (radius <= 0) return new Vector2;
|
|
523
|
+
const ratio = clamp(minRadius / radius);
|
|
524
|
+
return randVec2(radius * rand(ratio*ratio, 1)**.5);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
528
|
+
* @param {Color} [colorA=WHITE]
|
|
529
|
+
* @param {Color} [colorB=BLACK]
|
|
530
|
+
* @param {boolean} [linear]
|
|
531
|
+
* @return {Color}
|
|
532
|
+
* @memberof Random */
|
|
533
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
534
|
+
{
|
|
535
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
536
|
+
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Seeded random number generator
|
|
543
|
+
* - Can be used to create a deterministic random number sequence
|
|
544
|
+
* - The seed works as a 32 bit integer, and one that is 0 as an integer
|
|
545
|
+
* (0, a fraction between -1 and 1, or a multiple of 2**32) uses the default seed
|
|
546
|
+
* @memberof Engine
|
|
547
|
+
* @example
|
|
548
|
+
* let r = new RandomGenerator(123); // random number generator with seed 123
|
|
549
|
+
* let a = r.float(); // random value between 0 and 1
|
|
550
|
+
* let b = r.int(10); // random integer between 0 and 9
|
|
551
|
+
* r.seed = 123; // reset the seed
|
|
552
|
+
* let c = r.float(); // the same value as a
|
|
553
|
+
*/
|
|
554
|
+
class RandomGenerator
|
|
555
|
+
{
|
|
556
|
+
/** Create a random number generator with the seed passed in
|
|
557
|
+
* @param {number} [seed] - Starting seed, 0 as an integer uses the default seed */
|
|
558
|
+
constructor(seed = 123456789)
|
|
559
|
+
{
|
|
560
|
+
/** @property {number} - random seed, set it to reseed */
|
|
561
|
+
this.seed = seed;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Returns a seeded random value between the two values passed in
|
|
565
|
+
* @param {number} [valueA]
|
|
566
|
+
* @param {number} [valueB]
|
|
567
|
+
* @return {number} */
|
|
568
|
+
float(valueA=1, valueB=0)
|
|
569
|
+
{
|
|
570
|
+
// xorshift stays at 0 once there, so a seed that is 0 as an integer uses the default seed
|
|
571
|
+
this.seed = this.seed|0 || 123456789;
|
|
572
|
+
|
|
573
|
+
// xorshift algorithm steps the seed
|
|
574
|
+
this.seed ^= this.seed << 13;
|
|
575
|
+
this.seed ^= this.seed >>> 17;
|
|
576
|
+
this.seed ^= this.seed << 5;
|
|
577
|
+
|
|
578
|
+
// the value is the seed scrambled by a hash, since xorshift barely mixes a small seed in its first steps,
|
|
579
|
+
// so nearby seeds would start with nearly the same values; the seed itself steps as before
|
|
580
|
+
return valueB + (valueA - valueB) * noiseHash(this.seed);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** Returns a floored seeded random value between the two values passed in
|
|
584
|
+
* @param {number} valueA
|
|
585
|
+
* @param {number} [valueB]
|
|
586
|
+
* @return {number} */
|
|
587
|
+
int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
|
|
588
|
+
|
|
589
|
+
/** Randomly returns true or false given the chance of true passed in
|
|
590
|
+
* @param {number} [chance]
|
|
591
|
+
* @return {boolean} */
|
|
592
|
+
bool(chance=.5) { return this.float() < chance; }
|
|
593
|
+
|
|
594
|
+
/** Randomly returns either -1 or 1 deterministically
|
|
595
|
+
* @return {number} */
|
|
596
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
597
|
+
|
|
598
|
+
/** Returns a seeded random value between the two values passed in with a random sign
|
|
599
|
+
* @param {number} [valueA]
|
|
600
|
+
* @param {number} [valueB]
|
|
601
|
+
* @return {number} */
|
|
602
|
+
floatSign(valueA=1, valueB=0)
|
|
603
|
+
{
|
|
604
|
+
const lo = min(valueA, valueB);
|
|
605
|
+
const hi = max(valueA, valueB);
|
|
606
|
+
const d = hi - lo;
|
|
607
|
+
const e = this.float(d*2);
|
|
608
|
+
return e < d ? lo + e : d - lo - e;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Returns a random angle between -PI and PI
|
|
612
|
+
* @return {number} */
|
|
613
|
+
angle() { return this.float(-PI, PI); }
|
|
614
|
+
|
|
615
|
+
/** Returns a seeded vec2 with each component between the two values passed in
|
|
616
|
+
* - A point in a square, not a random direction like randVec2, see direction for that
|
|
617
|
+
* @param {number} [valueA]
|
|
618
|
+
* @param {number} [valueB]
|
|
619
|
+
* @return {Vector2} */
|
|
620
|
+
vec2(valueA=1, valueB=0)
|
|
621
|
+
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
622
|
+
|
|
623
|
+
/** Returns a seeded Vector2 pointing a random way with the length passed in, the twin of randVec2
|
|
624
|
+
* @param {number} [length]
|
|
625
|
+
* @return {Vector2} */
|
|
626
|
+
direction(length=1) { return new Vector2().setAngle(this.float(2*PI), length); }
|
|
627
|
+
|
|
628
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
629
|
+
* @param {Color} [colorA=WHITE]
|
|
630
|
+
* @param {Color} [colorB=BLACK]
|
|
631
|
+
* @param {boolean} [linear]
|
|
632
|
+
* @return {Color} */
|
|
633
|
+
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
634
|
+
{
|
|
635
|
+
return linear ? colorA.lerp(colorB, this.float()) :
|
|
636
|
+
new Color(
|
|
637
|
+
this.float(colorA.r,colorB.r),
|
|
638
|
+
this.float(colorA.g,colorB.g),
|
|
639
|
+
this.float(colorA.b,colorB.b),
|
|
640
|
+
this.float(colorA.a,colorB.a));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Returns a new color that has each component randomly adjusted
|
|
644
|
+
* @param {Color} color
|
|
645
|
+
* @param {number} [amount]
|
|
646
|
+
* @param {number} [alphaAmount]
|
|
647
|
+
* @return {Color} */
|
|
648
|
+
mutateColor(color, amount=.05, alphaAmount=0)
|
|
649
|
+
{
|
|
650
|
+
ASSERT_NUMBER_VALID(amount);
|
|
651
|
+
ASSERT_NUMBER_VALID(alphaAmount);
|
|
652
|
+
return new Color
|
|
653
|
+
(
|
|
654
|
+
color.r + this.float(amount, -amount),
|
|
655
|
+
color.g + this.float(amount, -amount),
|
|
656
|
+
color.b + this.float(amount, -amount),
|
|
657
|
+
color.a + this.float(alphaAmount, -alphaAmount)
|
|
658
|
+
).clamp();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Create a 2d vector, can take 1 or 2 scalar values
|
|
666
|
+
* @param {number} [x]
|
|
667
|
+
* @param {number} [y] - if y is undefined, x is used for both
|
|
668
|
+
* @return {Vector2}
|
|
669
|
+
* @example
|
|
670
|
+
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
671
|
+
* a = vec2(5); // set a to (5, 5)
|
|
672
|
+
* b = vec2(); // set b to (0, 0)
|
|
673
|
+
* @memberof Math */
|
|
674
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Check if object is a valid Vector2
|
|
678
|
+
* @param {any} v
|
|
679
|
+
* @return {boolean}
|
|
680
|
+
* @memberof Math */
|
|
681
|
+
function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
682
|
+
|
|
683
|
+
// vector2 asserts
|
|
684
|
+
function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v), 'Vector2 is invalid.', v); }
|
|
685
|
+
function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
|
|
686
|
+
function ASSERT_VECTOR2_NORMAL(v)
|
|
687
|
+
{
|
|
688
|
+
ASSERT_VECTOR2_VALID(v);
|
|
689
|
+
ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* 2D Vector object with vector math library
|
|
694
|
+
* - Most functions return a new vector so they can be chained, the set functions change this one
|
|
695
|
+
* @memberof Engine
|
|
696
|
+
* @example
|
|
697
|
+
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
698
|
+
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
699
|
+
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
700
|
+
* let d = a.add(b).scale(5); // operators can be chained
|
|
701
|
+
*/
|
|
702
|
+
class Vector2
|
|
703
|
+
{
|
|
704
|
+
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
705
|
+
* @param {number} [x] - X axis location
|
|
706
|
+
* @param {number} [y] - Y axis location */
|
|
707
|
+
constructor(x=0, y=0)
|
|
708
|
+
{
|
|
709
|
+
/** @property {number} - X axis location */
|
|
710
|
+
this.x = x;
|
|
711
|
+
/** @property {number} - Y axis location */
|
|
712
|
+
this.y = y;
|
|
713
|
+
ASSERT(this.isValid(), 'Constructed Vector2 is invalid.', this);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** Sets values of this vector and returns self
|
|
717
|
+
* @param {number} [x] - X axis location
|
|
718
|
+
* @param {number} [y] - Y axis location
|
|
719
|
+
* @return {Vector2} */
|
|
720
|
+
set(x=0, y=0)
|
|
721
|
+
{
|
|
722
|
+
this.x = x;
|
|
723
|
+
this.y = y;
|
|
724
|
+
ASSERT_VECTOR2_VALID(this);
|
|
725
|
+
return this;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** Sets this vector from another vector and returns self
|
|
729
|
+
* @param {Vector2} v - other vector
|
|
730
|
+
* @return {Vector2} */
|
|
731
|
+
setFrom(v) { return this.set(v.x, v.y); }
|
|
732
|
+
|
|
733
|
+
/** Returns a new vector that is a copy of this
|
|
734
|
+
* @return {Vector2} */
|
|
735
|
+
copy() { return new Vector2(this.x, this.y); }
|
|
736
|
+
|
|
737
|
+
/** Returns a copy of this vector plus the vector passed in
|
|
738
|
+
* @param {Vector2} v - other vector
|
|
739
|
+
* @return {Vector2} */
|
|
740
|
+
add(v) { return new Vector2(this.x + v.x, this.y + v.y);}
|
|
741
|
+
|
|
742
|
+
/** Returns a copy of this vector minus the vector passed in
|
|
743
|
+
* @param {Vector2} v - other vector
|
|
744
|
+
* @return {Vector2} */
|
|
745
|
+
subtract(v) { return new Vector2(this.x - v.x, this.y - v.y); }
|
|
746
|
+
|
|
747
|
+
/** Returns a copy of this vector times the vector passed in
|
|
748
|
+
* @param {Vector2} v - other vector
|
|
749
|
+
* @return {Vector2} */
|
|
750
|
+
multiply(v) { return new Vector2(this.x * v.x, this.y * v.y); }
|
|
751
|
+
|
|
752
|
+
/** Returns a copy of this vector divided by the vector passed in
|
|
753
|
+
* @param {Vector2} v - other vector
|
|
754
|
+
* @return {Vector2} */
|
|
755
|
+
divide(v) { return new Vector2(this.x / v.x, this.y / v.y); }
|
|
756
|
+
|
|
757
|
+
/** Returns a copy of this vector scaled by the number passed in
|
|
758
|
+
* @param {number} s - scale
|
|
759
|
+
* @return {Vector2} */
|
|
760
|
+
scale(s) { return new Vector2(this.x * s, this.y * s); }
|
|
761
|
+
|
|
762
|
+
/** Returns the length of this vector
|
|
763
|
+
* @return {number} */
|
|
764
|
+
length() { return this.lengthSquared()**.5; }
|
|
765
|
+
|
|
766
|
+
/** Returns the length of this vector squared
|
|
767
|
+
* @return {number} */
|
|
768
|
+
lengthSquared() { return this.x**2 + this.y**2; }
|
|
769
|
+
|
|
770
|
+
/** Returns the distance from this vector to vector passed in
|
|
771
|
+
* @param {Vector2} v - other vector
|
|
772
|
+
* @return {number} */
|
|
773
|
+
distance(v) { return this.distanceSquared(v)**.5; }
|
|
774
|
+
|
|
775
|
+
/** Returns the distance squared from this vector to vector passed in
|
|
776
|
+
* @param {Vector2} v - other vector
|
|
777
|
+
* @return {number} */
|
|
778
|
+
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
779
|
+
|
|
780
|
+
/** Returns a new vector in same direction as this one with the length passed in
|
|
781
|
+
* - A zero vector has no direction, so it normalizes to straight up
|
|
782
|
+
* @param {number} [length]
|
|
783
|
+
* @return {Vector2} */
|
|
784
|
+
normalize(length=1)
|
|
785
|
+
{
|
|
786
|
+
const l = this.length();
|
|
787
|
+
return l ? this.scale(length/l) : new Vector2(0, length);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** Returns a new vector clamped to length passed in
|
|
791
|
+
* @param {number} [length]
|
|
792
|
+
* @return {Vector2} */
|
|
793
|
+
clampLength(length=1)
|
|
794
|
+
{
|
|
795
|
+
const l = this.length();
|
|
796
|
+
return l > length ? this.scale(length/l) : this.copy();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** Returns the dot product of this and the vector passed in
|
|
800
|
+
* @param {Vector2} v - other vector
|
|
801
|
+
* @return {number} */
|
|
802
|
+
dot(v) { return this.x*v.x + this.y*v.y; }
|
|
803
|
+
|
|
804
|
+
/** Returns the cross product of this and the vector passed in
|
|
805
|
+
* @param {Vector2} v - other vector
|
|
806
|
+
* @return {number} */
|
|
807
|
+
cross(v) { return this.x*v.y - this.y*v.x; }
|
|
808
|
+
|
|
809
|
+
/** Returns a copy this vector reflected by the surface normal
|
|
810
|
+
* @param {Vector2} normal - surface normal (should be normalized)
|
|
811
|
+
* @param {number} [restitution] - how much to bounce, 1 is perfect bounce, 0 is no bounce
|
|
812
|
+
* @return {Vector2} */
|
|
813
|
+
reflect(normal, restitution=1)
|
|
814
|
+
{ return this.subtract(normal.scale((1+restitution)*this.dot(normal))); }
|
|
815
|
+
|
|
816
|
+
/** Returns the clockwise angle of this vector, up is angle 0
|
|
817
|
+
* @return {number} */
|
|
818
|
+
angle() { return atan2(this.x, this.y); }
|
|
819
|
+
|
|
820
|
+
/** Sets this vector with clockwise angle and length passed in
|
|
821
|
+
* @param {number} [angle]
|
|
822
|
+
* @param {number} [length]
|
|
823
|
+
* @return {Vector2} */
|
|
824
|
+
setAngle(angle=0, length=1)
|
|
825
|
+
{
|
|
826
|
+
ASSERT_NUMBER_VALID(angle);
|
|
827
|
+
ASSERT_NUMBER_VALID(length);
|
|
828
|
+
this.x = length*sin(angle);
|
|
829
|
+
this.y = length*cos(angle);
|
|
830
|
+
return this;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/** Returns copy of this vector rotated by the clockwise angle passed in
|
|
834
|
+
* @param {number} angle
|
|
835
|
+
* @return {Vector2} */
|
|
836
|
+
rotate(angle)
|
|
837
|
+
{
|
|
838
|
+
ASSERT_NUMBER_VALID(angle);
|
|
839
|
+
const c = cos(-angle), s = sin(-angle);
|
|
840
|
+
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** Sets this vector to point in the specified integer direction (0-3), corresponding to multiples of 90 degree rotation
|
|
844
|
+
* @param {number} direction
|
|
845
|
+
* @param {number} [length]
|
|
846
|
+
* @return {Vector2} */
|
|
847
|
+
setDirection(direction, length=1)
|
|
848
|
+
{
|
|
849
|
+
ASSERT_NUMBER_VALID(direction);
|
|
850
|
+
ASSERT_NUMBER_VALID(length);
|
|
851
|
+
direction = mod(direction, 4);
|
|
852
|
+
ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
|
|
853
|
+
'Vector2.setDirection() direction must be an integer between 0 and 3.');
|
|
854
|
+
|
|
855
|
+
this.x = direction%2 ? direction-1 ? -length : length : 0;
|
|
856
|
+
this.y = direction%2 ? 0 : direction ? -length : length;
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
|
|
861
|
+
* @return {number} */
|
|
862
|
+
direction()
|
|
863
|
+
{ return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
|
|
864
|
+
|
|
865
|
+
/** Returns a copy of this vector with absolute values
|
|
866
|
+
* @return {Vector2} */
|
|
867
|
+
abs() { return new Vector2(abs(this.x), abs(this.y)); }
|
|
868
|
+
|
|
869
|
+
/** Returns a copy of this vector with each axis floored
|
|
870
|
+
* @return {Vector2} */
|
|
871
|
+
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
872
|
+
|
|
873
|
+
/** Returns a copy of this vector with each axis rounded
|
|
874
|
+
* @return {Vector2} */
|
|
875
|
+
round() { return new Vector2(round(this.x), round(this.y)); }
|
|
876
|
+
|
|
877
|
+
/** Returns a copy of this vector snapped down to a grid. Note that `grid` is
|
|
878
|
+
* the number of snap steps per unit (so `grid=2` snaps to halves and
|
|
879
|
+
* `grid=0.5` snaps to twos), not the cell size.
|
|
880
|
+
* @param {number} grid - snap steps per unit
|
|
881
|
+
* @return {Vector2} */
|
|
882
|
+
snap(grid)
|
|
883
|
+
{
|
|
884
|
+
ASSERT_NUMBER_VALID(grid);
|
|
885
|
+
return new Vector2(floor(this.x*grid)/grid, floor(this.y*grid)/grid);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** Returns new vec2 with modded values
|
|
889
|
+
* @param {number} [divisor]
|
|
890
|
+
* @return {Vector2} */
|
|
891
|
+
mod(divisor=1)
|
|
892
|
+
{ return new Vector2(mod(this.x, divisor), mod(this.y, divisor)); }
|
|
893
|
+
|
|
894
|
+
/** Returns the area this vector covers as a rectangle
|
|
895
|
+
* @return {number} */
|
|
896
|
+
area() { return abs(this.x * this.y); }
|
|
897
|
+
|
|
898
|
+
/** Returns a new vector that is p percent between this and the vector passed in, percent is clamped to 0-1
|
|
899
|
+
* @param {Vector2} v - other vector
|
|
900
|
+
* @param {number} percent
|
|
901
|
+
* @return {Vector2} */
|
|
902
|
+
lerp(v, percent)
|
|
903
|
+
{
|
|
904
|
+
ASSERT_VECTOR2_VALID(v);
|
|
905
|
+
ASSERT_NUMBER_VALID(percent);
|
|
906
|
+
const p = clamp(percent);
|
|
907
|
+
return new Vector2(v.x*p + this.x*(1-p), v.y*p + this.y*(1-p));
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** Returns true if this vector is within the bounds of an array size passed in
|
|
911
|
+
* @param {Vector2} arraySize
|
|
912
|
+
* @return {boolean} */
|
|
913
|
+
arrayCheck(arraySize)
|
|
914
|
+
{ return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
|
|
915
|
+
|
|
916
|
+
/** Returns this vector expressed as a string
|
|
917
|
+
* @param {number} [digits] - precision to display
|
|
918
|
+
* @return {string} */
|
|
919
|
+
toString(digits=3)
|
|
920
|
+
{
|
|
921
|
+
ASSERT_NUMBER_VALID(digits);
|
|
922
|
+
if (this.isValid())
|
|
923
|
+
return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
|
|
924
|
+
else
|
|
925
|
+
return `(${this.x}, ${this.y})`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/** Checks if this is a valid vector
|
|
929
|
+
* @return {boolean} */
|
|
930
|
+
isValid() { return isNumber(this.x) && isNumber(this.y); }
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Create a color object with RGBA values, white by default
|
|
937
|
+
* @param {number} [r=1] - red
|
|
938
|
+
* @param {number} [g=1] - green
|
|
939
|
+
* @param {number} [b=1] - blue
|
|
940
|
+
* @param {number} [a=1] - alpha
|
|
941
|
+
* @return {Color}
|
|
942
|
+
* @memberof Math
|
|
943
|
+
*/
|
|
944
|
+
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Create a color object with HSLA values, white by default
|
|
948
|
+
* @param {number} [h=0] - hue
|
|
949
|
+
* @param {number} [s=0] - saturation
|
|
950
|
+
* @param {number} [l=1] - lightness
|
|
951
|
+
* @param {number} [a=1] - alpha
|
|
952
|
+
* @return {Color}
|
|
953
|
+
* @memberof Math */
|
|
954
|
+
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Check if object is a valid Color
|
|
958
|
+
* @param {any} c
|
|
959
|
+
* @return {boolean}
|
|
960
|
+
* @memberof Math */
|
|
961
|
+
function isColor(c) { return c instanceof Color && c.isValid(); }
|
|
962
|
+
|
|
963
|
+
// color asserts
|
|
964
|
+
function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Color object (red, green, blue, alpha) with some helpful functions
|
|
968
|
+
* @memberof Engine
|
|
969
|
+
* @example
|
|
970
|
+
* let a = new Color; // white
|
|
971
|
+
* let b = new Color(1, 0, 0); // red
|
|
972
|
+
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
973
|
+
* let d = rgb(0, 0, 1); // blue using rgb color
|
|
974
|
+
* let e = hsl(.3, 1, .5); // green using hsl color
|
|
975
|
+
*/
|
|
976
|
+
class Color
|
|
977
|
+
{
|
|
978
|
+
/** Create a color with the rgba components passed in, white by default
|
|
979
|
+
* @param {number} [r] - red
|
|
980
|
+
* @param {number} [g] - green
|
|
981
|
+
* @param {number} [b] - blue
|
|
982
|
+
* @param {number} [a] - alpha*/
|
|
983
|
+
constructor(r=1, g=1, b=1, a=1)
|
|
984
|
+
{
|
|
985
|
+
/** @property {number} - Red */
|
|
986
|
+
this.r = r;
|
|
987
|
+
/** @property {number} - Green */
|
|
988
|
+
this.g = g;
|
|
989
|
+
/** @property {number} - Blue */
|
|
990
|
+
this.b = b;
|
|
991
|
+
/** @property {number} - Alpha */
|
|
992
|
+
this.a = a;
|
|
993
|
+
ASSERT(this.isValid(), 'Constructed Color is invalid.', this);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/** Sets values of this color and returns self
|
|
997
|
+
* @param {number} [r] - red
|
|
998
|
+
* @param {number} [g] - green
|
|
999
|
+
* @param {number} [b] - blue
|
|
1000
|
+
* @param {number} [a] - alpha
|
|
1001
|
+
* @return {Color} */
|
|
1002
|
+
set(r=1, g=1, b=1, a=1)
|
|
1003
|
+
{
|
|
1004
|
+
this.r = r;
|
|
1005
|
+
this.g = g;
|
|
1006
|
+
this.b = b;
|
|
1007
|
+
this.a = a;
|
|
1008
|
+
ASSERT_COLOR_VALID(this);
|
|
1009
|
+
return this;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/** Sets this color from another color and returns self
|
|
1013
|
+
* @param {Color} c - other color
|
|
1014
|
+
* @return {Color} */
|
|
1015
|
+
setFrom(c) { return this.set(c.r, c.g, c.b, c.a); }
|
|
1016
|
+
|
|
1017
|
+
/** Sets the alpha of this color and returns self
|
|
1018
|
+
* @param {number} [a] - alpha
|
|
1019
|
+
* @return {Color} */
|
|
1020
|
+
setAlpha(a=1)
|
|
1021
|
+
{
|
|
1022
|
+
this.a = a;
|
|
1023
|
+
ASSERT_COLOR_VALID(this);
|
|
1024
|
+
return this;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/** Returns a new color that is a copy of this
|
|
1028
|
+
* @return {Color} */
|
|
1029
|
+
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
1030
|
+
|
|
1031
|
+
/** Returns a copy of this color with the alpha set
|
|
1032
|
+
* @param {number} [a] - alpha
|
|
1033
|
+
* @return {Color} */
|
|
1034
|
+
withAlpha(a=1) { return new Color(this.r, this.g, this.b, a); }
|
|
1035
|
+
|
|
1036
|
+
/** Returns a copy of this color plus the color passed in
|
|
1037
|
+
* @param {Color} c - other color
|
|
1038
|
+
* @return {Color} */
|
|
1039
|
+
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
1040
|
+
|
|
1041
|
+
/** Returns a copy of this color minus the color passed in
|
|
1042
|
+
* @param {Color} c - other color
|
|
1043
|
+
* @return {Color} */
|
|
1044
|
+
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
1045
|
+
|
|
1046
|
+
/** Returns a copy of this color times the color passed in
|
|
1047
|
+
* @param {Color} c - other color
|
|
1048
|
+
* @return {Color} */
|
|
1049
|
+
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
1050
|
+
|
|
1051
|
+
/** Returns a copy of this color divided by the color passed in
|
|
1052
|
+
* @param {Color} c - other color
|
|
1053
|
+
* @return {Color} */
|
|
1054
|
+
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
1055
|
+
|
|
1056
|
+
/** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
|
|
1057
|
+
* @param {number} scale
|
|
1058
|
+
* @param {number} [alphaScale=scale]
|
|
1059
|
+
* @return {Color} */
|
|
1060
|
+
scale(scale, alphaScale=scale)
|
|
1061
|
+
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
1062
|
+
|
|
1063
|
+
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
1064
|
+
* @return {Color} */
|
|
1065
|
+
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
1066
|
+
|
|
1067
|
+
/** Returns a new color that is p percent between this and the color passed in, percent is clamped to 0-1
|
|
1068
|
+
* @param {Color} c - other color
|
|
1069
|
+
* @param {number} percent
|
|
1070
|
+
* @return {Color} */
|
|
1071
|
+
lerp(c, percent)
|
|
1072
|
+
{
|
|
1073
|
+
ASSERT_COLOR_VALID(c);
|
|
1074
|
+
ASSERT_NUMBER_VALID(percent);
|
|
1075
|
+
const p = clamp(percent);
|
|
1076
|
+
return new Color(
|
|
1077
|
+
c.r*p + this.r*(1-p),
|
|
1078
|
+
c.g*p + this.g*(1-p),
|
|
1079
|
+
c.b*p + this.b*(1-p),
|
|
1080
|
+
c.a*p + this.a*(1-p));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
1084
|
+
* @param {number} [h] - hue
|
|
1085
|
+
* @param {number} [s] - saturation
|
|
1086
|
+
* @param {number} [l] - lightness
|
|
1087
|
+
* @param {number} [a] - alpha
|
|
1088
|
+
* @return {Color} */
|
|
1089
|
+
setHSLA(h=0, s=0, l=1, a=1)
|
|
1090
|
+
{
|
|
1091
|
+
h = mod(h,1);
|
|
1092
|
+
s = clamp(s);
|
|
1093
|
+
l = clamp(l);
|
|
1094
|
+
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
1095
|
+
f = (p, q, t)=>
|
|
1096
|
+
(t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
|
|
1097
|
+
t*2 < 1 ? q :
|
|
1098
|
+
t*3 < 2 ? p+(q-p)*(4-t*6) : p;
|
|
1099
|
+
this.r = f(p, q, h + 1/3);
|
|
1100
|
+
this.g = f(p, q, h);
|
|
1101
|
+
this.b = f(p, q, h - 1/3);
|
|
1102
|
+
this.a = a;
|
|
1103
|
+
ASSERT_COLOR_VALID(this);
|
|
1104
|
+
return this;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/** Returns this color expressed in hsla format
|
|
1108
|
+
* @return {[number, number, number, number]} - hue, saturation, lightness and alpha */
|
|
1109
|
+
HSLA()
|
|
1110
|
+
{
|
|
1111
|
+
const r = clamp(this.r);
|
|
1112
|
+
const g = clamp(this.g);
|
|
1113
|
+
const b = clamp(this.b);
|
|
1114
|
+
const a = clamp(this.a);
|
|
1115
|
+
const maxC = max(r, g, b);
|
|
1116
|
+
const minC = min(r, g, b);
|
|
1117
|
+
const l = (maxC + minC) / 2;
|
|
1118
|
+
let h = 0, s = 0;
|
|
1119
|
+
if (maxC !== minC)
|
|
1120
|
+
{
|
|
1121
|
+
let d = maxC - minC;
|
|
1122
|
+
s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
|
|
1123
|
+
if (r === maxC)
|
|
1124
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
1125
|
+
else if (g === maxC)
|
|
1126
|
+
h = (b - r) / d + 2;
|
|
1127
|
+
else if (b === maxC)
|
|
1128
|
+
h = (r - g) / d + 4;
|
|
1129
|
+
}
|
|
1130
|
+
return [h / 6, s, l, a];
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/** Returns a new color that has each component randomly adjusted
|
|
1134
|
+
* @param {number} [amount]
|
|
1135
|
+
* @param {number} [alphaAmount]
|
|
1136
|
+
* @return {Color} */
|
|
1137
|
+
mutate(amount=.05, alphaAmount=0)
|
|
1138
|
+
{
|
|
1139
|
+
ASSERT_NUMBER_VALID(amount);
|
|
1140
|
+
ASSERT_NUMBER_VALID(alphaAmount);
|
|
1141
|
+
return new Color
|
|
1142
|
+
(
|
|
1143
|
+
this.r + rand(amount, -amount),
|
|
1144
|
+
this.g + rand(amount, -amount),
|
|
1145
|
+
this.b + rand(amount, -amount),
|
|
1146
|
+
this.a + rand(alphaAmount, -alphaAmount)
|
|
1147
|
+
).clamp();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/** Returns this color expressed as a hex color code
|
|
1151
|
+
* @param {boolean} [useAlpha] - if alpha should be included in result
|
|
1152
|
+
* @return {string} */
|
|
1153
|
+
toString(useAlpha = true)
|
|
1154
|
+
{
|
|
1155
|
+
if (debug && !this.isValid())
|
|
1156
|
+
return '#000';
|
|
1157
|
+
const toHex = (c)=> ((c=round(clamp(c)*255))<16 ? '0' : '') + c.toString(16);
|
|
1158
|
+
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/** Set this color from a hex code
|
|
1162
|
+
* @param {string} hex - html hex code
|
|
1163
|
+
* @return {Color} */
|
|
1164
|
+
setHex(hex)
|
|
1165
|
+
{
|
|
1166
|
+
ASSERT(isStringLike(hex), 'Color hex code must be a string');
|
|
1167
|
+
ASSERT(hex[0] === '#', 'Color hex code must start with #');
|
|
1168
|
+
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
1169
|
+
|
|
1170
|
+
if (hex.length < 6)
|
|
1171
|
+
{
|
|
1172
|
+
const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
|
|
1173
|
+
this.r = fromHex(1);
|
|
1174
|
+
this.g = fromHex(2);
|
|
1175
|
+
this.b = fromHex(3);
|
|
1176
|
+
this.a = hex.length === 5 ? fromHex(4) : 1;
|
|
1177
|
+
}
|
|
1178
|
+
else
|
|
1179
|
+
{
|
|
1180
|
+
const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
|
|
1181
|
+
this.r = fromHex(1);
|
|
1182
|
+
this.g = fromHex(3);
|
|
1183
|
+
this.b = fromHex(5);
|
|
1184
|
+
this.a = hex.length === 9 ? fromHex(7) : 1;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
ASSERT_COLOR_VALID(this);
|
|
1188
|
+
return this;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/** Returns this color expressed as 32 bit RGBA value
|
|
1192
|
+
* @return {number} */
|
|
1193
|
+
rgbaInt()
|
|
1194
|
+
{
|
|
1195
|
+
// round like toString, so WebGL and Canvas2D colors match
|
|
1196
|
+
const r = clamp(this.r)*255+.5|0;
|
|
1197
|
+
const g = clamp(this.g)*255+.5<<8;
|
|
1198
|
+
const b = clamp(this.b)*255+.5<<16;
|
|
1199
|
+
const a = clamp(this.a)*255+.5<<24;
|
|
1200
|
+
return r + g + b + a;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/** Checks if this is a valid color
|
|
1204
|
+
* @return {boolean} */
|
|
1205
|
+
isValid()
|
|
1206
|
+
{ return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a); }
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1210
|
+
// Default Colors
|
|
1211
|
+
|
|
1212
|
+
/** Color - White #ffffff
|
|
1213
|
+
* @type {Color}
|
|
1214
|
+
* @memberof Math */
|
|
1215
|
+
const WHITE = debugProtectConstant(rgb());
|
|
1216
|
+
|
|
1217
|
+
/** Color - Clear White #ffffff00 with 0 alpha
|
|
1218
|
+
* @type {Color}
|
|
1219
|
+
* @memberof Math */
|
|
1220
|
+
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
1221
|
+
|
|
1222
|
+
/** Color - Black #000000
|
|
1223
|
+
* @type {Color}
|
|
1224
|
+
* @memberof Math */
|
|
1225
|
+
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
1226
|
+
|
|
1227
|
+
/** Color - Clear Black #00000000 with 0 alpha
|
|
1228
|
+
* @type {Color}
|
|
1229
|
+
* @memberof Math */
|
|
1230
|
+
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
1231
|
+
|
|
1232
|
+
/** Color - Gray #808080
|
|
1233
|
+
* @type {Color}
|
|
1234
|
+
* @memberof Math */
|
|
1235
|
+
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
1236
|
+
|
|
1237
|
+
/** Color - Red #ff0000
|
|
1238
|
+
* @type {Color}
|
|
1239
|
+
* @memberof Math */
|
|
1240
|
+
const RED = debugProtectConstant(rgb(1,0,0));
|
|
1241
|
+
|
|
1242
|
+
/** Color - Orange #ff8000
|
|
1243
|
+
* @type {Color}
|
|
1244
|
+
* @memberof Math */
|
|
1245
|
+
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
1246
|
+
|
|
1247
|
+
/** Color - Yellow #ffff00
|
|
1248
|
+
* @type {Color}
|
|
1249
|
+
* @memberof Math */
|
|
1250
|
+
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
1251
|
+
|
|
1252
|
+
/** Color - Green #00ff00
|
|
1253
|
+
* @type {Color}
|
|
1254
|
+
* @memberof Math */
|
|
1255
|
+
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
1256
|
+
|
|
1257
|
+
/** Color - Cyan #00ffff
|
|
1258
|
+
* @type {Color}
|
|
1259
|
+
* @memberof Math */
|
|
1260
|
+
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
1261
|
+
|
|
1262
|
+
/** Color - Blue #0000ff
|
|
1263
|
+
* @type {Color}
|
|
1264
|
+
* @memberof Math */
|
|
1265
|
+
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
1266
|
+
|
|
1267
|
+
/** Color - Purple #8000ff
|
|
1268
|
+
* @type {Color}
|
|
1269
|
+
* @memberof Math */
|
|
1270
|
+
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
1271
|
+
|
|
1272
|
+
/** Color - Magenta #ff00ff
|
|
1273
|
+
* @type {Color}
|
|
1274
|
+
* @memberof Math */
|
|
1253
1275
|
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|