@flare-engine/math 0.3.1 → 0.4.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/README.md +56 -1
- package/dist/index.d.mts +96 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +96 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +267 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +256 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -270,6 +270,141 @@ const Color = {
|
|
|
270
270
|
}
|
|
271
271
|
};
|
|
272
272
|
//#endregion
|
|
273
|
+
//#region src/curves.ts
|
|
274
|
+
/**
|
|
275
|
+
* Parametric curves — quadratic/cubic Bézier and Catmull-Rom — plus approximate
|
|
276
|
+
* arc length for each.
|
|
277
|
+
*
|
|
278
|
+
* Every sampler comes in two forms, per the repo's hot-path convention: an
|
|
279
|
+
* allocating one returning a fresh `Vec2`, and an `…Into` one writing to a
|
|
280
|
+
* caller-owned buffer. Sampling a path each frame is exactly the case the
|
|
281
|
+
* `…Into` forms exist for.
|
|
282
|
+
*
|
|
283
|
+
* Control points are passed as loose numbers rather than `Vec2` objects so a
|
|
284
|
+
* caller holding coordinates in a `Float32Array` — the documented storage for
|
|
285
|
+
* bulk numeric data — can sample without building temporaries first.
|
|
286
|
+
*/
|
|
287
|
+
/** Quadratic Bézier at `t`, writing into `out`. `t` is clamped to [0, 1]. */
|
|
288
|
+
function bezier2Into(p0x, p0y, p1x, p1y, p2x, p2y, t, out) {
|
|
289
|
+
const u = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
290
|
+
const v = 1 - u;
|
|
291
|
+
const a = v * v;
|
|
292
|
+
const b = 2 * v * u;
|
|
293
|
+
const c = u * u;
|
|
294
|
+
out.x = a * p0x + b * p1x + c * p2x;
|
|
295
|
+
out.y = a * p0y + b * p1y + c * p2y;
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
/** Quadratic Bézier at `t`. Allocates — see `bezier2Into` for the hot path. */
|
|
299
|
+
function bezier2(p0x, p0y, p1x, p1y, p2x, p2y, t) {
|
|
300
|
+
return bezier2Into(p0x, p0y, p1x, p1y, p2x, p2y, t, {
|
|
301
|
+
x: 0,
|
|
302
|
+
y: 0
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** Cubic Bézier at `t`, writing into `out`. `t` is clamped to [0, 1]. */
|
|
306
|
+
function bezier3Into(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t, out) {
|
|
307
|
+
const u = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
308
|
+
const v = 1 - u;
|
|
309
|
+
const vv = v * v;
|
|
310
|
+
const uu = u * u;
|
|
311
|
+
const a = vv * v;
|
|
312
|
+
const b = 3 * vv * u;
|
|
313
|
+
const c = 3 * v * uu;
|
|
314
|
+
const d = uu * u;
|
|
315
|
+
out.x = a * p0x + b * p1x + c * p2x + d * p3x;
|
|
316
|
+
out.y = a * p0y + b * p1y + c * p2y + d * p3y;
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
/** Cubic Bézier at `t`. Allocates — see `bezier3Into` for the hot path. */
|
|
320
|
+
function bezier3(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t) {
|
|
321
|
+
return bezier3Into(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t, {
|
|
322
|
+
x: 0,
|
|
323
|
+
y: 0
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Centripetal-free (uniform) Catmull-Rom spline at `t`, writing into `out`.
|
|
328
|
+
*
|
|
329
|
+
* The curve passes **through** `p1` and `p2`; `p0` and `p3` are the neighbouring
|
|
330
|
+
* control points that set the tangents, and `t` runs from `p1` (0) to `p2` (1).
|
|
331
|
+
* That interpolating property is the reason to reach for this over a Bézier when
|
|
332
|
+
* a path must hit its waypoints exactly.
|
|
333
|
+
*/
|
|
334
|
+
function catmullRomInto(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t, out) {
|
|
335
|
+
const u = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
336
|
+
const uu = u * u;
|
|
337
|
+
const uuu = uu * u;
|
|
338
|
+
out.x = .5 * (2 * p1x + (-p0x + p2x) * u + (2 * p0x - 5 * p1x + 4 * p2x - p3x) * uu + (-p0x + 3 * p1x - 3 * p2x + p3x) * uuu);
|
|
339
|
+
out.y = .5 * (2 * p1y + (-p0y + p2y) * u + (2 * p0y - 5 * p1y + 4 * p2y - p3y) * uu + (-p0y + 3 * p1y - 3 * p2y + p3y) * uuu);
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
/** Catmull-Rom at `t`. Allocates — see `catmullRomInto` for the hot path. */
|
|
343
|
+
function catmullRom(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t) {
|
|
344
|
+
return catmullRomInto(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t, {
|
|
345
|
+
x: 0,
|
|
346
|
+
y: 0
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/** Scratch buffer for the length helpers — they are not reentrant, which is
|
|
350
|
+
* fine because they do not call user code. */
|
|
351
|
+
const _scratch = {
|
|
352
|
+
x: 0,
|
|
353
|
+
y: 0
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Approximate arc length by sampling `segments` chords.
|
|
357
|
+
*
|
|
358
|
+
* Always an **underestimate**: a chord is the shortest path between its
|
|
359
|
+
* endpoints, so a polyline through the curve is never longer than the curve.
|
|
360
|
+
* Error falls roughly with the square of `segments`; 16 is a reasonable default
|
|
361
|
+
* for UI paths, more for anything a camera follows closely.
|
|
362
|
+
*/
|
|
363
|
+
function bezier2Length(p0x, p0y, p1x, p1y, p2x, p2y, segments = 16) {
|
|
364
|
+
const n = segments < 1 ? 1 : segments | 0;
|
|
365
|
+
let total = 0;
|
|
366
|
+
let px = p0x;
|
|
367
|
+
let py = p0y;
|
|
368
|
+
for (let i = 1; i <= n; i++) {
|
|
369
|
+
bezier2Into(p0x, p0y, p1x, p1y, p2x, p2y, i / n, _scratch);
|
|
370
|
+
total += Math.hypot(_scratch.x - px, _scratch.y - py);
|
|
371
|
+
px = _scratch.x;
|
|
372
|
+
py = _scratch.y;
|
|
373
|
+
}
|
|
374
|
+
return total;
|
|
375
|
+
}
|
|
376
|
+
/** Approximate arc length of a cubic Bézier. See `bezier2Length`. */
|
|
377
|
+
function bezier3Length(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, segments = 16) {
|
|
378
|
+
const n = segments < 1 ? 1 : segments | 0;
|
|
379
|
+
let total = 0;
|
|
380
|
+
let px = p0x;
|
|
381
|
+
let py = p0y;
|
|
382
|
+
for (let i = 1; i <= n; i++) {
|
|
383
|
+
bezier3Into(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, i / n, _scratch);
|
|
384
|
+
total += Math.hypot(_scratch.x - px, _scratch.y - py);
|
|
385
|
+
px = _scratch.x;
|
|
386
|
+
py = _scratch.y;
|
|
387
|
+
}
|
|
388
|
+
return total;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Approximate arc length of the Catmull-Rom span between `p1` and `p2`.
|
|
392
|
+
* See `bezier2Length` for the accuracy note.
|
|
393
|
+
*/
|
|
394
|
+
function catmullRomLength(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, segments = 16) {
|
|
395
|
+
const n = segments < 1 ? 1 : segments | 0;
|
|
396
|
+
let total = 0;
|
|
397
|
+
let px = p1x;
|
|
398
|
+
let py = p1y;
|
|
399
|
+
for (let i = 1; i <= n; i++) {
|
|
400
|
+
catmullRomInto(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, i / n, _scratch);
|
|
401
|
+
total += Math.hypot(_scratch.x - px, _scratch.y - py);
|
|
402
|
+
px = _scratch.x;
|
|
403
|
+
py = _scratch.y;
|
|
404
|
+
}
|
|
405
|
+
return total;
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
273
408
|
//#region src/easings.ts
|
|
274
409
|
const PI = Math.PI;
|
|
275
410
|
const HALF_PI = PI / 2;
|
|
@@ -517,6 +652,102 @@ function rayVsRect(originX, originY, dirX, dirY, r) {
|
|
|
517
652
|
if (tMax < 0 || tMin > tMax) return null;
|
|
518
653
|
return tMin >= 0 ? tMin : tMax;
|
|
519
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Is `p` on the segment `a`→`b`? Assumes neither is degenerate.
|
|
657
|
+
*/
|
|
658
|
+
function pointOnSegment(px, py, ax, ay, bx, by) {
|
|
659
|
+
if ((px - ax) * (by - ay) - (py - ay) * (bx - ax) !== 0) return false;
|
|
660
|
+
const dot = (px - ax) * (bx - ax) + (py - ay) * (by - ay);
|
|
661
|
+
if (dot < 0) return false;
|
|
662
|
+
return dot <= (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Do two segments intersect, including collinear overlap?
|
|
666
|
+
*
|
|
667
|
+
* The companion to `segmentIntersection`: that returns *where* two segments
|
|
668
|
+
* cross and is `null` when there is no single crossing point, which includes
|
|
669
|
+
* collinear segments lying on top of each other. Those genuinely do intersect,
|
|
670
|
+
* so a caller who only needs the yes/no answer asks here instead.
|
|
671
|
+
*/
|
|
672
|
+
function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
|
|
673
|
+
const d1x = x2 - x1;
|
|
674
|
+
const d1y = y2 - y1;
|
|
675
|
+
const d2x = x4 - x3;
|
|
676
|
+
const d2y = y4 - y3;
|
|
677
|
+
const denom = d1x * d2y - d1y * d2x;
|
|
678
|
+
const ex = x3 - x1;
|
|
679
|
+
const ey = y3 - y1;
|
|
680
|
+
if (denom === 0) {
|
|
681
|
+
const aPoint = d1x === 0 && d1y === 0;
|
|
682
|
+
const bPoint = d2x === 0 && d2y === 0;
|
|
683
|
+
if (aPoint && bPoint) return x1 === x3 && y1 === y3;
|
|
684
|
+
if (aPoint) return pointOnSegment(x1, y1, x3, y3, x4, y4);
|
|
685
|
+
if (bPoint) return pointOnSegment(x3, y3, x1, y1, x2, y2);
|
|
686
|
+
if (ex * d1y - ey * d1x !== 0) return false;
|
|
687
|
+
const useX = Math.abs(d1x) >= Math.abs(d1y);
|
|
688
|
+
return (useX ? Math.min(x1, x2) : Math.min(y1, y2)) <= (useX ? Math.max(x3, x4) : Math.max(y3, y4)) && (useX ? Math.min(x3, x4) : Math.min(y3, y4)) <= (useX ? Math.max(x1, x2) : Math.max(y1, y2));
|
|
689
|
+
}
|
|
690
|
+
const t = (ex * d2y - ey * d2x) / denom;
|
|
691
|
+
const u = (ex * d1y - ey * d1x) / denom;
|
|
692
|
+
return t >= 0 && t <= 1 && u >= 0 && u <= 1;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Where two segments cross, written into `out`.
|
|
696
|
+
*
|
|
697
|
+
* Returns `null` when there is no **single** crossing point — segments that miss
|
|
698
|
+
* each other, are parallel, or are collinear. Collinear overlap is the case
|
|
699
|
+
* worth knowing about: the segments do intersect, at infinitely many points, so
|
|
700
|
+
* there is no one answer to write into `out`. Use `segmentsIntersect` when the
|
|
701
|
+
* yes/no answer is what you need.
|
|
702
|
+
*
|
|
703
|
+
* `out` is left untouched when the result is `null`.
|
|
704
|
+
*/
|
|
705
|
+
function segmentIntersectionInto(x1, y1, x2, y2, x3, y3, x4, y4, out) {
|
|
706
|
+
const d1x = x2 - x1;
|
|
707
|
+
const d1y = y2 - y1;
|
|
708
|
+
const d2x = x4 - x3;
|
|
709
|
+
const d2y = y4 - y3;
|
|
710
|
+
const denom = d1x * d2y - d1y * d2x;
|
|
711
|
+
if (denom === 0) {
|
|
712
|
+
const aPoint = d1x === 0 && d1y === 0;
|
|
713
|
+
const bPoint = d2x === 0 && d2y === 0;
|
|
714
|
+
if (aPoint && !bPoint && pointOnSegment(x1, y1, x3, y3, x4, y4)) {
|
|
715
|
+
out.x = x1;
|
|
716
|
+
out.y = y1;
|
|
717
|
+
return out;
|
|
718
|
+
}
|
|
719
|
+
if (bPoint && !aPoint && pointOnSegment(x3, y3, x1, y1, x2, y2)) {
|
|
720
|
+
out.x = x3;
|
|
721
|
+
out.y = y3;
|
|
722
|
+
return out;
|
|
723
|
+
}
|
|
724
|
+
if (aPoint && bPoint && x1 === x3 && y1 === y3) {
|
|
725
|
+
out.x = x1;
|
|
726
|
+
out.y = y1;
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const ex = x3 - x1;
|
|
732
|
+
const ey = y3 - y1;
|
|
733
|
+
const t = (ex * d2y - ey * d2x) / denom;
|
|
734
|
+
if (t < 0 || t > 1) return null;
|
|
735
|
+
const u = (ex * d1y - ey * d1x) / denom;
|
|
736
|
+
if (u < 0 || u > 1) return null;
|
|
737
|
+
out.x = x1 + t * d1x;
|
|
738
|
+
out.y = y1 + t * d1y;
|
|
739
|
+
return out;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Where two segments cross. Allocates — see `segmentIntersectionInto` for the
|
|
743
|
+
* hot path.
|
|
744
|
+
*/
|
|
745
|
+
function segmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
|
|
746
|
+
return segmentIntersectionInto(x1, y1, x2, y2, x3, y3, x4, y4, {
|
|
747
|
+
x: 0,
|
|
748
|
+
y: 0
|
|
749
|
+
});
|
|
750
|
+
}
|
|
520
751
|
//#endregion
|
|
521
752
|
//#region src/mat3.ts
|
|
522
753
|
const Mat3 = {
|
|
@@ -667,6 +898,25 @@ var SeededRNG = class {
|
|
|
667
898
|
int(min, max) {
|
|
668
899
|
return Math.floor(this.float(min, max + 1));
|
|
669
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* The generator's entire internal state, as a single int32.
|
|
903
|
+
*
|
|
904
|
+
* Enough to resume the exact sequence later, which is what makes a save file
|
|
905
|
+
* or a replay reproducible: store this alongside the save, hand it back to
|
|
906
|
+
* `setState`, and the next `next()` returns what it would have returned.
|
|
907
|
+
*/
|
|
908
|
+
getState() {
|
|
909
|
+
return this.state;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Restore a state previously read from `getState`.
|
|
913
|
+
*
|
|
914
|
+
* Coerced to int32 exactly as the constructor coerces a seed, so any number
|
|
915
|
+
* is accepted and a round-trip through JSON is safe.
|
|
916
|
+
*/
|
|
917
|
+
setState(state) {
|
|
918
|
+
this.state = state | 0;
|
|
919
|
+
}
|
|
670
920
|
};
|
|
671
921
|
/** Returns a random float in [min, max). */
|
|
672
922
|
function randomFloat(min, max) {
|
|
@@ -1072,15 +1322,16 @@ const Vec2 = {
|
|
|
1072
1322
|
y: v.y * inv
|
|
1073
1323
|
};
|
|
1074
1324
|
},
|
|
1075
|
-
/**
|
|
1076
|
-
|
|
1325
|
+
/** The direction of `(dx, dy)` scaled to `length`; writes zeros when `(dx, dy)` is shorter than
|
|
1326
|
+
* 1e-6. Zero-alloc, mandatory `out`; scalar components so hot paths need no temporary Vec2. */
|
|
1327
|
+
scaleToLengthInto(dx, dy, length, out) {
|
|
1077
1328
|
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1078
1329
|
if (len <= 1e-6) {
|
|
1079
1330
|
out.x = 0;
|
|
1080
1331
|
out.y = 0;
|
|
1081
1332
|
return out;
|
|
1082
1333
|
}
|
|
1083
|
-
const inv =
|
|
1334
|
+
const inv = length / len;
|
|
1084
1335
|
out.x = dx * inv;
|
|
1085
1336
|
out.y = dy * inv;
|
|
1086
1337
|
return out;
|
|
@@ -1237,7 +1488,7 @@ const Vec2 = {
|
|
|
1237
1488
|
/** Zero-alloc homing steer: rotate velocity (vx,vy) toward direction (dx,dy) by at most maxStep
|
|
1238
1489
|
* radians, preserving speed; writes into out. Mandatory out. Byte-identical to hand-rolled
|
|
1239
1490
|
* homing-bullet steering (Math.hypot speed + atan2 + branch-form turn + cos/sin * speed). */
|
|
1240
|
-
|
|
1491
|
+
turnVelocityTowardInto(vx, vy, dx, dy, maxStep, out) {
|
|
1241
1492
|
const speed = Math.hypot(vx, vy);
|
|
1242
1493
|
const newAng = rotateTowards(Math.atan2(vy, vx), Math.atan2(dy, dx), maxStep);
|
|
1243
1494
|
out.x = Math.cos(newAng) * speed;
|
|
@@ -1246,6 +1497,6 @@ const Vec2 = {
|
|
|
1246
1497
|
}
|
|
1247
1498
|
};
|
|
1248
1499
|
//#endregion
|
|
1249
|
-
export { Circle, Color, Mat3, Rect, SeededRNG, Transform2D, Vec2, advanceWindowedStreak, angleDelta, approxEquals, cappedBonus, choose, circleVsCircle, clamp, degToRad, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, hashU32, inverseLerp, lerp, lineVsCircle, lineVsRect, linear, map, pingPong, pointInCircle, pointInRect, radToDeg, randomFloat, randomInt, randomPointInCircle, randomVec2InRect, rayVsRect, rectVsCircle, rectVsRect, remainingInWindow, rotateTowards, seededIndex, shuffle, sign, smoothStep, springIn, springInOut, springOut, step, vec2, weightedIndex, weightedIndexBy, weightedPick, windowExpired, wrap };
|
|
1500
|
+
export { Circle, Color, Mat3, Rect, SeededRNG, Transform2D, Vec2, advanceWindowedStreak, angleDelta, approxEquals, bezier2, bezier2Into, bezier2Length, bezier3, bezier3Into, bezier3Length, cappedBonus, catmullRom, catmullRomInto, catmullRomLength, choose, circleVsCircle, clamp, degToRad, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, hashU32, inverseLerp, lerp, lineVsCircle, lineVsRect, linear, map, pingPong, pointInCircle, pointInRect, radToDeg, randomFloat, randomInt, randomPointInCircle, randomVec2InRect, rayVsRect, rectVsCircle, rectVsRect, remainingInWindow, rotateTowards, seededIndex, segmentIntersection, segmentIntersectionInto, segmentsIntersect, shuffle, sign, smoothStep, springIn, springInOut, springOut, step, vec2, weightedIndex, weightedIndexBy, weightedPick, windowExpired, wrap };
|
|
1250
1501
|
|
|
1251
1502
|
//# sourceMappingURL=index.mjs.map
|