@sachitv/safe-math-ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,361 @@
1
+ import { quantity, } from '../units.js';
2
+ /**
3
+ * Casts a raw number into a branded quantity.
4
+ *
5
+ * @param value Raw numeric scalar.
6
+ * @returns Branded quantity.
7
+ */
8
+ const asQuantity = (value) => value;
9
+ /**
10
+ * Casts xyz components to a branded displacement tuple.
11
+ *
12
+ * @param x X component.
13
+ * @param y Y component.
14
+ * @param z Z component.
15
+ * @returns Branded displacement.
16
+ */
17
+ const asDelta3 = (x, y, z) => [x, y, z];
18
+ /**
19
+ * Casts xyz components to a branded point tuple.
20
+ *
21
+ * @param x X component.
22
+ * @param y Y component.
23
+ * @param z Z component.
24
+ * @returns Branded point.
25
+ */
26
+ const asPoint3 = (x, y, z) => [x, y, z];
27
+ /**
28
+ * Casts xyz components to a branded direction tuple.
29
+ *
30
+ * @param x X component.
31
+ * @param y Y component.
32
+ * @param z Z component.
33
+ * @returns Branded direction.
34
+ */
35
+ const asDir3 = (x, y, z) => [x, y, z];
36
+ /**
37
+ * Constructs a frame-aware displacement vector.
38
+ *
39
+ * @param frameTag Frame token for the resulting displacement.
40
+ * @param x X component.
41
+ * @param y Y component.
42
+ * @param z Z component.
43
+ * @returns Displacement in `frameTag`.
44
+ */
45
+ export const delta3 = (frameTag, x, y, z) => {
46
+ void frameTag;
47
+ return asDelta3(x, y, z);
48
+ };
49
+ /**
50
+ * Constructs a frame-aware point.
51
+ *
52
+ * @param frameTag Frame token for the resulting point.
53
+ * @param x X component.
54
+ * @param y Y component.
55
+ * @param z Z component.
56
+ * @returns Point in `frameTag`.
57
+ */
58
+ export const point3 = (frameTag, x, y, z) => {
59
+ void frameTag;
60
+ return asPoint3(x, y, z);
61
+ };
62
+ /**
63
+ * Constructs a frame-aware direction (dimensionless).
64
+ *
65
+ * @param frameTag Frame token for the resulting direction.
66
+ * @param x X component.
67
+ * @param y Y component.
68
+ * @param z Z component.
69
+ * @returns Direction in `frameTag`.
70
+ */
71
+ export const dir3 = (frameTag, x, y, z) => {
72
+ void frameTag;
73
+ return asDir3(x, y, z);
74
+ };
75
+ /**
76
+ * Constructs a zero displacement for an explicit unit and frame.
77
+ *
78
+ * @param unitTag Unit token for the displacement components.
79
+ * @param frameTag Frame token for the displacement.
80
+ * @returns Zero displacement in the provided unit and frame.
81
+ */
82
+ export const zeroVec3 = (unitTag, frameTag) => delta3(frameTag, quantity(unitTag, 0), quantity(unitTag, 0), quantity(unitTag, 0));
83
+ /**
84
+ * Adds two displacements with matching unit and frame.
85
+ *
86
+ * @param left Left displacement.
87
+ * @param right Right displacement in the same unit/frame.
88
+ * @returns Component-wise sum.
89
+ */
90
+ export const addVec3 = (left, right) => {
91
+ const x = asQuantity(left[0] + right[0]);
92
+ const y = asQuantity(left[1] + right[1]);
93
+ const z = asQuantity(left[2] + right[2]);
94
+ return asDelta3(x, y, z);
95
+ };
96
+ /**
97
+ * Subtracts two displacements with matching unit and frame.
98
+ *
99
+ * @param left Left displacement.
100
+ * @param right Right displacement in the same unit/frame.
101
+ * @returns Component-wise difference.
102
+ */
103
+ export const subVec3 = (left, right) => {
104
+ const x = asQuantity(left[0] - right[0]);
105
+ const y = asQuantity(left[1] - right[1]);
106
+ const z = asQuantity(left[2] - right[2]);
107
+ return asDelta3(x, y, z);
108
+ };
109
+ /**
110
+ * Negates each displacement component.
111
+ *
112
+ * @param value Displacement to negate.
113
+ * @returns Negated displacement.
114
+ */
115
+ export const negVec3 = (value) => asDelta3(asQuantity(-value[0]), asQuantity(-value[1]), asQuantity(-value[2]));
116
+ /**
117
+ * Multiplies each displacement component by a unitless scalar.
118
+ *
119
+ * @param value Displacement to scale.
120
+ * @param scalar Unitless multiplier.
121
+ * @returns Scaled displacement.
122
+ */
123
+ export const scaleVec3 = (value, scalar) => {
124
+ const x = asQuantity(value[0] * scalar);
125
+ const y = asQuantity(value[1] * scalar);
126
+ const z = asQuantity(value[2] * scalar);
127
+ return asDelta3(x, y, z);
128
+ };
129
+ /**
130
+ * Scales a unitless direction by a unitful magnitude.
131
+ *
132
+ * @param value Direction vector.
133
+ * @param magnitude Unitful scalar magnitude.
134
+ * @returns Displacement with unit derived from `magnitude`.
135
+ */
136
+ export const scaleDir3 = (value, magnitude) => asDelta3(asQuantity(value[0] * magnitude), asQuantity(value[1] * magnitude), asQuantity(value[2] * magnitude));
137
+ /**
138
+ * Translates a point by a displacement.
139
+ *
140
+ * @param point Input point.
141
+ * @param delta Translation displacement in the same frame/unit.
142
+ * @returns Translated point.
143
+ */
144
+ export const addPoint3 = (point, delta) => asPoint3(asQuantity(point[0] + delta[0]), asQuantity(point[1] + delta[1]), asQuantity(point[2] + delta[2]));
145
+ /**
146
+ * Offsets a point by subtracting a displacement.
147
+ *
148
+ * @param point Input point.
149
+ * @param delta Displacement to subtract.
150
+ * @returns Offset point.
151
+ */
152
+ export const subPoint3Delta3 = (point, delta) => asPoint3(asQuantity(point[0] - delta[0]), asQuantity(point[1] - delta[1]), asQuantity(point[2] - delta[2]));
153
+ /**
154
+ * Computes the displacement from `right` point to `left` point.
155
+ *
156
+ * @param left Destination point.
157
+ * @param right Source point.
158
+ * @returns Displacement that moves `right` to `left`.
159
+ */
160
+ export const subPoint3 = (left, right) => asDelta3(asQuantity(left[0] - right[0]), asQuantity(left[1] - right[1]), asQuantity(left[2] - right[2]));
161
+ /**
162
+ * Computes dot product for two displacements/directions in the same frame.
163
+ *
164
+ * @param left Left vector.
165
+ * @param right Right vector in the same frame.
166
+ * @returns Scalar product with multiplied unit.
167
+ */
168
+ export const dotVec3 = (left, right) => {
169
+ const xx = left[0] * right[0];
170
+ const yy = left[1] * right[1];
171
+ const zz = left[2] * right[2];
172
+ const dot = xx + yy + zz;
173
+ return asQuantity(dot);
174
+ };
175
+ /**
176
+ * Computes cross product for two displacements/directions in the same frame.
177
+ *
178
+ * @param left Left vector.
179
+ * @param right Right vector in the same frame.
180
+ * @returns Cross-product vector with multiplied unit.
181
+ */
182
+ export const crossVec3 = (left, right) => {
183
+ const x = asQuantity(left[1] * right[2] - left[2] * right[1]);
184
+ const y = asQuantity(left[2] * right[0] - left[0] * right[2]);
185
+ const z = asQuantity(left[0] * right[1] - left[1] * right[0]);
186
+ return asDelta3(x, y, z);
187
+ };
188
+ /**
189
+ * Computes squared Euclidean length of a displacement/direction.
190
+ *
191
+ * @param value Input vector.
192
+ * @returns Squared length.
193
+ */
194
+ export const lengthSquaredVec3 = (value) => dotVec3(value, value);
195
+ /**
196
+ * Computes Euclidean length of a displacement/direction.
197
+ *
198
+ * @param value Input vector.
199
+ * @returns Vector length.
200
+ */
201
+ export const lengthVec3 = (value) => asQuantity(Math.hypot(value[0], value[1], value[2]));
202
+ export function distanceVec3(left, right) {
203
+ const dx = left[0] - right[0];
204
+ const dy = left[1] - right[1];
205
+ const dz = left[2] - right[2];
206
+ const distance = Math.hypot(dx, dy, dz);
207
+ return asQuantity(distance);
208
+ }
209
+ /**
210
+ * Computes Euclidean distance between two points.
211
+ *
212
+ * @param left Left point.
213
+ * @param right Right point in the same unit/frame.
214
+ * @returns Distance magnitude.
215
+ */
216
+ export const distancePoint3 = (left, right) => distanceVec3(left, right);
217
+ const NEAR_ZERO = 1e-14;
218
+ /**
219
+ * Normalizes displacement length to 1.
220
+ *
221
+ * Unsafe variant: performs no zero-length guard.
222
+ * Degenerate inputs can yield `NaN`/`Infinity`.
223
+ *
224
+ * @param value Vector to normalize.
225
+ * @returns Unit-length direction in the same frame.
226
+ */
227
+ export const normalizeVec3Unsafe = (value) => {
228
+ const magnitude = lengthVec3(value);
229
+ return asDir3(asQuantity(value[0] / magnitude), asQuantity(value[1] / magnitude), asQuantity(value[2] / magnitude));
230
+ };
231
+ /**
232
+ * Normalizes displacement length to 1.
233
+ *
234
+ * Throws when vector length is at or below `1e-14`.
235
+ *
236
+ * @param value Vector to normalize.
237
+ * @returns Unit-length direction in the same frame.
238
+ * @throws {Error} When the vector is near zero length.
239
+ */
240
+ export const normalizeVec3 = (value) => {
241
+ const magnitude = lengthVec3(value);
242
+ if (magnitude <= NEAR_ZERO) {
243
+ throw new Error('Cannot normalize a zero-length vector');
244
+ }
245
+ return normalizeVec3Unsafe(value);
246
+ };
247
+ export function lerpVec3(start, end, t) {
248
+ const inverseT = 1 - t;
249
+ const x = start[0] * inverseT +
250
+ end[0] * t;
251
+ const y = start[1] * inverseT +
252
+ end[1] * t;
253
+ const z = start[2] * inverseT +
254
+ end[2] * t;
255
+ return [
256
+ asQuantity(x),
257
+ asQuantity(y),
258
+ asQuantity(z),
259
+ ];
260
+ }
261
+ /**
262
+ * Projects a displacement onto another displacement in the same frame.
263
+ *
264
+ * Unsafe variant: performs no zero-length guard for `onto`.
265
+ *
266
+ * @param value Vector being projected.
267
+ * @param onto Target direction for projection.
268
+ * @returns Projection of `value` onto `onto`.
269
+ */
270
+ export const projectVec3Unsafe = (value, onto) => {
271
+ const ontoLengthSquared = onto[0] * onto[0] +
272
+ onto[1] * onto[1] +
273
+ onto[2] * onto[2];
274
+ const dotValueOnto = value[0] * onto[0] +
275
+ value[1] * onto[1] +
276
+ value[2] * onto[2];
277
+ const scalar = dotValueOnto / ontoLengthSquared;
278
+ return asDelta3(asQuantity(onto[0] * scalar), asQuantity(onto[1] * scalar), asQuantity(onto[2] * scalar));
279
+ };
280
+ /**
281
+ * Projects a displacement onto another displacement in the same frame.
282
+ *
283
+ * @param value Vector being projected.
284
+ * @param onto Target direction for projection.
285
+ * @returns Projection of `value` onto `onto`.
286
+ * @throws {Error} When `onto` is near zero length.
287
+ */
288
+ export const projectVec3 = (value, onto) => {
289
+ const ontoLengthSquared = onto[0] * onto[0] +
290
+ onto[1] * onto[1] +
291
+ onto[2] * onto[2];
292
+ if (ontoLengthSquared <= NEAR_ZERO * NEAR_ZERO) {
293
+ throw new Error('Cannot project onto a zero-length vector');
294
+ }
295
+ return projectVec3Unsafe(value, onto);
296
+ };
297
+ /**
298
+ * Reflects a displacement around a normal direction.
299
+ *
300
+ * Unsafe variant: performs no zero-length guard for `normal`.
301
+ *
302
+ * @param incident Incident displacement.
303
+ * @param normal Reflection normal direction.
304
+ * @returns Reflected displacement.
305
+ */
306
+ export const reflectVec3Unsafe = (incident, normal) => {
307
+ const dir_normalized = normalizeVec3Unsafe(normal);
308
+ const dotIncidentNormal = incident[0] * dir_normalized[0] +
309
+ incident[1] * dir_normalized[1] +
310
+ incident[2] * dir_normalized[2];
311
+ const scale = 2 * dotIncidentNormal;
312
+ return asDelta3(asQuantity(incident[0] - dir_normalized[0] * scale), asQuantity(incident[1] - dir_normalized[1] * scale), asQuantity(incident[2] - dir_normalized[2] * scale));
313
+ };
314
+ /**
315
+ * Reflects a displacement around a normal direction.
316
+ *
317
+ * @param incident Incident displacement.
318
+ * @param normal Reflection normal direction.
319
+ * @returns Reflected displacement.
320
+ * @throws {Error} When `normal` is near zero length.
321
+ */
322
+ export const reflectVec3 = (incident, normal) => {
323
+ normalizeVec3(normal);
324
+ return reflectVec3Unsafe(incident, normal);
325
+ };
326
+ /**
327
+ * Computes the angle in radians between two displacements.
328
+ *
329
+ * Unsafe variant: performs no zero-length guards.
330
+ *
331
+ * @param left Left vector.
332
+ * @param right Right vector.
333
+ * @returns Angle in radians.
334
+ */
335
+ export const angleBetweenVec3Unsafe = (left, right) => {
336
+ const leftLength = Math.hypot(left[0], left[1], left[2]);
337
+ const rightLength = Math.hypot(right[0], right[1], right[2]);
338
+ const dotLeftRight = left[0] * right[0] +
339
+ left[1] * right[1] +
340
+ left[2] * right[2];
341
+ const lengthProduct = leftLength * rightLength;
342
+ const cosine = dotLeftRight / lengthProduct;
343
+ const clamped = Math.max(-1, Math.min(1, cosine));
344
+ return Math.acos(clamped);
345
+ };
346
+ /**
347
+ * Computes the angle in radians between two non-zero displacements.
348
+ *
349
+ * @param left Left vector.
350
+ * @param right Right vector.
351
+ * @returns Angle in radians.
352
+ * @throws {Error} When either vector is near zero length.
353
+ */
354
+ export const angleBetweenVec3 = (left, right) => {
355
+ const leftLength = Math.hypot(left[0], left[1], left[2]);
356
+ const rightLength = Math.hypot(right[0], right[1], right[2]);
357
+ if (leftLength <= NEAR_ZERO || rightLength <= NEAR_ZERO) {
358
+ throw new Error('Cannot compute angle with a zero-length vector');
359
+ }
360
+ return angleBetweenVec3Unsafe(left, right);
361
+ };