@pacem/pacem-numerical 1.0.0-bessel → 1.0.0-dirac

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @pacem/pacem-numerical v1.0.0-bessel (https://js.pacem.it)
2
+ * @pacem/pacem-numerical v1.0.0-dirac (https://js.pacem.it)
3
3
  * Pacem (https://pacem.it)
4
4
  * Licensed under Apache-2.0
5
5
  */
@@ -17,9 +17,17 @@
17
17
  this.normalize(clone);
18
18
  return clone;
19
19
  }
20
+ /**
21
+ * Returns the squared magnitude (length) of the vector. Cheaper than {@link mag} since it avoids the square root.
22
+ * @param v
23
+ */
20
24
  static magSqr(v) {
21
25
  return v.x * v.x + v.y * v.y;
22
26
  }
27
+ /**
28
+ * Returns the magnitude (length) of the vector.
29
+ * @param v
30
+ */
23
31
  static mag(v) {
24
32
  return Math.sqrt(Vector.magSqr(v));
25
33
  }
@@ -239,7 +247,32 @@
239
247
  // This implementation is self-contained, numerically robust (small epsilon), and works with floating-point coordinates.
240
248
  // It assumes general position (no four points exactly cocircular). For production use with degenerate cases,
241
249
  // you may want to add a tiny random jitter to the points if needed.
250
+ /**
251
+ * Computes the (unconstrained) 2D Delaunay triangulation of a set of points, using the
252
+ * incremental Bowyer-Watson algorithm.
253
+ *
254
+ * A Delaunay triangulation connects a set of points into triangles such that no point lies
255
+ * strictly inside the circumcircle of any triangle. Among all the ways a point set can be
256
+ * triangulated, this property makes it the one that maximizes the minimum angle of all the
257
+ * triangles, avoiding thin "sliver" triangles — which is why it's the standard choice for
258
+ * meshes, terrain modeling, and (via its dual) Voronoi diagrams.
259
+ *
260
+ * The Bowyer-Watson algorithm builds the triangulation incrementally: starting from a single
261
+ * "super triangle" that encloses every input point, each point is inserted one at a time by
262
+ * removing every existing triangle whose circumcircle contains it (which necessarily leaves a
263
+ * star-shaped polygonal hole around the new point) and re-triangulating that hole by connecting
264
+ * the new point to the hole's boundary edges. Once every point has been inserted, any triangle
265
+ * still touching a vertex of the initial super triangle is discarded, leaving the final
266
+ * triangulation of the original points.
267
+ */
242
268
  class Delaunay {
269
+ /**
270
+ * Computes the Delaunay triangulation of the given points.
271
+ * @param vertices Points to triangulate.
272
+ * @returns A flat array of triangle indices into {@link vertices} (each consecutive group of 3
273
+ * entries identifies one triangle), in the same "vertices + triangle indices" format used by
274
+ * most graphics/meshing libraries. Returns an empty array if fewer than 3 points are provided.
275
+ */
243
276
  static triangulate(vertices) {
244
277
  return delaunayTriangulation(vertices);
245
278
  }
@@ -256,6 +289,7 @@
256
289
  return s != null && Array.isArray(s) && s.length === 2
257
290
  && isPoint(s[0]) && isPoint(s[1]);
258
291
  }
292
+ /** Utility tools for 2D geometry: points, segments, polygons and their interactions. */
259
293
  let Utils$2 = class Utils {
260
294
  /**
261
295
  * Computes the slope (in radians) of the segment joining two points.
@@ -303,6 +337,11 @@
303
337
  return null;
304
338
  }
305
339
  }
340
+ /**
341
+ * Computes the axis-aligned bounding box of a set of points.
342
+ * @param vertices Points to enclose.
343
+ * @returns The smallest rect containing all provided points.
344
+ */
306
345
  static boundingBox(vertices) {
307
346
  let xmin = Number.MAX_VALUE, ymin = Number.MAX_VALUE, xmax = -Number.MAX_VALUE, ymax = -Number.MAX_VALUE;
308
347
  vertices.length;
@@ -314,6 +353,15 @@
314
353
  }
315
354
  return { x: xmin, y: ymin, width: xmax - xmin, height: ymax - ymin };
316
355
  }
356
+ /**
357
+ * Combines two polygons using a boolean set operator (union, intersection or difference).
358
+ * Internally triangulates the merged vertex set (via {@link mesh}) and filters the resulting
359
+ * triangles by whether their barycenter falls inside either source polygon.
360
+ * @param polygon1 First polygon.
361
+ * @param polygon2 Second polygon.
362
+ * @param operator Boolean operator to apply.
363
+ * @returns The resulting polygon(s), plus the underlying triangulation mesh and triangles used to compute them; or `null` if the operation yields no polygon.
364
+ */
317
365
  static combinePolygons(polygon1, polygon2, operator) {
318
366
  return Utils._mergePolygons(polygon1, polygon2, operator);
319
367
  }
@@ -720,6 +768,12 @@
720
768
  const minx = Math.min(segment[0].x, segment[1].x), maxx = Math.max(segment[0].x, segment[1].x), miny = Math.min(segment[0].y, segment[1].y), maxy = Math.max(segment[0].y, segment[1].y);
721
769
  return p.x >= minx && p.x <= maxx && p.y >= miny && p.y <= maxy && Utils.inLine(p, segment, precision);
722
770
  }
771
+ /**
772
+ * Checks whether a point lies inside (or on the boundary of) a triangle, by verifying that it
773
+ * is on the same rotational side of all three edges.
774
+ * @param p Point to test.
775
+ * @param triangle Triangle vertices.
776
+ */
723
777
  static inTriangle(p, triangle) {
724
778
  let last;
725
779
  for (let j = 0; j < 3; j++) {
@@ -850,24 +904,52 @@
850
904
  && (vertices = obj.vertices).length >= 3
851
905
  && vertices.every(i => pacemFoundation.Point.isPoint(i));
852
906
  }
907
+ /** Utility tools to build and inspect {@link Polygon}s. */
853
908
  class Polygon {
854
909
  static { this._eps = 1e-12; }
910
+ /**
911
+ * Type guard checking whether the provided value is a valid {@link Polygon} (has at least 3 point vertices).
912
+ * @param obj
913
+ */
855
914
  static isPolygon(obj) {
856
915
  return isPolygon(obj);
857
916
  }
917
+ /**
918
+ * Builds a polygon from the provided vertices, in order.
919
+ * @param points Vertices of the polygon.
920
+ */
858
921
  static from(...points) {
859
922
  return { vertices: Array.from(points) };
860
923
  }
924
+ /**
925
+ * Checks whether a point lies inside the polygon.
926
+ * @param polygon
927
+ * @param p Point to test.
928
+ */
861
929
  static contains(polygon, p) {
862
930
  return Utils$2.inPolygon(p, polygon.vertices, 12);
863
931
  }
932
+ /**
933
+ * Returns the center of the polygon's bounding box.
934
+ * Note: this is not the geometric centroid (center of mass) for non-symmetric polygons.
935
+ * @param polygon
936
+ */
864
937
  static centroid(polygon) {
865
938
  const bbox = Polygon.boundingBox(polygon);
866
939
  return { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 };
867
940
  }
941
+ /**
942
+ * Computes the axis-aligned bounding box of the polygon.
943
+ * @param polygon
944
+ */
868
945
  static boundingBox(polygon) {
869
946
  return Utils$2.boundingBox(polygon.vertices);
870
947
  }
948
+ /**
949
+ * Returns the polygon's edges as an ordered list of segments, each joining a vertex to the next
950
+ * (the last one wrapping back to the first).
951
+ * @param polygon
952
+ */
871
953
  static sides(polygon) {
872
954
  const { vertices } = polygon;
873
955
  const retval = [];
@@ -877,6 +959,10 @@
877
959
  }
878
960
  return retval;
879
961
  }
962
+ /**
963
+ * Checks whether the polygon is convex, i.e. all its interior angles turn in the same rotational direction.
964
+ * @param polygon
965
+ */
880
966
  static isConvex(polygon) {
881
967
  const { vertices } = polygon, l = vertices.length;
882
968
  if (l <= 3) {
@@ -965,6 +1051,12 @@
965
1051
  static area(polygon) {
966
1052
  return Utils$2.area(polygon.vertices);
967
1053
  }
1054
+ /**
1055
+ * Computes the intersection area(s) between two polygons.
1056
+ * @param polygon1
1057
+ * @param polygon2
1058
+ * @returns The overlapping area(s) as one or more polygons.
1059
+ */
968
1060
  static intersect(polygon1, polygon2) {
969
1061
  return Utils$2.intersect(polygon1, polygon2);
970
1062
  }
@@ -983,7 +1075,13 @@
983
1075
  // namespace Pacem.Geometry.LinearAlgebra {
984
1076
  const RAD2DEG = 180.0 / Math.PI;
985
1077
  const DEG2RAD = 1.0 / RAD2DEG;
1078
+ /** 3D vector utils. */
986
1079
  class Vector3D {
1080
+ /**
1081
+ * Builds a {@link Vector3D} from its x, y, z components, in that order.
1082
+ * @param args Exactly 3 numbers: x, y, z.
1083
+ * @throws If the number of arguments is not exactly 3.
1084
+ */
987
1085
  static from(...args) {
988
1086
  const l = 3;
989
1087
  if (args.length !== l) {
@@ -991,6 +1089,11 @@
991
1089
  }
992
1090
  return { x: args[0], y: args[1], z: args[2] };
993
1091
  }
1092
+ /**
1093
+ * Parses a string representation (e.g. `"1 2 3"` or `"1,2,3"`) into a {@link Vector3D}.
1094
+ * @param input String to parse.
1095
+ * @throws If the input cannot be parsed as exactly 3 numbers.
1096
+ */
994
1097
  static parse(input) {
995
1098
  const arr = pacemFoundation.parseAsNumericalArray(input);
996
1099
  if (arr && arr.length === 3) {
@@ -999,15 +1102,19 @@
999
1102
  throw new Error(`Cannot parse "${input}" as a valid Vector3D.`);
1000
1103
  }
1001
1104
  // notable vectors
1105
+ /** Returns the unit vector along the x axis: `(1, 0, 0)`. */
1002
1106
  static i() {
1003
1107
  return { x: 1, y: 0, z: 0 };
1004
1108
  }
1109
+ /** Returns the unit vector along the y axis: `(0, 1, 0)`. */
1005
1110
  static j() {
1006
1111
  return { x: 0, y: 1, z: 0 };
1007
1112
  }
1113
+ /** Returns the unit vector along the z axis: `(0, 0, 1)`. */
1008
1114
  static k() {
1009
1115
  return { x: 0, y: 0, z: 1 };
1010
1116
  }
1117
+ /** Returns the zero vector: `(0, 0, 0)`. */
1011
1118
  static zero() {
1012
1119
  return { x: 0, y: 0, z: 0 };
1013
1120
  }
@@ -1055,6 +1162,10 @@
1055
1162
  static scale(v, fx, fy = fx, fz = fx) {
1056
1163
  return { x: v.x * fx, y: v.y * fy, z: v.z * fz };
1057
1164
  }
1165
+ /**
1166
+ * Returns the squared magnitude (length) of the vector. Cheaper than {@link mag} since it avoids the square root.
1167
+ * @param v
1168
+ */
1058
1169
  static magSqr(v) {
1059
1170
  return v.x * v.x + v.y * v.y + v.z * v.z;
1060
1171
  }
@@ -1069,9 +1180,17 @@
1069
1180
  && (v2.y - v1.y).isCloseTo(0)
1070
1181
  && (v2.z - v1.z).isCloseTo(0);
1071
1182
  }
1183
+ /**
1184
+ * Returns the magnitude (length) of the vector.
1185
+ * @param v
1186
+ */
1072
1187
  static mag(v) {
1073
1188
  return Math.sqrt(Vector3D.magSqr(v));
1074
1189
  }
1190
+ /**
1191
+ * Returns the opposite vector (each component negated).
1192
+ * @param v
1193
+ */
1075
1194
  static negate(v) {
1076
1195
  return { x: -v.x, y: -v.y, z: -v.z };
1077
1196
  }
@@ -1121,6 +1240,10 @@
1121
1240
  return RAD2DEG * num;
1122
1241
  }
1123
1242
  }
1243
+ /**
1244
+ * Utilities to convert between spherical coordinates (radial distance `rho`, azimuth `theta` and
1245
+ * polar angle `phi`) and their cartesian ({@link Vector3D}) or rotation-matrix ({@link Matrix3D}) equivalents.
1246
+ */
1124
1247
  class Spherical {
1125
1248
  /**
1126
1249
  * Creates a new {@link Spherical} object.
@@ -1143,6 +1266,10 @@
1143
1266
  const theta = Math.atan2(v.x, v.z), phi = Math.acos((v.y / rho).clamp(-1, 1));
1144
1267
  return Spherical.from(rho, theta * RAD2DEG, phi * RAD2DEG);
1145
1268
  }
1269
+ /**
1270
+ * Converts {@link Spherical} coordinates back into cartesian {@link Vector3D} coordinates.
1271
+ * @param coords
1272
+ */
1146
1273
  static toVector(coords) {
1147
1274
  const { rho, theta: thetaDeg, phi: phiDeg } = coords;
1148
1275
  const phi = phiDeg * DEG2RAD;
@@ -1202,6 +1329,7 @@
1202
1329
  return Matrix3D.from((((m1.m11 * m2.m11) + (m1.m12 * m2.m21)) + (m1.m13 * m2.m31)) + (m1.m14 * m2.offsetX), (((m1.m11 * m2.m12) + (m1.m12 * m2.m22)) + (m1.m13 * m2.m32)) + (m1.m14 * m2.offsetY), (((m1.m11 * m2.m13) + (m1.m12 * m2.m23)) + (m1.m13 * m2.m33)) + (m1.m14 * m2.offsetZ), (((m1.m11 * m2.m14) + (m1.m12 * m2.m24)) + (m1.m13 * m2.m34)) + (m1.m14 * m2.m44), (((m1.m21 * m2.m11) + (m1.m22 * m2.m21)) + (m1.m23 * m2.m31)) + (m1.m24 * m2.offsetX), (((m1.m21 * m2.m12) + (m1.m22 * m2.m22)) + (m1.m23 * m2.m32)) + (m1.m24 * m2.offsetY), (((m1.m21 * m2.m13) + (m1.m22 * m2.m23)) + (m1.m23 * m2.m33)) + (m1.m24 * m2.offsetZ), (((m1.m21 * m2.m14) + (m1.m22 * m2.m24)) + (m1.m23 * m2.m34)) + (m1.m24 * m2.m44), (((m1.m31 * m2.m11) + (m1.m32 * m2.m21)) + (m1.m33 * m2.m31)) + (m1.m34 * m2.offsetX), (((m1.m31 * m2.m12) + (m1.m32 * m2.m22)) + (m1.m33 * m2.m32)) + (m1.m34 * m2.offsetY), (((m1.m31 * m2.m13) + (m1.m32 * m2.m23)) + (m1.m33 * m2.m33)) + (m1.m34 * m2.offsetZ), (((m1.m31 * m2.m14) + (m1.m32 * m2.m24)) + (m1.m33 * m2.m34)) + (m1.m34 * m2.m44), (((m1.offsetX * m2.m11) + (m1.offsetY * m2.m21)) + (m1.offsetZ * m2.m31)) + (m1.m44 * m2.offsetX), (((m1.offsetX * m2.m12) + (m1.offsetY * m2.m22)) + (m1.offsetZ * m2.m32)) + (m1.m44 * m2.offsetY), (((m1.offsetX * m2.m13) + (m1.offsetY * m2.m23)) + (m1.offsetZ * m2.m33)) + (m1.m44 * m2.offsetZ), (((m1.offsetX * m2.m14) + (m1.offsetY * m2.m24)) + (m1.offsetZ * m2.m34)) + (m1.m44 * m2.m44));
1203
1330
  }
1204
1331
  }
1332
+ /** Utilities to build, combine and invert 4x4 transformation matrices ({@link Matrix3D}) in 3D space. */
1205
1333
  class Matrix3D {
1206
1334
  //#region notable matrices
1207
1335
  /**
@@ -1217,6 +1345,11 @@
1217
1345
  };
1218
1346
  }
1219
1347
  //#endregion
1348
+ /**
1349
+ * Builds a {@link Matrix3D} from its 16 components, in row-major order.
1350
+ * @param args Exactly 16 numbers.
1351
+ * @throws If the number of arguments is not exactly 16.
1352
+ */
1220
1353
  static from(...args) {
1221
1354
  const l = 16;
1222
1355
  if (args.length !== l) {
@@ -1241,9 +1374,17 @@
1241
1374
  m44: args[15]
1242
1375
  };
1243
1376
  }
1377
+ /**
1378
+ * Returns the transpose of the given matrix (rows and columns swapped).
1379
+ * @param m
1380
+ */
1244
1381
  static transpose(m) {
1245
1382
  return Matrix3D.from(m.m11, m.m21, m.m31, m.offsetX, m.m12, m.m22, m.m32, m.offsetY, m.m13, m.m23, m.m33, m.offsetZ, m.m14, m.m24, m.m34, m.m44);
1246
1383
  }
1384
+ /**
1385
+ * Flattens the matrix into a 16-number array, in row-major order (the same order accepted by {@link from}).
1386
+ * @param m
1387
+ */
1247
1388
  static toArray(m) {
1248
1389
  return [m.m11, m.m12, m.m13, m.m14,
1249
1390
  m.m21, m.m22, m.m23, m.m24,
@@ -1269,6 +1410,11 @@
1269
1410
  s.m33 *= z;
1270
1411
  });
1271
1412
  }
1413
+ /**
1414
+ * Returns a copy of the matrix translated (offset) by the given vector.
1415
+ * @param m
1416
+ * @param offset
1417
+ */
1272
1418
  static translate(m, offset) {
1273
1419
  return Matrix3DUtils.modify(m, s => {
1274
1420
  s.offsetX += offset.x;
@@ -1276,6 +1422,11 @@
1276
1422
  s.offsetZ += offset.z;
1277
1423
  });
1278
1424
  }
1425
+ /**
1426
+ * Parses a string representation of a matrix (16 numbers) into a {@link Matrix3D}.
1427
+ * @param input String to parse.
1428
+ * @throws If the input cannot be parsed as exactly 16 numbers.
1429
+ */
1279
1430
  static parse(input) {
1280
1431
  const arr = pacemFoundation.parseAsNumericalArray(input);
1281
1432
  if (arr && arr.length === 16) {
@@ -1283,15 +1434,28 @@
1283
1434
  }
1284
1435
  throw new Error(`Cannot parse "${input}" as a valid Matrix3D.`);
1285
1436
  }
1437
+ /**
1438
+ * Checks whether the matrix is the identity matrix.
1439
+ * @param m
1440
+ */
1286
1441
  static isIdentity(m) {
1287
1442
  return m.m11 == 1.0 && m.m12 == .0 && m.m13 == .0 && m.m14 == .0
1288
1443
  && m.m21 == .0 && m.m22 == 1.0 && m.m23 == .0 && m.m24 == .0
1289
1444
  && m.m31 == .0 && m.m32 == .0 && m.m33 == 1.0 && m.m34 == .0
1290
1445
  && m.offsetX == .0 && m.offsetY == .0 && m.offsetZ == .0 && m.m44 == 1.0;
1291
1446
  }
1447
+ /**
1448
+ * Checks whether the matrix represents a pure affine transform, i.e. it has no perspective
1449
+ * component (its 4th column is `(0, 0, 0, 1)`).
1450
+ * @param m
1451
+ */
1292
1452
  static isAffine(m) {
1293
1453
  return m.m14 == .0 && m.m24 == .0 && m.m34 == .0 && m.m44 == 1.0;
1294
1454
  }
1455
+ /**
1456
+ * Computes the determinant of the matrix.
1457
+ * @param m
1458
+ */
1295
1459
  static determinant(m) {
1296
1460
  if (Matrix3D.isIdentity(m)) {
1297
1461
  return 1.0;
@@ -1332,6 +1496,12 @@
1332
1496
  }
1333
1497
  return m;
1334
1498
  }
1499
+ /**
1500
+ * Computes the inverse of the matrix, using the cofactor/adjugate method (with a specialized,
1501
+ * cheaper path for affine matrices).
1502
+ * @param m
1503
+ * @returns The inverse matrix, or `null` if the matrix is singular (determinant is 0).
1504
+ */
1335
1505
  static invert(m) {
1336
1506
  if (Matrix3D.isAffine(m)) {
1337
1507
  // normalize affine invert
@@ -1436,10 +1606,17 @@
1436
1606
  return pt;
1437
1607
  }
1438
1608
  }
1609
+ /** Utilities to build, combine and convert {@link Quaternion} rotations. */
1439
1610
  class Quaternion {
1611
+ /** Returns the identity quaternion (no rotation). */
1440
1612
  static identity() {
1441
1613
  return Quaternion.from(0, 0, 0, 1);
1442
1614
  }
1615
+ /**
1616
+ * Builds a {@link Quaternion} from its x, y, z, w components, in that order.
1617
+ * @param args Exactly 4 numbers: x, y, z, w.
1618
+ * @throws If the number of arguments is not exactly 4.
1619
+ */
1443
1620
  static from(...args) {
1444
1621
  const l = 4;
1445
1622
  if (args.length !== l) {
@@ -1452,6 +1629,11 @@
1452
1629
  w: args[3],
1453
1630
  };
1454
1631
  }
1632
+ /**
1633
+ * Parses a string representation of a quaternion (4 numbers) into a {@link Quaternion}.
1634
+ * @param input String to parse.
1635
+ * @throws If the input cannot be parsed as exactly 4 numbers.
1636
+ */
1455
1637
  static parse(input) {
1456
1638
  const arr = pacemFoundation.parseAsNumericalArray(input);
1457
1639
  if (arr && arr.length === 4) {
@@ -1459,6 +1641,12 @@
1459
1641
  }
1460
1642
  throw new Error(`Cannot parse "${input}" as a valid Quaternion.`);
1461
1643
  }
1644
+ /**
1645
+ * Builds the (shortest-arc) quaternion representing the rotation that takes one unit vector onto another.
1646
+ * Normalizes both input vectors in place as a side effect.
1647
+ * @param from Source direction.
1648
+ * @param to Target direction.
1649
+ */
1462
1650
  static fromVectors(from, to) {
1463
1651
  // setup
1464
1652
  Vector3D.normalize(from);
@@ -1566,6 +1754,11 @@
1566
1754
  }
1567
1755
  }
1568
1756
  }
1757
+ /**
1758
+ * Returns the conjugate of the quaternion (vector part negated). For a unit quaternion, this
1759
+ * represents the inverse rotation.
1760
+ * @param q
1761
+ */
1569
1762
  static conjugate(q) {
1570
1763
  return Quaternion.from(-q.x, -q.y, -q.z, q.w);
1571
1764
  }
@@ -1576,15 +1769,29 @@
1576
1769
  static mag(q) {
1577
1770
  return Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
1578
1771
  }
1772
+ /**
1773
+ * Returns the squared magnitude of the vector (imaginary) part of the quaternion.
1774
+ * @param q
1775
+ */
1579
1776
  static norm(q) {
1580
1777
  return (q.x * q.x + q.y * q.y + q.z * q.z);
1581
1778
  }
1779
+ /**
1780
+ * Returns the (unit) rotation axis represented by the quaternion, defaulting to the y axis
1781
+ * when the quaternion has no vector part (i.e. represents no rotation).
1782
+ * @param q
1783
+ */
1582
1784
  static axis(q) {
1583
1785
  if (q.x == .0 && q.y == .0 && q.z == .0) {
1584
1786
  return Vector3D.j();
1585
1787
  }
1586
1788
  return Vector3D.unit(q);
1587
1789
  }
1790
+ /**
1791
+ * Rotates a vector by the given quaternion (normalizes the quaternion in place as a side effect).
1792
+ * @param v Vector to rotate.
1793
+ * @param q Rotation quaternion.
1794
+ */
1588
1795
  static transform(v, q) {
1589
1796
  Quaternion.normalize(q);
1590
1797
  const cross = Vector3D.cross(q, v);
@@ -1613,6 +1820,10 @@
1613
1820
  }
1614
1821
  return (Math.atan2(y, x) * 114.59155902616465);
1615
1822
  }
1823
+ /**
1824
+ * Builds the rotation matrix equivalent to the given quaternion.
1825
+ * @param q
1826
+ */
1616
1827
  static toRotationMatrix(q) {
1617
1828
  var m = Matrix3D.identity();
1618
1829
  var X = q.x;
@@ -1630,6 +1841,10 @@
1630
1841
  m.m33 = 1.0 - 2.0 * X * X - 2.0 * Y * Y;
1631
1842
  return m;
1632
1843
  }
1844
+ /**
1845
+ * Returns the inverse rotation of the given quaternion (the conjugate of its normalized form).
1846
+ * @param q
1847
+ */
1633
1848
  static invert(q) {
1634
1849
  const n = Quaternion.unit(q);
1635
1850
  return Quaternion.conjugate(n);
@@ -1657,6 +1872,7 @@
1657
1872
 
1658
1873
  //namespace Pacem.Geometry {
1659
1874
  //#region Utils
1875
+ /** Utility tools for 3D geometry: rays, triangles and their interactions. */
1660
1876
  class Utils3D {
1661
1877
  /**
1662
1878
  * Computes the intersection between a ray and a triangle in the 3D space - if any - using the Möller–Trumbore algorithm.
@@ -1731,6 +1947,7 @@
1731
1947
  function nac() {
1732
1948
  return NOT_A_COMPLEX || (NOT_A_COMPLEX = buildComplex(Number.NaN, Number.NaN));
1733
1949
  }
1950
+ /** Utility tools to build and operate on {@link Complex} numbers. */
1734
1951
  class Complex {
1735
1952
  static build(real, img) {
1736
1953
  if (this.isComplex(real)) {
@@ -1742,20 +1959,41 @@
1742
1959
  }
1743
1960
  return buildComplex(real, img || 0);
1744
1961
  }
1962
+ /**
1963
+ * Adds two complex numbers (real numbers are treated as having a zero imaginary part).
1964
+ * @param a First addend.
1965
+ * @param b Second addend.
1966
+ */
1745
1967
  static add(a, b) {
1746
1968
  const ac = complex(a), bc = complex(b);
1747
1969
  return buildComplex(ac.real + bc.real, ac.img + bc.img);
1748
1970
  }
1971
+ /**
1972
+ * Subtracts one complex number from another.
1973
+ * @param from Minuend.
1974
+ * @param what Subtrahend.
1975
+ */
1749
1976
  static subtract(from, what) {
1750
1977
  const ac = complex(from), bc = complex(what);
1751
1978
  return buildComplex(ac.real - bc.real, ac.img - bc.img);
1752
1979
  }
1980
+ /**
1981
+ * Multiplies two complex numbers.
1982
+ * @param a First factor.
1983
+ * @param b Second factor.
1984
+ */
1753
1985
  static multiply(a, b) {
1754
1986
  const ac = complex(a), bc = complex(b);
1755
1987
  return buildComplex(
1756
1988
  /* real*/ ac.real * bc.real - ac.img * bc.img,
1757
1989
  /* img */ ac.real * bc.img + ac.img * bc.real);
1758
1990
  }
1991
+ /**
1992
+ * Divides one complex number by another.
1993
+ * @param a Dividend.
1994
+ * @param b Divisor.
1995
+ * @returns The quotient, or {@link Complex.NaC} if the divisor is (close to) zero.
1996
+ */
1759
1997
  static divide(a, b) {
1760
1998
  const ac = complex(a), bc = complex(b);
1761
1999
  const div = this.absSquare(bc).roundoff();
@@ -1767,20 +2005,42 @@
1767
2005
  /* real*/ inv_div * (ac.real * bc.real + ac.img * bc.img),
1768
2006
  /* img */ inv_div * (ac.img * bc.real - ac.real * bc.img));
1769
2007
  }
2008
+ /**
2009
+ * Returns the squared modulus (|z|²) of the complex number, i.e. real² + img².
2010
+ * Cheaper than {@link modulus} since it avoids the square root.
2011
+ * @param c
2012
+ */
1770
2013
  static absSquare(c) {
1771
2014
  const ac = complex(c);
1772
2015
  return Math.pow(ac.real, 2) + Math.pow(ac.img, 2);
1773
2016
  }
2017
+ /**
2018
+ * Returns the modulus (magnitude) of the complex number.
2019
+ * @param c
2020
+ */
1774
2021
  static modulus(c) {
1775
2022
  return Math.sqrt(this.absSquare(c));
1776
2023
  }
2024
+ /**
2025
+ * Type guard checking whether the provided value is a {@link Complex} number.
2026
+ * @param c
2027
+ */
1777
2028
  static isComplex(c) {
1778
2029
  return c != null && typeof c === 'object' && 'real' in c && 'img' in c && typeof c.real === 'number' && typeof c.img === 'number';
1779
2030
  }
2031
+ /**
2032
+ * Returns the complex conjugate: same real part, negated imaginary part.
2033
+ * @param a
2034
+ */
1780
2035
  static conjugate(a) {
1781
2036
  a = complex(a);
1782
2037
  return buildComplex(a.real, Math.abs(a.img) == 0 ? 0 : -a.img);
1783
2038
  }
2039
+ /**
2040
+ * Checks whether two complex numbers have the same real and imaginary parts.
2041
+ * @param c1
2042
+ * @param c2
2043
+ */
1784
2044
  static equals(c1, c2) {
1785
2045
  const c_1 = this.build(c1), c_2 = this.build(c2);
1786
2046
  if (!this.isComplex(c1) || !this.isComplex(c2)) {
@@ -1800,6 +2060,7 @@
1800
2060
  // if (!this.isComplex( c)
1801
2061
  // }
1802
2062
  //}
2063
+ /** The "Not a Complex" sentinel value (`NaN + NaNi`), returned by operations on invalid input. */
1803
2064
  static get NaC() { return nac(); }
1804
2065
  }
1805
2066
  //}
@@ -1840,6 +2101,7 @@
1840
2101
  }
1841
2102
  return retval;
1842
2103
  }
2104
+ /** Discrete Fourier Transform (DFT) utilities, including a Cooley-Tukey FFT fast path for power-of-two inputs. */
1843
2105
  class Fourier {
1844
2106
  /**
1845
2107
  * Checks the input vector and outputs a frequency vector using the best performing algo.
@@ -1913,12 +2175,19 @@
1913
2175
  t * (-0.82215223 + t * 0.17087277)))))))));
1914
2176
  return x >= 0.0 ? ans : 2.0 - ans;
1915
2177
  }
2178
+ /** Represents a Gaussian (normal) distribution and exposes its density and cumulative probability functions. */
1916
2179
  class Gaussian {
2180
+ /**
2181
+ * Creates a new {@link Gaussian} distribution with the given mean and standard deviation.
2182
+ * @param mean Mean (μ) of the distribution.
2183
+ * @param stdev Standard deviation (σ) of the distribution (its absolute value is used).
2184
+ */
1917
2185
  constructor(mean, stdev) {
1918
2186
  this.mean = mean;
1919
2187
  this.stdev = Math.abs(stdev);
1920
2188
  this.variance = Math.pow(stdev, 2);
1921
2189
  }
2190
+ /** The standard normal distribution (mean 0, standard deviation 1). */
1922
2191
  static get normal() {
1923
2192
  return _normal;
1924
2193
  }
@@ -1955,7 +2224,13 @@
1955
2224
  const _normal = new Gaussian(0, 1);
1956
2225
  //}
1957
2226
 
2227
+ /**
2228
+ * Lagrange polynomial interpolation, evaluated using the numerically stable barycentric form.
2229
+ * Given a set of `(x, y)` points, builds the unique polynomial of minimal degree passing through
2230
+ * all of them and evaluates it at arbitrary x values.
2231
+ */
1958
2232
  class Lagrange {
2233
+ /** Use {@link Lagrange.create} to build an instance. */
1959
2234
  constructor(points) {
1960
2235
  this.#set = points;
1961
2236
  }
@@ -2031,6 +2306,11 @@
2031
2306
  }
2032
2307
  // Iterative/global Lagrange computation removed — barycentric is used
2033
2308
  // exclusively for interpolation.
2309
+ /**
2310
+ * Evaluates the Lagrange interpolating polynomial at the given x, using barycentric weights.
2311
+ * @param x Input value.
2312
+ * @returns The interpolated y value (the exact node y if x coincides with a node).
2313
+ */
2034
2314
  interpolate(x) {
2035
2315
  return this._computeBarycentric(x);
2036
2316
  }
@@ -2052,7 +2332,14 @@
2052
2332
  const Lagrangian = {
2053
2333
  create: Lagrange.create
2054
2334
  };
2335
+ /**
2336
+ * Piecewise Cubic Hermite Interpolating Polynomial (PCHIP).
2337
+ * Builds a shape-preserving (monotonicity-respecting) piecewise-cubic curve through a set of
2338
+ * `(x, y)` points: unlike {@link Lagrange}, it does not overshoot between nodes, at the cost of
2339
+ * only being C¹ (continuous first derivative, not second).
2340
+ */
2055
2341
  class Pchip {
2342
+ /** Use {@link Pchip.create} to build an instance. */
2056
2343
  constructor(points) {
2057
2344
  if (!points || points.length < 2)
2058
2345
  throw new Error('Need at least 2 points');
@@ -2104,6 +2391,12 @@
2104
2391
  }
2105
2392
  return m;
2106
2393
  }
2394
+ /**
2395
+ * Evaluates the PCHIP curve at the given x, using cubic Hermite interpolation on the enclosing segment.
2396
+ * Values outside the node range are linearly extrapolated using the boundary derivative.
2397
+ * @param x Input value.
2398
+ * @returns The interpolated y value.
2399
+ */
2107
2400
  interpolate(x) {
2108
2401
  const xs = this.xs, ys = this.ys, ms = this.ms;
2109
2402
  const n = xs.length;
@@ -2148,7 +2441,14 @@
2148
2441
  return new Pchip(points);
2149
2442
  }
2150
2443
  }
2444
+ /**
2445
+ * Newton's divided-difference polynomial interpolation.
2446
+ * Builds the same interpolating polynomial as {@link Lagrange} but represents it in Newton form
2447
+ * (a nested/Horner-like product of divided differences), which is efficient to evaluate and to
2448
+ * extend incrementally as new points are added.
2449
+ */
2151
2450
  class Newton {
2451
+ /** Use {@link Newton.create} to build an instance. */
2152
2452
  constructor(points) {
2153
2453
  if (!points || points.length === 0)
2154
2454
  throw new Error('Need at least 1 point');
@@ -2167,6 +2467,11 @@
2167
2467
  }
2168
2468
  this.coeffs = a;
2169
2469
  }
2470
+ /**
2471
+ * Evaluates the Newton form of the interpolating polynomial at the given x, via nested multiplication.
2472
+ * @param x Input value.
2473
+ * @returns The interpolated y value (`NaN` if the interpolator has no points).
2474
+ */
2170
2475
  interpolate(x) {
2171
2476
  const xs = this.xs, a = this.coeffs;
2172
2477
  const n = a.length;
@@ -2386,6 +2691,7 @@
2386
2691
  const fn = searchFnFactory(minAssigner, maxAssigner);
2387
2692
  return fn(left, right, weight, tolerance);
2388
2693
  };
2694
+ /** Ready-made {@link SearchFunction} implementations for unimodal minimum-search over an interval. */
2389
2695
  const SearchFunctions = {
2390
2696
  /**
2391
2697
  * Creates a linear search function.
@@ -2416,6 +2722,12 @@
2416
2722
  const OUT_OF_RANGE = `Radix out of range: possible values go between positive 1 exclusive and ${ALPHABET.length} inclusive`;
2417
2723
  /** Utility tools about number theory, 'diophantine' stuff... */
2418
2724
  class Utils {
2725
+ /**
2726
+ * Computes the least common multiple (LCM) of the given numbers (each rounded to the nearest integer).
2727
+ * @param args Two or more numbers.
2728
+ * @returns The least common multiple.
2729
+ * @throws If fewer than two numbers are provided.
2730
+ */
2419
2731
  static lcd(...args) {
2420
2732
  if (pacemFoundation.NullChecker.isNullOrEmpty(args) || args.length <= 1) {
2421
2733
  throw 'Insufficient set of numbers.';
@@ -2434,6 +2746,12 @@
2434
2746
  }
2435
2747
  return result;
2436
2748
  }
2749
+ /**
2750
+ * Computes the greatest common divisor (GCD) of two numbers (each rounded to the nearest integer)
2751
+ * using the Euclidean algorithm.
2752
+ * @param a
2753
+ * @param b
2754
+ */
2437
2755
  static gcd(a, b) {
2438
2756
  // force integers
2439
2757
  a = Math.round(a),
@@ -2443,9 +2761,26 @@
2443
2761
  }
2444
2762
  return Utils.gcd(b % a, a);
2445
2763
  }
2764
+ /**
2765
+ * Converts an integer value from one numeric base (radix) to another.
2766
+ * @param v Value to convert, in the source radix.
2767
+ * @param from Source radix.
2768
+ * @param to Target radix.
2769
+ * @returns The value re-expressed in the target radix, as a string.
2770
+ */
2446
2771
  static rebaseInt(v, from, to) {
2447
2772
  return pacemFoundation.Numbers.rebase(v, from, to);
2448
2773
  }
2774
+ /**
2775
+ * Converts a (possibly fractional) numeric value from one radix to another, dispatching to the
2776
+ * appropriate specialized conversion routine depending on whether base-10 is the source, the
2777
+ * target, or neither.
2778
+ * @param v Value to convert, in the source radix.
2779
+ * @param fromRadix Source radix.
2780
+ * @param toRadix Target radix.
2781
+ * @param precision Maximum number of digits to compute for the fractional part (default 12).
2782
+ * @returns The converted value: a number when converting to base 10, otherwise a string.
2783
+ */
2449
2784
  static rebaseFloat(v, fromRadix, toRadix, precision = 12) {
2450
2785
  const from10 = Utils.rebaseFloat10ToN, to10 = Utils.rebaseFloatNTo10;
2451
2786
  if (fromRadix === 10) {
@@ -2461,6 +2796,14 @@
2461
2796
  const _10 = to10(v.toString(), fromRadix, precision + 1);
2462
2797
  return from10(_10, toRadix, precision);
2463
2798
  }
2799
+ /**
2800
+ * Converts a numeric string expressed in the given radix into its base-10 (decimal) value.
2801
+ * @param v Value to convert, expressed in the source radix (digits from `0-9a-z`).
2802
+ * @param radix Source radix (must be greater than 1 and no larger than the size of the digit alphabet).
2803
+ * @param precision Unused for the integer part; reserved for symmetry with the sibling conversion methods.
2804
+ * @returns The base-10 numeric value, or `NaN` if the input contains invalid digits.
2805
+ * @throws If radix is out of range.
2806
+ */
2464
2807
  static rebaseFloatNTo10(v, radix, precision = 12) {
2465
2808
  const alphabet = ALPHABET;
2466
2809
  if (radix <= 1 || radix > alphabet.length) {
@@ -2486,6 +2829,14 @@
2486
2829
  }
2487
2830
  return sign * retval;
2488
2831
  }
2832
+ /**
2833
+ * Converts a base-10 number into its representation in another **integer** radix.
2834
+ * @param v Base-10 value to convert.
2835
+ * @param radix Target integer radix (must be greater than 1 and no larger than the size of the digit alphabet).
2836
+ * @param precision Maximum number of digits to compute for the fractional part (default 12).
2837
+ * @returns The value re-expressed in the target radix, as a string.
2838
+ * @throws If radix is out of range.
2839
+ */
2489
2840
  static rebaseFloat10ToNIntBase(v, radix, precision = 12) {
2490
2841
  const alphabet = ALPHABET;
2491
2842
  if (radix <= 1 || radix > alphabet.length) {
@@ -2540,6 +2891,14 @@
2540
2891
  }
2541
2892
  return (Math.sign(v) < 0 ? '-' : '') + output + '.' + rebasedFrac.replace(/0+$/, '');
2542
2893
  }
2894
+ /**
2895
+ * Converts a base-10 number into its representation in another (possibly non-integer) radix.
2896
+ * @param v Base-10 value to convert.
2897
+ * @param radix Target radix, possibly fractional (must be greater than 1 and no larger than the size of the digit alphabet).
2898
+ * @param precision Maximum number of digits to compute for the fractional part (default 12).
2899
+ * @returns The value re-expressed in the target radix, as a string.
2900
+ * @throws If radix is out of range.
2901
+ */
2543
2902
  static rebaseFloat10ToN(v, radix, precision = 12) {
2544
2903
  const alphabet = ALPHABET;
2545
2904
  if (radix <= 1 || radix > alphabet.length) {