@js-draw/math 1.18.0 → 1.21.1
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/LICENSE +1 -1
- package/dist/cjs/Mat33.d.ts +91 -0
- package/dist/cjs/Mat33.js +88 -0
- package/dist/cjs/Vec2.d.ts +3 -40
- package/dist/cjs/Vec2.js +8 -46
- package/dist/cjs/Vec3.d.ts +112 -16
- package/dist/cjs/Vec3.js +184 -136
- package/dist/cjs/shapes/LineSegment2.d.ts +13 -2
- package/dist/cjs/shapes/LineSegment2.js +13 -2
- package/dist/cjs/shapes/PointShape2D.d.ts +33 -1
- package/dist/cjs/shapes/Rect2.d.ts +35 -3
- package/dist/cjs/shapes/Rect2.js +3 -3
- package/dist/mjs/Mat33.d.ts +91 -0
- package/dist/mjs/Mat33.mjs +88 -0
- package/dist/mjs/Vec2.d.ts +3 -40
- package/dist/mjs/Vec2.mjs +6 -42
- package/dist/mjs/Vec3.d.ts +112 -16
- package/dist/mjs/Vec3.mjs +183 -134
- package/dist/mjs/shapes/LineSegment2.d.ts +13 -2
- package/dist/mjs/shapes/LineSegment2.mjs +13 -2
- package/dist/mjs/shapes/PointShape2D.d.ts +33 -1
- package/dist/mjs/shapes/Rect2.d.ts +35 -3
- package/dist/mjs/shapes/Rect2.mjs +3 -3
- package/dist-test/test_imports/yarn.lock +12 -0
- package/package.json +6 -7
- package/src/Mat33.ts +92 -1
- package/src/Vec2.test.ts +5 -3
- package/src/Vec2.ts +7 -47
- package/src/Vec3.ts +408 -121
- package/src/shapes/BezierJSWrapper.ts +1 -1
- package/src/shapes/LineSegment2.ts +13 -2
- package/src/shapes/Rect2.ts +3 -3
- package/dist-test/test_imports/package-lock.json +0 -13
package/src/Vec3.ts
CHANGED
@@ -19,49 +19,25 @@
|
|
19
19
|
* console.log('As an array:', Vec3.unitZ.asArray());
|
20
20
|
* ```
|
21
21
|
*/
|
22
|
-
export
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
public readonly z: number
|
27
|
-
) {
|
28
|
-
}
|
29
|
-
|
30
|
-
/** Returns the x, y components of this. */
|
31
|
-
public get xy(): { x: number; y: number } {
|
32
|
-
// Useful for APIs that behave differently if .z is present.
|
33
|
-
return {
|
34
|
-
x: this.x,
|
35
|
-
y: this.y,
|
36
|
-
};
|
37
|
-
}
|
22
|
+
export interface Vec3 {
|
23
|
+
readonly x: number;
|
24
|
+
readonly y: number;
|
25
|
+
readonly z: number;
|
38
26
|
|
39
|
-
/**
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
/** Returns this' `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
|
45
|
-
public at(idx: number): number {
|
46
|
-
if (idx === 0) return this.x;
|
47
|
-
if (idx === 1) return this.y;
|
48
|
-
if (idx === 2) return this.z;
|
49
|
-
|
50
|
-
throw new Error(`${idx} out of bounds!`);
|
51
|
-
}
|
52
|
-
|
53
|
-
/** Alias for this.magnitude. */
|
54
|
-
public length(): number {
|
55
|
-
return this.magnitude();
|
56
|
-
}
|
27
|
+
/**
|
28
|
+
* Returns the x, y components of this.
|
29
|
+
* May be implemented as a getter method.
|
30
|
+
*/
|
31
|
+
readonly xy: { x: number, y: number };
|
57
32
|
|
58
|
-
|
59
|
-
|
60
|
-
}
|
33
|
+
/** Returns the vector's `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
|
34
|
+
at(i: number): number;
|
61
35
|
|
62
|
-
|
63
|
-
|
64
|
-
|
36
|
+
/** Alias for `.magnitude`. */
|
37
|
+
length(): number;
|
38
|
+
/** Returns the length of this vector in ℝ^3. */
|
39
|
+
magnitude(): number;
|
40
|
+
magnitudeSquared(): number;
|
65
41
|
|
66
42
|
/**
|
67
43
|
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
@@ -69,12 +45,7 @@ export class Vec3 {
|
|
69
45
|
*
|
70
46
|
* Equivalent to `.minus(p).magnitudeSquared()`.
|
71
47
|
*/
|
72
|
-
|
73
|
-
const dx = this.x - p.x;
|
74
|
-
const dy = this.y - p.y;
|
75
|
-
const dz = this.z - p.z;
|
76
|
-
return dx * dx + dy * dy + dz * dz;
|
77
|
-
}
|
48
|
+
squareDistanceTo(other: Vec3): number;
|
78
49
|
|
79
50
|
/**
|
80
51
|
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
@@ -82,9 +53,7 @@ export class Vec3 {
|
|
82
53
|
*
|
83
54
|
* Equivalent to `.minus(p).magnitude()`.
|
84
55
|
*/
|
85
|
-
|
86
|
-
return Math.sqrt(this.squareDistanceTo(p));
|
87
|
-
}
|
56
|
+
distanceTo(p: Vec3): number;
|
88
57
|
|
89
58
|
/**
|
90
59
|
* Returns the entry of this with the greatest magnitude.
|
@@ -98,9 +67,7 @@ export class Vec3 {
|
|
98
67
|
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
99
68
|
* ```
|
100
69
|
*/
|
101
|
-
|
102
|
-
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
103
|
-
}
|
70
|
+
maximumEntryMagnitude(): number;
|
104
71
|
|
105
72
|
/**
|
106
73
|
* Return this' angle in the XY plane (treats this as a Vec2).
|
@@ -117,23 +84,175 @@ export class Vec3 {
|
|
117
84
|
* console.log(Vec2.of(-1, 0).angle()); // atan2(0, -1)
|
118
85
|
* ```
|
119
86
|
*/
|
120
|
-
|
121
|
-
return Math.atan2(this.y, this.x);
|
122
|
-
}
|
87
|
+
angle(): number;
|
123
88
|
|
124
89
|
/**
|
125
90
|
* Returns a unit vector in the same direction as this.
|
126
91
|
*
|
127
92
|
* If `this` has zero length, the resultant vector has `NaN` components.
|
128
93
|
*/
|
94
|
+
normalized(): Vec3;
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Like {@link normalized}, except returns zero if this has zero magnitude.
|
98
|
+
*/
|
99
|
+
normalizedOrZero(): Vec3;
|
100
|
+
|
101
|
+
/** @returns A copy of `this` multiplied by a scalar. */
|
102
|
+
times(c: number): Vec3;
|
103
|
+
|
104
|
+
/** Performs vector addition. */
|
105
|
+
plus(v: Vec3): Vec3;
|
106
|
+
minus(v: Vec3): Vec3;
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Computes the scalar product between this and `v`.
|
110
|
+
*
|
111
|
+
* In particular, `a.dot(b)` is equivalent to `a.x * b.x + a.y * b.y + a.z * b.z`.
|
112
|
+
*/
|
113
|
+
dot(v: Vec3): number;
|
114
|
+
|
115
|
+
/** Computes the cross product between this and `v` */
|
116
|
+
cross(v: Vec3): Vec3;
|
117
|
+
|
118
|
+
/**
|
119
|
+
* If `other` is a `Vec3`, multiplies `this` component-wise by `other`. Otherwise,
|
120
|
+
* if `other is a `number`, returns the result of scalar multiplication.
|
121
|
+
*
|
122
|
+
* @example
|
123
|
+
* ```
|
124
|
+
* Vec3.of(1, 2, 3).scale(Vec3.of(2, 4, 6)); // → Vec3(2, 8, 18)
|
125
|
+
* ```
|
126
|
+
*/
|
127
|
+
scale(other: Vec3|number): Vec3;
|
128
|
+
|
129
|
+
/**
|
130
|
+
* Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
|
131
|
+
* 90 degrees counter-clockwise.
|
132
|
+
*/
|
133
|
+
orthog(): Vec3;
|
134
|
+
|
135
|
+
/** Returns this plus a vector of length `distance` in `direction`. */
|
136
|
+
extend(distance: number, direction: Vec3): Vec3;
|
137
|
+
|
138
|
+
/** Returns a vector `fractionTo` of the way to target from this. */
|
139
|
+
lerp(target: Vec3, fractionTo: number): Vec3;
|
140
|
+
|
141
|
+
/**
|
142
|
+
* `zip` Maps a component of this and a corresponding component of
|
143
|
+
* `other` to a component of the output vector.
|
144
|
+
*
|
145
|
+
* @example
|
146
|
+
* ```
|
147
|
+
* const a = Vec3.of(1, 2, 3);
|
148
|
+
* const b = Vec3.of(0.5, 2.1, 2.9);
|
149
|
+
*
|
150
|
+
* const zipped = a.zip(b, (aComponent, bComponent) => {
|
151
|
+
* return Math.min(aComponent, bComponent);
|
152
|
+
* });
|
153
|
+
*
|
154
|
+
* console.log(zipped.toString()); // → Vec(0.5, 2, 2.9)
|
155
|
+
* ```
|
156
|
+
*/
|
157
|
+
zip(
|
158
|
+
other: Vec3, zip: (componentInThis: number, componentInOther: number)=> number
|
159
|
+
): Vec3;
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Returns a vector with each component acted on by `fn`.
|
163
|
+
*
|
164
|
+
* @example
|
165
|
+
* ```ts,runnable,console
|
166
|
+
* import { Vec3 } from '@js-draw/math';
|
167
|
+
* console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
|
168
|
+
* ```
|
169
|
+
*/
|
170
|
+
map(fn: (component: number, index: number)=> number): Vec3;
|
171
|
+
|
172
|
+
asArray(): [ number, number, number ];
|
173
|
+
|
174
|
+
|
175
|
+
/**
|
176
|
+
* [fuzz] The maximum difference between two components for this and [other]
|
177
|
+
* to be considered equal.
|
178
|
+
*
|
179
|
+
* @example
|
180
|
+
* ```
|
181
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 100); // → true
|
182
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 0.1); // → false
|
183
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3); // → true
|
184
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3.01); // → true
|
185
|
+
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
186
|
+
* ```
|
187
|
+
*/
|
188
|
+
eq(other: Vec3, tolerance?: number): boolean;
|
189
|
+
|
190
|
+
toString(): string;
|
191
|
+
}
|
192
|
+
|
193
|
+
const defaultEqlTolerance = 1e-10;
|
194
|
+
|
195
|
+
class Vec3Impl implements Vec3 {
|
196
|
+
public constructor(
|
197
|
+
public readonly x: number,
|
198
|
+
public readonly y: number,
|
199
|
+
public readonly z: number
|
200
|
+
) {
|
201
|
+
}
|
202
|
+
|
203
|
+
public get xy(): { x: number; y: number } {
|
204
|
+
// Useful for APIs that behave differently if .z is present.
|
205
|
+
return {
|
206
|
+
x: this.x,
|
207
|
+
y: this.y,
|
208
|
+
};
|
209
|
+
}
|
210
|
+
|
211
|
+
/** Returns this' `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
|
212
|
+
public at(idx: number): number {
|
213
|
+
if (idx === 0) return this.x;
|
214
|
+
if (idx === 1) return this.y;
|
215
|
+
if (idx === 2) return this.z;
|
216
|
+
|
217
|
+
throw new Error(`${idx} out of bounds!`);
|
218
|
+
}
|
219
|
+
|
220
|
+
public length(): number {
|
221
|
+
return this.magnitude();
|
222
|
+
}
|
223
|
+
|
224
|
+
public magnitude(): number {
|
225
|
+
return Math.sqrt(this.magnitudeSquared());
|
226
|
+
}
|
227
|
+
|
228
|
+
public magnitudeSquared(): number {
|
229
|
+
return this.x * this.x + this.y * this.y + this.z * this.z;
|
230
|
+
}
|
231
|
+
|
232
|
+
public squareDistanceTo(p: Vec3) {
|
233
|
+
const dx = this.x - p.x;
|
234
|
+
const dy = this.y - p.y;
|
235
|
+
const dz = this.z - p.z;
|
236
|
+
return dx * dx + dy * dy + dz * dz;
|
237
|
+
}
|
238
|
+
|
239
|
+
public distanceTo(p: Vec3) {
|
240
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
241
|
+
}
|
242
|
+
|
243
|
+
public maximumEntryMagnitude(): number {
|
244
|
+
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
245
|
+
}
|
246
|
+
|
247
|
+
public angle(): number {
|
248
|
+
return Math.atan2(this.y, this.x);
|
249
|
+
}
|
250
|
+
|
129
251
|
public normalized(): Vec3 {
|
130
252
|
const norm = this.magnitude();
|
131
253
|
return Vec3.of(this.x / norm, this.y / norm, this.z / norm);
|
132
254
|
}
|
133
255
|
|
134
|
-
/**
|
135
|
-
* Like {@link normalized}, except returns zero if this has zero magnitude.
|
136
|
-
*/
|
137
256
|
public normalizedOrZero(): Vec3 {
|
138
257
|
if (this.eq(Vec3.zero)) {
|
139
258
|
return Vec3.zero;
|
@@ -142,7 +261,6 @@ export class Vec3 {
|
|
142
261
|
return this.normalized();
|
143
262
|
}
|
144
263
|
|
145
|
-
/** @returns A copy of `this` multiplied by a scalar. */
|
146
264
|
public times(c: number): Vec3 {
|
147
265
|
return Vec3.of(this.x * c, this.y * c, this.z * c);
|
148
266
|
}
|
@@ -166,19 +284,10 @@ export class Vec3 {
|
|
166
284
|
return Vec3.of(
|
167
285
|
this.y * other.z - other.y * this.z,
|
168
286
|
other.x * this.z - this.x * other.z,
|
169
|
-
this.x * other.y - other.x * this.y
|
287
|
+
this.x * other.y - other.x * this.y,
|
170
288
|
);
|
171
289
|
}
|
172
290
|
|
173
|
-
/**
|
174
|
-
* If `other` is a `Vec3`, multiplies `this` component-wise by `other`. Otherwise,
|
175
|
-
* if `other is a `number`, returns the result of scalar multiplication.
|
176
|
-
*
|
177
|
-
* @example
|
178
|
-
* ```
|
179
|
-
* Vec3.of(1, 2, 3).scale(Vec3.of(2, 4, 6)); // → Vec3(2, 8, 18)
|
180
|
-
* ```
|
181
|
-
*/
|
182
291
|
public scale(other: Vec3|number): Vec3 {
|
183
292
|
if (typeof other === 'number') {
|
184
293
|
return this.times(other);
|
@@ -191,10 +300,6 @@ export class Vec3 {
|
|
191
300
|
);
|
192
301
|
}
|
193
302
|
|
194
|
-
/**
|
195
|
-
* Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
|
196
|
-
* 90 degrees counter-clockwise.
|
197
|
-
*/
|
198
303
|
public orthog(): Vec3 {
|
199
304
|
// If parallel to the z-axis
|
200
305
|
if (this.dot(Vec3.unitX) === 0 && this.dot(Vec3.unitY) === 0) {
|
@@ -204,32 +309,14 @@ export class Vec3 {
|
|
204
309
|
return this.cross(Vec3.unitZ.times(-1)).normalized();
|
205
310
|
}
|
206
311
|
|
207
|
-
/** Returns this plus a vector of length `distance` in `direction`. */
|
208
312
|
public extend(distance: number, direction: Vec3): Vec3 {
|
209
313
|
return this.plus(direction.normalized().times(distance));
|
210
314
|
}
|
211
315
|
|
212
|
-
/** Returns a vector `fractionTo` of the way to target from this. */
|
213
316
|
public lerp(target: Vec3, fractionTo: number): Vec3 {
|
214
317
|
return this.times(1 - fractionTo).plus(target.times(fractionTo));
|
215
318
|
}
|
216
319
|
|
217
|
-
/**
|
218
|
-
* `zip` Maps a component of this and a corresponding component of
|
219
|
-
* `other` to a component of the output vector.
|
220
|
-
*
|
221
|
-
* @example
|
222
|
-
* ```
|
223
|
-
* const a = Vec3.of(1, 2, 3);
|
224
|
-
* const b = Vec3.of(0.5, 2.1, 2.9);
|
225
|
-
*
|
226
|
-
* const zipped = a.zip(b, (aComponent, bComponent) => {
|
227
|
-
* return Math.min(aComponent, bComponent);
|
228
|
-
* });
|
229
|
-
*
|
230
|
-
* console.log(zipped.toString()); // → Vec(0.5, 2, 2.9)
|
231
|
-
* ```
|
232
|
-
*/
|
233
320
|
public zip(
|
234
321
|
other: Vec3, zip: (componentInThis: number, componentInOther: number)=> number
|
235
322
|
): Vec3 {
|
@@ -240,39 +327,15 @@ export class Vec3 {
|
|
240
327
|
);
|
241
328
|
}
|
242
329
|
|
243
|
-
/**
|
244
|
-
* Returns a vector with each component acted on by `fn`.
|
245
|
-
*
|
246
|
-
* @example
|
247
|
-
* ```ts,runnable,console
|
248
|
-
* import { Vec3 } from '@js-draw/math';
|
249
|
-
* console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
|
250
|
-
* ```
|
251
|
-
*/
|
252
330
|
public map(fn: (component: number, index: number)=> number): Vec3 {
|
253
|
-
return Vec3.of(
|
254
|
-
fn(this.x, 0), fn(this.y, 1), fn(this.z, 2)
|
255
|
-
);
|
331
|
+
return Vec3.of(fn(this.x, 0), fn(this.y, 1), fn(this.z, 2));
|
256
332
|
}
|
257
333
|
|
258
334
|
public asArray(): [ number, number, number ] {
|
259
335
|
return [this.x, this.y, this.z];
|
260
336
|
}
|
261
337
|
|
262
|
-
|
263
|
-
* [fuzz] The maximum difference between two components for this and [other]
|
264
|
-
* to be considered equal.
|
265
|
-
*
|
266
|
-
* @example
|
267
|
-
* ```
|
268
|
-
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 100); // → true
|
269
|
-
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 0.1); // → false
|
270
|
-
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3); // → true
|
271
|
-
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3.01); // → true
|
272
|
-
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
273
|
-
* ```
|
274
|
-
*/
|
275
|
-
public eq(other: Vec3, fuzz: number = 1e-10): boolean {
|
338
|
+
public eq(other: Vec3, fuzz: number = defaultEqlTolerance): boolean {
|
276
339
|
return (
|
277
340
|
Math.abs(other.x - this.x) <= fuzz
|
278
341
|
&& Math.abs(other.y - this.y) <= fuzz
|
@@ -283,11 +346,235 @@ export class Vec3 {
|
|
283
346
|
public toString(): string {
|
284
347
|
return `Vec(${this.x}, ${this.y}, ${this.z})`;
|
285
348
|
}
|
349
|
+
}
|
350
|
+
|
351
|
+
class Vec2Impl implements Vec3 {
|
352
|
+
public constructor(
|
353
|
+
public readonly x: number,
|
354
|
+
public readonly y: number,
|
355
|
+
) {
|
356
|
+
}
|
357
|
+
|
358
|
+
public get z() { return 0; }
|
359
|
+
|
360
|
+
public get xy(): { x: number; y: number } {
|
361
|
+
// Useful for APIs that behave differently if .z is present.
|
362
|
+
return {
|
363
|
+
x: this.x,
|
364
|
+
y: this.y,
|
365
|
+
};
|
366
|
+
}
|
367
|
+
|
368
|
+
public at(idx: number): number {
|
369
|
+
if (idx === 0) return this.x;
|
370
|
+
if (idx === 1) return this.y;
|
371
|
+
if (idx === 2) return 0;
|
372
|
+
|
373
|
+
throw new Error(`${idx} out of bounds!`);
|
374
|
+
}
|
375
|
+
|
376
|
+
public length(): number {
|
377
|
+
return this.magnitude();
|
378
|
+
}
|
379
|
+
|
380
|
+
public magnitude(): number {
|
381
|
+
return Math.sqrt(this.x * this.x + this.y * this.y);
|
382
|
+
}
|
383
|
+
|
384
|
+
public magnitudeSquared(): number {
|
385
|
+
return this.x * this.x + this.y * this.y;
|
386
|
+
}
|
387
|
+
|
388
|
+
public squareDistanceTo(p: Vec3) {
|
389
|
+
const dx = this.x - p.x;
|
390
|
+
const dy = this.y - p.y;
|
391
|
+
return dx * dx + dy * dy + p.z * p.z;
|
392
|
+
}
|
286
393
|
|
394
|
+
public distanceTo(p: Vec3) {
|
395
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
396
|
+
}
|
397
|
+
|
398
|
+
public maximumEntryMagnitude(): number {
|
399
|
+
return Math.max(Math.abs(this.x), Math.abs(this.y));
|
400
|
+
}
|
401
|
+
|
402
|
+
public angle(): number {
|
403
|
+
return Math.atan2(this.y, this.x);
|
404
|
+
}
|
405
|
+
|
406
|
+
public normalized(): Vec3 {
|
407
|
+
const norm = this.magnitude();
|
408
|
+
return Vec2.of(this.x / norm, this.y / norm);
|
409
|
+
}
|
410
|
+
|
411
|
+
public normalizedOrZero(): Vec3 {
|
412
|
+
if (this.eq(Vec3.zero)) {
|
413
|
+
return Vec3.zero;
|
414
|
+
}
|
415
|
+
|
416
|
+
return this.normalized();
|
417
|
+
}
|
418
|
+
|
419
|
+
public times(c: number): Vec3 {
|
420
|
+
return Vec2.of(this.x * c, this.y * c);
|
421
|
+
}
|
422
|
+
|
423
|
+
public plus(v: Vec3): Vec3 {
|
424
|
+
return Vec3.of(this.x + v.x, this.y + v.y, v.z);
|
425
|
+
}
|
426
|
+
|
427
|
+
public minus(v: Vec3): Vec3 {
|
428
|
+
return Vec3.of(this.x - v.x, this.y - v.y, -v.z);
|
429
|
+
}
|
287
430
|
|
288
|
-
public
|
289
|
-
|
290
|
-
|
291
|
-
|
431
|
+
public dot(other: Vec3): number {
|
432
|
+
return this.x * other.x + this.y * other.y;
|
433
|
+
}
|
434
|
+
|
435
|
+
public cross(other: Vec3): Vec3 {
|
436
|
+
// | i j k |
|
437
|
+
// | x1 y1 z1| = (i)(y1z2 - y2z1) - (j)(x1z2 - x2z1) + (k)(x1y2 - x2y1)
|
438
|
+
// | x2 y2 z2|
|
439
|
+
return Vec3.of(
|
440
|
+
this.y * other.z,
|
441
|
+
-this.x * other.z,
|
442
|
+
this.x * other.y - other.x * this.y,
|
443
|
+
);
|
444
|
+
}
|
445
|
+
|
446
|
+
public scale(other: Vec3|number): Vec3 {
|
447
|
+
if (typeof other === 'number') {
|
448
|
+
return this.times(other);
|
449
|
+
}
|
450
|
+
|
451
|
+
return Vec2.of(
|
452
|
+
this.x * other.x,
|
453
|
+
this.y * other.y,
|
454
|
+
);
|
455
|
+
}
|
456
|
+
|
457
|
+
public orthog(): Vec3 {
|
458
|
+
// If parallel to the z-axis
|
459
|
+
if (this.dot(Vec3.unitX) === 0 && this.dot(Vec3.unitY) === 0) {
|
460
|
+
return this.dot(Vec3.unitX) === 0 ? Vec3.unitX : this.cross(Vec3.unitX).normalized();
|
461
|
+
}
|
462
|
+
|
463
|
+
return this.cross(Vec3.unitZ.times(-1)).normalized();
|
464
|
+
}
|
465
|
+
|
466
|
+
public extend(distance: number, direction: Vec3): Vec3 {
|
467
|
+
return this.plus(direction.normalized().times(distance));
|
468
|
+
}
|
469
|
+
|
470
|
+
public lerp(target: Vec3, fractionTo: number): Vec3 {
|
471
|
+
return this.times(1 - fractionTo).plus(target.times(fractionTo));
|
472
|
+
}
|
473
|
+
|
474
|
+
public zip(
|
475
|
+
other: Vec3, zip: (componentInThis: number, componentInOther: number)=> number
|
476
|
+
): Vec3 {
|
477
|
+
return Vec3.of(
|
478
|
+
zip(other.x, this.x),
|
479
|
+
zip(other.y, this.y),
|
480
|
+
zip(other.z, 0),
|
481
|
+
);
|
482
|
+
}
|
483
|
+
|
484
|
+
public map(fn: (component: number, index: number)=> number): Vec3 {
|
485
|
+
return Vec3.of(
|
486
|
+
fn(this.x, 0), fn(this.y, 1), fn(0, 2)
|
487
|
+
);
|
488
|
+
}
|
489
|
+
|
490
|
+
public asArray(): [ number, number, number ] {
|
491
|
+
return [this.x, this.y, 0];
|
492
|
+
}
|
493
|
+
|
494
|
+
public eq(other: Vec3, fuzz: number = defaultEqlTolerance): boolean {
|
495
|
+
return (
|
496
|
+
Math.abs(other.x - this.x) <= fuzz
|
497
|
+
&& Math.abs(other.y - this.y) <= fuzz
|
498
|
+
&& Math.abs(other.z) <= fuzz
|
499
|
+
);
|
500
|
+
}
|
501
|
+
|
502
|
+
public toString(): string {
|
503
|
+
return `Vec(${this.x}, ${this.y})`;
|
504
|
+
}
|
505
|
+
}
|
506
|
+
|
507
|
+
/**
|
508
|
+
* A `Vec2` is a `Vec3` optimized for working in a plane. As such, they have an
|
509
|
+
* always-zero `z` component.
|
510
|
+
*
|
511
|
+
* ```ts,runnable,console
|
512
|
+
* import { Vec2 } from '@js-draw/math';
|
513
|
+
* console.log(Vec2.of(1, 2));
|
514
|
+
* ```
|
515
|
+
*/
|
516
|
+
export namespace Vec2 {
|
517
|
+
/**
|
518
|
+
* Creates a `Vec2` from an x and y coordinate.
|
519
|
+
*
|
520
|
+
* @example
|
521
|
+
* ```ts,runnable,console
|
522
|
+
* import { Vec2 } from '@js-draw/math';
|
523
|
+
* const v = Vec2.of(3, 4); // x=3, y=4.
|
524
|
+
* ```
|
525
|
+
*/
|
526
|
+
export const of = (x: number, y: number) => {
|
527
|
+
return new Vec2Impl(x, y);
|
528
|
+
};
|
529
|
+
|
530
|
+
/**
|
531
|
+
* Creates a `Vec2` from an object containing `x` and `y` coordinates.
|
532
|
+
*
|
533
|
+
* @example
|
534
|
+
* ```ts,runnable,console
|
535
|
+
* import { Vec2 } from '@js-draw/math';
|
536
|
+
* const v1 = Vec2.ofXY({ x: 3, y: 4.5 });
|
537
|
+
* const v2 = Vec2.ofXY({ x: -123.4, y: 1 });
|
538
|
+
* ```
|
539
|
+
*/
|
540
|
+
export const ofXY = ({x, y}: {x: number, y: number}) => {
|
541
|
+
return Vec2.of(x, y);
|
542
|
+
};
|
543
|
+
|
544
|
+
/** A vector of length 1 in the X direction (→). */
|
545
|
+
export const unitX = Vec2.of(1, 0);
|
546
|
+
|
547
|
+
/** A vector of length 1 in the Y direction (↑). */
|
548
|
+
export const unitY = Vec2.of(0, 1);
|
549
|
+
|
550
|
+
/** The zero vector: A vector with x=0, y=0. */
|
551
|
+
export const zero = Vec2.of(0, 0);
|
292
552
|
}
|
553
|
+
|
554
|
+
export namespace Vec3 {
|
555
|
+
/**
|
556
|
+
* Construct a vector from three components.
|
557
|
+
*
|
558
|
+
* @example
|
559
|
+
* ```ts,runnable,console
|
560
|
+
* import { Vec3 } from '@js-draw/math';
|
561
|
+
* const v1 = Vec3.of(1, 2, 3);
|
562
|
+
* ```
|
563
|
+
*/
|
564
|
+
export const of = (x: number, y: number, z: number): Vec3 => {
|
565
|
+
if (z === 0) {
|
566
|
+
return Vec2.of(x, y);
|
567
|
+
} else {
|
568
|
+
return new Vec3Impl(x, y, z);
|
569
|
+
}
|
570
|
+
};
|
571
|
+
|
572
|
+
export const unitX = Vec2.unitX;
|
573
|
+
export const unitY = Vec2.unitY;
|
574
|
+
export const zero = Vec2.zero;
|
575
|
+
|
576
|
+
/** A vector of length 1 in the z direction. */
|
577
|
+
export const unitZ = Vec3.of(0, 0, 1);
|
578
|
+
}
|
579
|
+
|
293
580
|
export default Vec3;
|
@@ -118,7 +118,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
118
118
|
}
|
119
119
|
|
120
120
|
return t;
|
121
|
-
}).filter(entry => entry !== null)
|
121
|
+
}).filter(entry => entry !== null);
|
122
122
|
}
|
123
123
|
|
124
124
|
public override splitAt(t: number): [BezierJSWrapper] | [BezierJSWrapper, BezierJSWrapper] {
|
@@ -9,7 +9,18 @@ interface IntersectionResult {
|
|
9
9
|
t: number;
|
10
10
|
}
|
11
11
|
|
12
|
-
/**
|
12
|
+
/**
|
13
|
+
* Represents a line segment. A `LineSegment2` is immutable.
|
14
|
+
*
|
15
|
+
* @example
|
16
|
+
* ```ts,runnable,console
|
17
|
+
* import {LineSegment2, Vec2} from '@js-draw/math';
|
18
|
+
* const l = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 2));
|
19
|
+
* console.log('length: ', l.length);
|
20
|
+
* console.log('direction: ', l.direction);
|
21
|
+
* console.log('bounding box: ', l.bbox);
|
22
|
+
* ```
|
23
|
+
*/
|
13
24
|
export class LineSegment2 extends Parameterized2DShape {
|
14
25
|
// invariant: ||direction|| = 1
|
15
26
|
|
@@ -51,7 +62,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
51
62
|
* if no such line segment exists.
|
52
63
|
*
|
53
64
|
* @example
|
54
|
-
* ```ts,runnable
|
65
|
+
* ```ts,runnable,console
|
55
66
|
* import {LineSegment2, Vec2} from '@js-draw/math';
|
56
67
|
* console.log(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 0), Vec2.of(0, 1)]));
|
57
68
|
* ```
|
package/src/shapes/Rect2.ts
CHANGED
@@ -298,9 +298,9 @@ export class Rect2 extends Abstract2DShape {
|
|
298
298
|
return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
|
299
299
|
}
|
300
300
|
|
301
|
-
/** @return true iff this is equal to
|
302
|
-
public eq(other: Rect2,
|
303
|
-
return this.topLeft.eq(other.topLeft,
|
301
|
+
/** @return true iff this is equal to `other ± tolerance` */
|
302
|
+
public eq(other: Rect2, tolerance: number = 0): boolean {
|
303
|
+
return this.topLeft.eq(other.topLeft, tolerance) && this.size.eq(other.size, tolerance);
|
304
304
|
}
|
305
305
|
|
306
306
|
public override toString(): string {
|