@js-draw/math 1.21.2 → 1.22.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/build-config.json +1 -1
- package/dist/cjs/Color4.js +2 -2
- package/dist/cjs/Mat33.d.ts +1 -11
- package/dist/cjs/Mat33.js +8 -24
- package/dist/cjs/Vec3.js +9 -7
- package/dist/cjs/shapes/BezierJSWrapper.js +20 -13
- package/dist/cjs/shapes/LineSegment2.js +13 -17
- package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
- package/dist/cjs/shapes/Path.js +49 -47
- package/dist/cjs/shapes/Rect2.js +13 -15
- package/dist/cjs/shapes/Triangle.js +4 -5
- package/dist/cjs/utils/convexHull2Of.js +3 -3
- package/dist/mjs/Color4.mjs +2 -2
- package/dist/mjs/Mat33.d.ts +1 -11
- package/dist/mjs/Mat33.mjs +8 -24
- package/dist/mjs/Vec3.mjs +9 -7
- package/dist/mjs/shapes/BezierJSWrapper.mjs +20 -13
- package/dist/mjs/shapes/LineSegment2.mjs +13 -17
- package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
- package/dist/mjs/shapes/Path.mjs +49 -47
- package/dist/mjs/shapes/Rect2.mjs +13 -15
- package/dist/mjs/shapes/Triangle.mjs +4 -5
- package/dist/mjs/utils/convexHull2Of.mjs +3 -3
- package/dist-test/test_imports/test-require.cjs +1 -1
- package/package.json +3 -3
- package/src/Color4.test.ts +16 -21
- package/src/Color4.ts +22 -17
- package/src/Mat33.fromCSSMatrix.test.ts +31 -45
- package/src/Mat33.test.ts +58 -96
- package/src/Mat33.ts +61 -104
- package/src/Vec2.test.ts +3 -3
- package/src/Vec3.test.ts +2 -3
- package/src/Vec3.ts +34 -58
- package/src/lib.ts +0 -2
- package/src/polynomial/solveQuadratic.test.ts +39 -13
- package/src/polynomial/solveQuadratic.ts +5 -6
- package/src/rounding/cleanUpNumber.test.ts +1 -1
- package/src/rounding/constants.ts +1 -3
- package/src/rounding/getLenAfterDecimal.ts +1 -2
- package/src/rounding/lib.ts +1 -2
- package/src/rounding/toRoundedString.test.ts +1 -1
- package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
- package/src/rounding/toStringOfSamePrecision.ts +1 -1
- package/src/shapes/BezierJSWrapper.ts +54 -37
- package/src/shapes/CubicBezier.ts +3 -3
- package/src/shapes/LineSegment2.test.ts +24 -17
- package/src/shapes/LineSegment2.ts +26 -29
- package/src/shapes/Parameterized2DShape.ts +5 -4
- package/src/shapes/Path.fromString.test.ts +5 -5
- package/src/shapes/Path.test.ts +122 -120
- package/src/shapes/Path.toString.test.ts +7 -7
- package/src/shapes/Path.ts +378 -352
- package/src/shapes/PointShape2D.ts +3 -3
- package/src/shapes/QuadraticBezier.test.ts +27 -21
- package/src/shapes/QuadraticBezier.ts +4 -9
- package/src/shapes/Rect2.test.ts +44 -75
- package/src/shapes/Rect2.ts +30 -35
- package/src/shapes/Triangle.test.ts +31 -29
- package/src/shapes/Triangle.ts +17 -18
- package/src/utils/convexHull2Of.test.ts +54 -15
- package/src/utils/convexHull2Of.ts +9 -7
- package/tsconfig.json +1 -3
- package/typedoc.json +2 -2
@@ -1,6 +1,5 @@
|
|
1
1
|
import { numberRegex } from './constants';
|
2
2
|
|
3
|
-
|
4
3
|
/**
|
5
4
|
* Returns the length of `numberAsString` after a decimal point.
|
6
5
|
*
|
@@ -16,7 +15,7 @@ export const getLenAfterDecimal = (numberAsString: string) => {
|
|
16
15
|
// like NaN or Infinity)
|
17
16
|
if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
|
18
17
|
return -1;
|
19
|
-
|
18
|
+
// Or it has no decimal point
|
20
19
|
} else {
|
21
20
|
return 0;
|
22
21
|
}
|
package/src/rounding/lib.ts
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
export { toRoundedString } from './toRoundedString';
|
1
|
+
export { toRoundedString } from './toRoundedString';
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { toStringOfSamePrecision } from './toStringOfSamePrecision';
|
2
2
|
|
3
|
-
|
4
3
|
it('toStringOfSamePrecision', () => {
|
5
4
|
expect(toStringOfSamePrecision(1.23456, '1.12')).toBe('1.23');
|
6
5
|
expect(toStringOfSamePrecision(1.23456, '1.120')).toBe('1.235');
|
@@ -18,4 +17,4 @@ it('toStringOfSamePrecision', () => {
|
|
18
17
|
expect(toStringOfSamePrecision(-0.9999999999999432, '291.3')).toBe('-1');
|
19
18
|
expect(toStringOfSamePrecision(9998.9, '.1', '-11')).toBe('9998.9');
|
20
19
|
expect(toStringOfSamePrecision(-14.20000000000002, '.000001', '-11')).toBe('-14.2');
|
21
|
-
});
|
20
|
+
});
|
@@ -14,11 +14,9 @@ import Parameterized2DShape from './Parameterized2DShape';
|
|
14
14
|
* @internal
|
15
15
|
*/
|
16
16
|
export abstract class BezierJSWrapper extends Parameterized2DShape {
|
17
|
-
#bezierJs: Bezier|null = null;
|
17
|
+
#bezierJs: Bezier | null = null;
|
18
18
|
|
19
|
-
protected constructor(
|
20
|
-
bezierJsBezier?: Bezier
|
21
|
-
) {
|
19
|
+
protected constructor(bezierJsBezier?: Bezier) {
|
22
20
|
super();
|
23
21
|
|
24
22
|
if (bezierJsBezier) {
|
@@ -31,7 +29,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
31
29
|
|
32
30
|
protected getBezier() {
|
33
31
|
if (!this.#bezierJs) {
|
34
|
-
this.#bezierJs = new Bezier(this.getPoints().map(p => p.xy));
|
32
|
+
this.#bezierJs = new Bezier(this.getPoints().map((p) => p.xy));
|
35
33
|
}
|
36
34
|
return this.#bezierJs;
|
37
35
|
}
|
@@ -96,41 +94,49 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
96
94
|
const asLine = LineSegment2.ofSmallestContainingPoints(this.getPoints());
|
97
95
|
if (asLine) {
|
98
96
|
const intersection = asLine.intersectsLineSegment(line);
|
99
|
-
return intersection.map(p => this.nearestPointTo(p).parameterValue);
|
97
|
+
return intersection.map((p) => this.nearestPointTo(p).parameterValue);
|
100
98
|
}
|
101
99
|
|
102
100
|
const bezier = this.getBezier();
|
103
101
|
|
104
|
-
return bezier
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
102
|
+
return bezier
|
103
|
+
.intersects(line)
|
104
|
+
.map((t) => {
|
105
|
+
// We're using the .intersects(line) function, which is documented
|
106
|
+
// to always return numbers. However, to satisfy the type checker (and
|
107
|
+
// possibly improperly-defined types),
|
108
|
+
if (typeof t === 'string') {
|
109
|
+
t = parseFloat(t);
|
110
|
+
}
|
111
|
+
|
112
|
+
const point = Vec2.ofXY(this.at(t));
|
113
|
+
|
114
|
+
// Ensure that the intersection is on the line segment
|
115
|
+
if (point.distanceTo(line.p1) > line.length || point.distanceTo(line.p2) > line.length) {
|
116
|
+
return null;
|
117
|
+
}
|
118
|
+
|
119
|
+
return t;
|
120
|
+
})
|
121
|
+
.filter((entry) => entry !== null);
|
122
122
|
}
|
123
123
|
|
124
124
|
public override splitAt(t: number): [BezierJSWrapper] | [BezierJSWrapper, BezierJSWrapper] {
|
125
125
|
if (t <= 0 || t >= 1) {
|
126
|
-
return [
|
126
|
+
return [this];
|
127
127
|
}
|
128
128
|
|
129
129
|
const bezier = this.getBezier();
|
130
130
|
const split = bezier.split(t);
|
131
131
|
return [
|
132
|
-
new BezierJSWrapperImpl(
|
133
|
-
|
132
|
+
new BezierJSWrapperImpl(
|
133
|
+
split.left.points.map((point) => Vec2.ofXY(point)),
|
134
|
+
split.left,
|
135
|
+
),
|
136
|
+
new BezierJSWrapperImpl(
|
137
|
+
split.right.points.map((point) => Vec2.ofXY(point)),
|
138
|
+
split.right,
|
139
|
+
),
|
134
140
|
];
|
135
141
|
}
|
136
142
|
|
@@ -162,7 +168,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
162
168
|
|
163
169
|
// Start by testing a few points:
|
164
170
|
const pointsToTest = 4;
|
165
|
-
for (let i = 0; i < pointsToTest; i
|
171
|
+
for (let i = 0; i < pointsToTest; i++) {
|
166
172
|
const testT = i / (pointsToTest - 1);
|
167
173
|
const testMinSqrDist = sqrDistAt(testT);
|
168
174
|
|
@@ -181,8 +187,12 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
181
187
|
const bPrime = this.derivativeAt(t);
|
182
188
|
const bPrimePrime = this.secondDerivativeAt(t);
|
183
189
|
return (
|
184
|
-
2 * bPrime.x * bPrime.x
|
185
|
-
|
190
|
+
2 * bPrime.x * bPrime.x +
|
191
|
+
2 * b.x * bPrimePrime.x -
|
192
|
+
2 * point.x * bPrimePrime.x +
|
193
|
+
2 * bPrime.y * bPrime.y +
|
194
|
+
2 * b.y * bPrimePrime.y -
|
195
|
+
2 * point.y * bPrimePrime.y
|
186
196
|
);
|
187
197
|
};
|
188
198
|
// Because we're zeroing f'(t), we also need to be able to compute it:
|
@@ -191,8 +201,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
191
201
|
const b = this.at(t);
|
192
202
|
const bPrime = this.derivativeAt(t);
|
193
203
|
return (
|
194
|
-
2 * b.x * bPrime.x - 2 * point.x * bPrime.x
|
195
|
-
+ 2 * b.y * bPrime.y - 2 * point.y * bPrime.y
|
204
|
+
2 * b.x * bPrime.x - 2 * point.x * bPrime.x + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y
|
196
205
|
);
|
197
206
|
};
|
198
207
|
|
@@ -226,7 +235,10 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
226
235
|
}
|
227
236
|
|
228
237
|
public intersectsBezier(other: BezierJSWrapper) {
|
229
|
-
const intersections = this.getBezier().intersects(other.getBezier()) as
|
238
|
+
const intersections = this.getBezier().intersects(other.getBezier()) as
|
239
|
+
| string[]
|
240
|
+
| null
|
241
|
+
| undefined;
|
230
242
|
if (!intersections || intersections.length === 0) {
|
231
243
|
return [];
|
232
244
|
}
|
@@ -239,7 +251,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
239
251
|
|
240
252
|
if (!match) {
|
241
253
|
throw new Error(
|
242
|
-
`Incorrect format returned by .intersects: ${intersections} should be array of "number/number"
|
254
|
+
`Incorrect format returned by .intersects: ${intersections} should be array of "number/number"!`,
|
243
255
|
);
|
244
256
|
}
|
245
257
|
|
@@ -253,7 +265,9 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
253
265
|
}
|
254
266
|
|
255
267
|
public override toString() {
|
256
|
-
return `Bézier(${this.getPoints()
|
268
|
+
return `Bézier(${this.getPoints()
|
269
|
+
.map((point) => point.toString())
|
270
|
+
.join(', ')})`;
|
257
271
|
}
|
258
272
|
}
|
259
273
|
|
@@ -262,7 +276,10 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
|
|
262
276
|
* around a `Bezier`.
|
263
277
|
*/
|
264
278
|
class BezierJSWrapperImpl extends BezierJSWrapper {
|
265
|
-
public constructor(
|
279
|
+
public constructor(
|
280
|
+
private controlPoints: readonly Point2[],
|
281
|
+
curve?: Bezier,
|
282
|
+
) {
|
266
283
|
super(curve);
|
267
284
|
}
|
268
285
|
|
@@ -271,4 +288,4 @@ class BezierJSWrapperImpl extends BezierJSWrapper {
|
|
271
288
|
}
|
272
289
|
}
|
273
290
|
|
274
|
-
export default BezierJSWrapper;
|
291
|
+
export default BezierJSWrapper;
|
@@ -23,13 +23,13 @@ class CubicBezier extends BezierJSWrapper {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
public override getPoints() {
|
26
|
-
return [
|
26
|
+
return [this.p0, this.p1, this.p2, this.p3];
|
27
27
|
}
|
28
28
|
|
29
29
|
/** Returns an overestimate of this shape's bounding box. */
|
30
30
|
public override getLooseBoundingBox(): Rect2 {
|
31
|
-
return Rect2.bboxOf([
|
31
|
+
return Rect2.bboxOf([this.p0, this.p1, this.p2, this.p3]);
|
32
32
|
}
|
33
33
|
}
|
34
34
|
|
35
|
-
export default CubicBezier;
|
35
|
+
export default CubicBezier;
|
@@ -2,7 +2,6 @@ import LineSegment2 from './LineSegment2';
|
|
2
2
|
import { Vec2 } from '../Vec2';
|
3
3
|
import Mat33 from '../Mat33';
|
4
4
|
|
5
|
-
|
6
5
|
describe('Line2', () => {
|
7
6
|
it('x and y axes should intersect at (0, 0)', () => {
|
8
7
|
const xAxis = new LineSegment2(Vec2.of(-10, 0), Vec2.of(10, 0));
|
@@ -65,7 +64,7 @@ describe('Line2', () => {
|
|
65
64
|
// Points taken from issue observed directly in editor
|
66
65
|
const p1 = Vec2.of(769.6126045442547, 221.037877485765);
|
67
66
|
const p2 = Vec2.of(770.3873954557453, 224.962122514235);
|
68
|
-
const p3 = Vec2.of(
|
67
|
+
const p3 = Vec2.of(763.3590010920082, 223.66723995850086);
|
69
68
|
const p4 = Vec2.of(763.5494167642871, 223.66723995850086);
|
70
69
|
|
71
70
|
const line1 = new LineSegment2(p1, p2);
|
@@ -115,7 +114,7 @@ describe('Line2', () => {
|
|
115
114
|
// Halving
|
116
115
|
//
|
117
116
|
expect(lineSegment.at(0.5)).objEq(midpoint);
|
118
|
-
const [
|
117
|
+
const [firstHalf, secondHalf] = lineSegment.splitAt(0.5);
|
119
118
|
|
120
119
|
if (!secondHalf) {
|
121
120
|
throw new Error('Splitting a line segment in half should yield two line segments.');
|
@@ -136,24 +135,32 @@ describe('Line2', () => {
|
|
136
135
|
it('equivalence check should allow ignoring direction', () => {
|
137
136
|
expect(new LineSegment2(Vec2.zero, Vec2.unitX)).objEq(new LineSegment2(Vec2.zero, Vec2.unitX));
|
138
137
|
expect(new LineSegment2(Vec2.zero, Vec2.unitX)).objEq(new LineSegment2(Vec2.unitX, Vec2.zero));
|
139
|
-
expect(new LineSegment2(Vec2.zero, Vec2.unitX)).not.objEq(
|
138
|
+
expect(new LineSegment2(Vec2.zero, Vec2.unitX)).not.objEq(
|
139
|
+
new LineSegment2(Vec2.unitX, Vec2.zero),
|
140
|
+
{ ignoreDirection: false },
|
141
|
+
);
|
140
142
|
});
|
141
143
|
|
142
144
|
it('should support creating from a collection of points', () => {
|
143
145
|
expect(LineSegment2.ofSmallestContainingPoints([])).toBeNull();
|
144
146
|
expect(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1)])).toBeNull();
|
145
|
-
expect(
|
146
|
-
[Vec2.of(1, 1), Vec2.of(1, 2), Vec2.of(3, 3)]
|
147
|
-
)
|
148
|
-
|
149
|
-
expect(LineSegment2.ofSmallestContainingPoints(
|
150
|
-
|
151
|
-
)
|
152
|
-
expect(
|
153
|
-
[Vec2.of(1, 1), Vec2.of(2, 2), Vec2.of(3, 3)]
|
154
|
-
)
|
155
|
-
expect(
|
156
|
-
|
157
|
-
|
147
|
+
expect(
|
148
|
+
LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(1, 2), Vec2.of(3, 3)]),
|
149
|
+
).toBeNull();
|
150
|
+
|
151
|
+
expect(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(1, 2)])).objEq(
|
152
|
+
new LineSegment2(Vec2.of(1, 1), Vec2.of(1, 2)),
|
153
|
+
);
|
154
|
+
expect(
|
155
|
+
LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(2, 2), Vec2.of(3, 3)]),
|
156
|
+
).objEq(new LineSegment2(Vec2.of(1, 1), Vec2.of(3, 3)));
|
157
|
+
expect(
|
158
|
+
LineSegment2.ofSmallestContainingPoints([
|
159
|
+
Vec2.of(3, 3),
|
160
|
+
Vec2.of(2, 2),
|
161
|
+
Vec2.of(2.4, 2.4),
|
162
|
+
Vec2.of(3, 3),
|
163
|
+
]),
|
164
|
+
).objEq(new LineSegment2(Vec2.of(2, 2), Vec2.of(3, 3)));
|
158
165
|
});
|
159
166
|
});
|
@@ -42,7 +42,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
42
42
|
/** Creates a new `LineSegment2` from its endpoints. */
|
43
43
|
public constructor(
|
44
44
|
private readonly point1: Point2,
|
45
|
-
private readonly point2: Point2
|
45
|
+
private readonly point2: Point2,
|
46
46
|
) {
|
47
47
|
super();
|
48
48
|
|
@@ -70,7 +70,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
70
70
|
public static ofSmallestContainingPoints(points: readonly Point2[]) {
|
71
71
|
if (points.length <= 1) return null;
|
72
72
|
|
73
|
-
const sorted = [...points].sort((a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y);
|
73
|
+
const sorted = [...points].sort((a, b) => (a.x !== b.x ? a.x - b.x : a.y - b.y));
|
74
74
|
const line = new LineSegment2(sorted[0], sorted[sorted.length - 1]);
|
75
75
|
|
76
76
|
for (const point of sorted) {
|
@@ -127,15 +127,12 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
127
127
|
return this.direction;
|
128
128
|
}
|
129
129
|
|
130
|
-
public splitAt(t: number): [LineSegment2]|[LineSegment2,LineSegment2] {
|
130
|
+
public splitAt(t: number): [LineSegment2] | [LineSegment2, LineSegment2] {
|
131
131
|
if (t <= 0 || t >= 1) {
|
132
132
|
return [this];
|
133
133
|
}
|
134
134
|
|
135
|
-
return [
|
136
|
-
new LineSegment2(this.point1, this.at(t)),
|
137
|
-
new LineSegment2(this.at(t), this.point2),
|
138
|
-
];
|
135
|
+
return [new LineSegment2(this.point1, this.at(t)), new LineSegment2(this.at(t), this.point2)];
|
139
136
|
}
|
140
137
|
|
141
138
|
/**
|
@@ -146,7 +143,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
146
143
|
* This will change in a future release.
|
147
144
|
* @deprecated
|
148
145
|
*/
|
149
|
-
public intersection(other: LineSegment2): IntersectionResult|null {
|
146
|
+
public intersection(other: LineSegment2): IntersectionResult | null {
|
150
147
|
// TODO(v2.0.0): Make this return a `t` value from `0` to `1`.
|
151
148
|
|
152
149
|
// We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
|
@@ -191,21 +188,18 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
191
188
|
|
192
189
|
const xIntersect = this.point1.x;
|
193
190
|
const yIntersect =
|
194
|
-
(this.point1.x - other.point1.x) * other.direction.y / other.direction.x + other.point1.y;
|
191
|
+
((this.point1.x - other.point1.x) * other.direction.y) / other.direction.x + other.point1.y;
|
195
192
|
resultPoint = Vec2.of(xIntersect, yIntersect);
|
196
193
|
resultT = (yIntersect - this.point1.y) / this.direction.y;
|
197
194
|
} else {
|
198
195
|
// From above,
|
199
196
|
// x = ((o₁ᵧ - o₂ᵧ)(d₁ₓd₂ₓ) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
|
200
|
-
const numerator =
|
201
|
-
(this.point1.y - other.point1.y) * this.direction.x * other.direction.x
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
other.direction.y * this.direction.x
|
207
|
-
- this.direction.y * other.direction.x
|
208
|
-
);
|
197
|
+
const numerator =
|
198
|
+
(this.point1.y - other.point1.y) * this.direction.x * other.direction.x +
|
199
|
+
this.direction.x * other.direction.y * other.point1.x -
|
200
|
+
this.direction.y * other.direction.x * this.point1.x;
|
201
|
+
const denominator =
|
202
|
+
other.direction.y * this.direction.x - this.direction.y * other.direction.x;
|
209
203
|
|
210
204
|
// Avoid dividing by zero. It means there is no intersection
|
211
205
|
if (denominator === 0) {
|
@@ -225,10 +219,12 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
225
219
|
const resultToP3 = resultPoint.distanceTo(other.point1);
|
226
220
|
const resultToP4 = resultPoint.distanceTo(other.point2);
|
227
221
|
|
228
|
-
if (
|
229
|
-
|
230
|
-
|
231
|
-
|
222
|
+
if (
|
223
|
+
resultToP1 > this.length ||
|
224
|
+
resultToP2 > this.length ||
|
225
|
+
resultToP3 > other.length ||
|
226
|
+
resultToP4 > other.length
|
227
|
+
) {
|
232
228
|
return null;
|
233
229
|
}
|
234
230
|
|
@@ -246,7 +242,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
246
242
|
const intersection = this.intersection(lineSegment);
|
247
243
|
|
248
244
|
if (intersection) {
|
249
|
-
return [
|
245
|
+
return [intersection.t / this.length];
|
250
246
|
}
|
251
247
|
return [];
|
252
248
|
}
|
@@ -263,7 +259,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
263
259
|
const intersection = this.intersection(lineSegment);
|
264
260
|
|
265
261
|
if (intersection) {
|
266
|
-
return [
|
262
|
+
return [intersection.point];
|
267
263
|
}
|
268
264
|
return [];
|
269
265
|
}
|
@@ -273,7 +269,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
273
269
|
return this.nearestPointTo(target).point;
|
274
270
|
}
|
275
271
|
|
276
|
-
public override nearestPointTo(target: Vec3): { point: Vec3; parameterValue: number
|
272
|
+
public override nearestPointTo(target: Vec3): { point: Vec3; parameterValue: number } {
|
277
273
|
// Distance from P1 along this' direction.
|
278
274
|
const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
|
279
275
|
const projectedDistFromP2 = this.length - projectedDistFromP1;
|
@@ -304,7 +300,8 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
304
300
|
/** Returns a copy of this line segment transformed by the given `affineTransfm`. */
|
305
301
|
public transformedBy(affineTransfm: Mat33): LineSegment2 {
|
306
302
|
return new LineSegment2(
|
307
|
-
affineTransfm.transformVec2(this.p1),
|
303
|
+
affineTransfm.transformVec2(this.p1),
|
304
|
+
affineTransfm.transformVec2(this.p2),
|
308
305
|
);
|
309
306
|
}
|
310
307
|
|
@@ -324,7 +321,7 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
324
321
|
* - `tolerance`: The maximum difference between endpoints. (Default: 0)
|
325
322
|
* - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
|
326
323
|
*/
|
327
|
-
public eq(other: LineSegment2, options?: { tolerance?: number
|
324
|
+
public eq(other: LineSegment2, options?: { tolerance?: number; ignoreDirection?: boolean }) {
|
328
325
|
if (!(other instanceof LineSegment2)) {
|
329
326
|
return false;
|
330
327
|
}
|
@@ -333,8 +330,8 @@ export class LineSegment2 extends Parameterized2DShape {
|
|
333
330
|
const ignoreDirection = options?.ignoreDirection ?? true;
|
334
331
|
|
335
332
|
return (
|
336
|
-
(other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance))
|
337
|
-
|
333
|
+
(other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance)) ||
|
334
|
+
(ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance))
|
338
335
|
);
|
339
336
|
}
|
340
337
|
}
|
@@ -20,13 +20,15 @@ export abstract class Parameterized2DShape extends Abstract2DShape {
|
|
20
20
|
/**
|
21
21
|
* Divides this shape into two separate shapes at parameter value $t$.
|
22
22
|
*/
|
23
|
-
abstract splitAt(
|
23
|
+
abstract splitAt(
|
24
|
+
t: number,
|
25
|
+
): [Parameterized2DShape] | [Parameterized2DShape, Parameterized2DShape];
|
24
26
|
|
25
27
|
/**
|
26
28
|
* Returns the nearest point on `this` to `point` and the `parameterValue` at which
|
27
29
|
* that point occurs.
|
28
30
|
*/
|
29
|
-
abstract nearestPointTo(point: Point2): { point: Point2
|
31
|
+
abstract nearestPointTo(point: Point2): { point: Point2; parameterValue: number };
|
30
32
|
|
31
33
|
/**
|
32
34
|
* Returns the **parameter values** at which `lineSegment` intersects this shape.
|
@@ -35,9 +37,8 @@ export abstract class Parameterized2DShape extends Abstract2DShape {
|
|
35
37
|
*/
|
36
38
|
public abstract argIntersectsLineSegment(lineSegment: LineSegment2): number[];
|
37
39
|
|
38
|
-
|
39
40
|
public override intersectsLineSegment(line: LineSegment2): Point2[] {
|
40
|
-
return this.argIntersectsLineSegment(line).map(t => this.at(t));
|
41
|
+
return this.argIntersectsLineSegment(line).map((t) => this.at(t));
|
41
42
|
}
|
42
43
|
}
|
43
44
|
|
@@ -27,7 +27,7 @@ describe('Path.fromString', () => {
|
|
27
27
|
},
|
28
28
|
{
|
29
29
|
kind: PathCommandType.MoveTo,
|
30
|
-
point: Vec2.of(3,3),
|
30
|
+
point: Vec2.of(3, 3),
|
31
31
|
},
|
32
32
|
]);
|
33
33
|
expect(path3.startPoint).toMatchObject(Vec2.of(1, 1));
|
@@ -71,7 +71,7 @@ describe('Path.fromString', () => {
|
|
71
71
|
const path1 = Path.fromString('m3,3 l1,2 l1,1 z');
|
72
72
|
const path2 = Path.fromString('m3,3 l1,2 l1,1 Z');
|
73
73
|
|
74
|
-
expect(path1.startPoint).toMatchObject(Vec2.of(3,3));
|
74
|
+
expect(path1.startPoint).toMatchObject(Vec2.of(3, 3));
|
75
75
|
expect(path2.startPoint).toMatchObject(path1.startPoint);
|
76
76
|
expect(path1.parts).toMatchObject(path2.parts);
|
77
77
|
expect(path1.parts).toMatchObject([
|
@@ -86,7 +86,7 @@ describe('Path.fromString', () => {
|
|
86
86
|
{
|
87
87
|
kind: PathCommandType.LineTo,
|
88
88
|
point: path1.startPoint,
|
89
|
-
}
|
89
|
+
},
|
90
90
|
]);
|
91
91
|
});
|
92
92
|
|
@@ -128,7 +128,7 @@ describe('Path.fromString', () => {
|
|
128
128
|
controlPoint1: Vec2.of(1, 1),
|
129
129
|
controlPoint2: Vec2.of(0.1, 0.1),
|
130
130
|
endPoint: Vec2.zero,
|
131
|
-
}
|
131
|
+
},
|
132
132
|
]);
|
133
133
|
});
|
134
134
|
|
@@ -220,4 +220,4 @@ describe('Path.fromString', () => {
|
|
220
220
|
]);
|
221
221
|
expect(path.startPoint).toMatchObject(Vec2.of(5, 10));
|
222
222
|
});
|
223
|
-
});
|
223
|
+
});
|