pimath 0.2.13 → 0.2.15
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/dist/pimath.js +856 -1882
- package/dist/pimath.js.map +1 -1
- package/package.json +2 -2
- package/src/algebra/equation.ts +562 -558
- package/src/algebra/equationSolver.ts +489 -489
- package/src/algebra/factor.ts +396 -392
- package/src/algebra/linearSystem.ts +446 -442
- package/src/algebra/matrix.ts +515 -511
- package/src/algebra/monom.ts +932 -928
- package/src/algebra/polyFactor.ts +724 -720
- package/src/algebra/polynom.ts +1233 -1229
- package/src/coefficients/fraction.ts +907 -748
- package/src/coefficients/root.ts +500 -359
- package/src/geometry/TupleN.ts +129 -129
- package/src/geometry/circle.ts +455 -455
- package/src/geometry/line.ts +658 -654
- package/src/geometry/point.ts +105 -105
- package/src/geometry/sphere3.ts +2 -2
- package/src/geometry/triangle.ts +488 -488
- package/src/geometry/vector.ts +230 -226
- package/src/numeric.ts +136 -51
- package/src/pimath.interface.ts +2 -0
- package/types/algebra/equation.d.ts +1 -0
- package/types/algebra/equation.d.ts.map +1 -1
- package/types/algebra/equationSolver.d.ts.map +1 -1
- package/types/algebra/factor.d.ts +1 -0
- package/types/algebra/factor.d.ts.map +1 -1
- package/types/algebra/linearSystem.d.ts +1 -0
- package/types/algebra/linearSystem.d.ts.map +1 -1
- package/types/algebra/matrix.d.ts +6 -5
- package/types/algebra/matrix.d.ts.map +1 -1
- package/types/algebra/monom.d.ts +1 -0
- package/types/algebra/monom.d.ts.map +1 -1
- package/types/algebra/polyFactor.d.ts +1 -0
- package/types/algebra/polyFactor.d.ts.map +1 -1
- package/types/algebra/polynom.d.ts +1 -0
- package/types/algebra/polynom.d.ts.map +1 -1
- package/types/coefficients/fraction.d.ts +62 -53
- package/types/coefficients/fraction.d.ts.map +1 -1
- package/types/coefficients/root.d.ts +3 -0
- package/types/coefficients/root.d.ts.map +1 -1
- package/types/geometry/TupleN.d.ts +1 -1
- package/types/geometry/TupleN.d.ts.map +1 -1
- package/types/geometry/circle.d.ts.map +1 -1
- package/types/geometry/line.d.ts +1 -0
- package/types/geometry/line.d.ts.map +1 -1
- package/types/geometry/point.d.ts.map +1 -1
- package/types/geometry/sphere3.d.ts +2 -2
- package/types/geometry/sphere3.d.ts.map +1 -1
- package/types/geometry/triangle.d.ts.map +1 -1
- package/types/geometry/vector.d.ts +1 -0
- package/types/geometry/vector.d.ts.map +1 -1
- package/types/index.d.ts +3 -3
- package/types/numeric.d.ts +4 -4
- package/types/numeric.d.ts.map +1 -1
- package/types/pimath.interface.d.ts +1 -0
- package/types/pimath.interface.d.ts.map +1 -1
- package/types/primes.data.d.ts +2 -0
- package/types/primes.data.d.ts.map +1 -0
package/src/geometry/line.ts
CHANGED
|
@@ -1,655 +1,659 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This class works for 2d line in a plane.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {Numeric} from "../numeric"
|
|
6
|
-
import {Fraction} from "../coefficients"
|
|
7
|
-
import {Equation, Monom, Polynom} from "../algebra"
|
|
8
|
-
import {Vector} from "./vector"
|
|
9
|
-
import {type InputValue, type IPiMathObject, LinePropriety} from "../pimath.interface"
|
|
10
|
-
import {randomIntSym} from "../randomization/rndHelpers"
|
|
11
|
-
import {Point} from "./point"
|
|
12
|
-
import {Root} from "../coefficients/root"
|
|
13
|
-
import {ParseError} from "../errors"
|
|
14
|
-
|
|
15
|
-
enum LINE_DISPLAY {
|
|
16
|
-
CARTESIAN,
|
|
17
|
-
CANONICAL,
|
|
18
|
-
MXH,
|
|
19
|
-
PARAMETRIC,
|
|
20
|
-
SYSTEM
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
enum LINE_DIRECTION {
|
|
24
|
-
LEFT_TO_RIGHT = 'lr',
|
|
25
|
-
RIGHT_TO_LEFT = 'rl'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class Line implements IPiMathObject<Line> {
|
|
29
|
-
static PARALLEL = LinePropriety.Parallel
|
|
30
|
-
static PERPENDICULAR = LinePropriety.Perpendicular
|
|
31
|
-
#OA: Vector
|
|
32
|
-
// ax + by + c = 0
|
|
33
|
-
#a: Fraction
|
|
34
|
-
|
|
35
|
-
// A line is defined as the canonical form
|
|
36
|
-
#b: Fraction
|
|
37
|
-
#c: Fraction
|
|
38
|
-
#orientation: LINE_DIRECTION
|
|
39
|
-
// output mode.
|
|
40
|
-
#outputMode: LINE_DISPLAY = LINE_DISPLAY.CANONICAL
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Value can be a mix of:
|
|
44
|
-
*
|
|
45
|
-
* @param values
|
|
46
|
-
*/
|
|
47
|
-
constructor(...values: unknown[]) {
|
|
48
|
-
this.#a = new Fraction().zero()
|
|
49
|
-
this.#b = new Fraction().zero()
|
|
50
|
-
this.#c = new Fraction().zero()
|
|
51
|
-
this.#OA = new Vector()
|
|
52
|
-
this.#orientation = LINE_DIRECTION.LEFT_TO_RIGHT
|
|
53
|
-
|
|
54
|
-
if (values.length > 0) {
|
|
55
|
-
this.parse(...values)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return this
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ------------------------------------------
|
|
62
|
-
/**
|
|
63
|
-
* Parse data to a line
|
|
64
|
-
* @param {any} values
|
|
65
|
-
* @returns {Line}
|
|
66
|
-
*/
|
|
67
|
-
parse = (...values: unknown[]): this => {
|
|
68
|
-
// Nothing is given...
|
|
69
|
-
if (values.length === 0) {
|
|
70
|
-
return this
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// One value only: already a line (clone it), an Equation, a string (as Equation)
|
|
74
|
-
if (values.length === 1) {
|
|
75
|
-
if (values[0] instanceof Line) {
|
|
76
|
-
// Already a Line
|
|
77
|
-
return this.fromCoefficient(values[0].a, values[0].b, values[0].c)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (values[0] instanceof Equation) {
|
|
81
|
-
// It's an Equation
|
|
82
|
-
return this.fromEquation(values[0])
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (typeof values[0] === "string") {
|
|
86
|
-
// It's a string - create an Equation from it.
|
|
87
|
-
// A malformed string throws (ParseError) instead of silently
|
|
88
|
-
// returning a half-built Line.
|
|
89
|
-
try {
|
|
90
|
-
const E = new Equation(values[0])
|
|
91
|
-
return this.parse(E)
|
|
92
|
-
} catch (e) {
|
|
93
|
-
throw new ParseError(`Cannot parse "${values[0]}" as a Line`, {cause: e})
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (values.length === 2) {
|
|
99
|
-
if (values[0] instanceof Point && values[1] instanceof Point) {
|
|
100
|
-
return this.fromPoints(values[0], values[1])
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (values[0] instanceof Point && values[1] instanceof Vector) {
|
|
104
|
-
return this.fromPointAndDirection(values[0], values[1])
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (values.length === 3 && values.every(x => Fraction.isFraction(x as InputValue<Fraction>))) {
|
|
109
|
-
return this.fromCoefficient(
|
|
110
|
-
values[0] as InputValue<Fraction>,
|
|
111
|
-
values[1] as InputValue<Fraction>,
|
|
112
|
-
values[2] as InputValue<Fraction>
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.warn('Something wrong happened while creating the line')
|
|
117
|
-
console.warn(values)
|
|
118
|
-
return this
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ------------------------------------------
|
|
122
|
-
// Getter and setter
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
case LINE_DISPLAY.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
case LINE_DISPLAY.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
case LINE_DISPLAY.
|
|
191
|
-
const d = this.d.clone().simplify()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.#a
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
this.#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return this
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
this.#b
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.#c
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
canonical =
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
this.#a.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return this.fromPointAndNormal(point,
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
this
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
// (
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (this.
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
Pt.x = new Fraction().invalid()
|
|
557
|
-
Pt.y = new Fraction().invalid()
|
|
558
|
-
|
|
559
|
-
} else {
|
|
560
|
-
Pt.x =
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
)
|
|
586
|
-
.add(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return this.
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
return this.
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return this.slope.
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
return this.
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
get
|
|
612
|
-
return
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
this.#b.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
return this
|
|
654
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* This class works for 2d line in a plane.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {Numeric} from "../numeric"
|
|
6
|
+
import {Fraction} from "../coefficients"
|
|
7
|
+
import {Equation, Monom, Polynom} from "../algebra"
|
|
8
|
+
import {Vector} from "./vector"
|
|
9
|
+
import {type InputValue, type IPiMathObject, LinePropriety} from "../pimath.interface"
|
|
10
|
+
import {randomIntSym} from "../randomization/rndHelpers"
|
|
11
|
+
import {Point} from "./point"
|
|
12
|
+
import {Root} from "../coefficients/root"
|
|
13
|
+
import {NotImplementedError, ParseError} from "../errors"
|
|
14
|
+
|
|
15
|
+
enum LINE_DISPLAY {
|
|
16
|
+
CARTESIAN,
|
|
17
|
+
CANONICAL,
|
|
18
|
+
MXH,
|
|
19
|
+
PARAMETRIC,
|
|
20
|
+
SYSTEM
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
enum LINE_DIRECTION {
|
|
24
|
+
LEFT_TO_RIGHT = 'lr',
|
|
25
|
+
RIGHT_TO_LEFT = 'rl'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class Line implements IPiMathObject<Line> {
|
|
29
|
+
static PARALLEL = LinePropriety.Parallel
|
|
30
|
+
static PERPENDICULAR = LinePropriety.Perpendicular
|
|
31
|
+
#OA: Vector
|
|
32
|
+
// ax + by + c = 0
|
|
33
|
+
#a: Fraction
|
|
34
|
+
|
|
35
|
+
// A line is defined as the canonical form
|
|
36
|
+
#b: Fraction
|
|
37
|
+
#c: Fraction
|
|
38
|
+
#orientation: LINE_DIRECTION
|
|
39
|
+
// output mode.
|
|
40
|
+
#outputMode: LINE_DISPLAY = LINE_DISPLAY.CANONICAL
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Value can be a mix of:
|
|
44
|
+
*
|
|
45
|
+
* @param values
|
|
46
|
+
*/
|
|
47
|
+
constructor(...values: unknown[]) {
|
|
48
|
+
this.#a = new Fraction().zero()
|
|
49
|
+
this.#b = new Fraction().zero()
|
|
50
|
+
this.#c = new Fraction().zero()
|
|
51
|
+
this.#OA = new Vector()
|
|
52
|
+
this.#orientation = LINE_DIRECTION.LEFT_TO_RIGHT
|
|
53
|
+
|
|
54
|
+
if (values.length > 0) {
|
|
55
|
+
this.parse(...values)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Parse data to a line
|
|
64
|
+
* @param {any} values
|
|
65
|
+
* @returns {Line}
|
|
66
|
+
*/
|
|
67
|
+
parse = (...values: unknown[]): this => {
|
|
68
|
+
// Nothing is given...
|
|
69
|
+
if (values.length === 0) {
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// One value only: already a line (clone it), an Equation, a string (as Equation)
|
|
74
|
+
if (values.length === 1) {
|
|
75
|
+
if (values[0] instanceof Line) {
|
|
76
|
+
// Already a Line
|
|
77
|
+
return this.fromCoefficient(values[0].a, values[0].b, values[0].c)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (values[0] instanceof Equation) {
|
|
81
|
+
// It's an Equation
|
|
82
|
+
return this.fromEquation(values[0])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof values[0] === "string") {
|
|
86
|
+
// It's a string - create an Equation from it.
|
|
87
|
+
// A malformed string throws (ParseError) instead of silently
|
|
88
|
+
// returning a half-built Line.
|
|
89
|
+
try {
|
|
90
|
+
const E = new Equation(values[0])
|
|
91
|
+
return this.parse(E)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
throw new ParseError(`Cannot parse "${values[0]}" as a Line`, {cause: e})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (values.length === 2) {
|
|
99
|
+
if (values[0] instanceof Point && values[1] instanceof Point) {
|
|
100
|
+
return this.fromPoints(values[0], values[1])
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (values[0] instanceof Point && values[1] instanceof Vector) {
|
|
104
|
+
return this.fromPointAndDirection(values[0], values[1])
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (values.length === 3 && values.every(x => Fraction.isFraction(x as InputValue<Fraction>))) {
|
|
109
|
+
return this.fromCoefficient(
|
|
110
|
+
values[0] as InputValue<Fraction>,
|
|
111
|
+
values[1] as InputValue<Fraction>,
|
|
112
|
+
values[2] as InputValue<Fraction>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.warn('Something wrong happened while creating the line')
|
|
117
|
+
console.warn(values)
|
|
118
|
+
return this
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ------------------------------------------
|
|
122
|
+
// Getter and setter
|
|
123
|
+
|
|
124
|
+
copy(): this {
|
|
125
|
+
throw new NotImplementedError("copy() is not yet implemented for Line")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clone = (): Line => {
|
|
129
|
+
return new Line().fromCoefficient(this.#a, this.#b, this.#c)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get tex(): string {
|
|
133
|
+
// canonical => ax + by + c = 0
|
|
134
|
+
// mxh => y = -a/b x - c/b
|
|
135
|
+
// parametric => (xy) = OA + k*d
|
|
136
|
+
// equation => ax + by = -c
|
|
137
|
+
// system => \begin{}...
|
|
138
|
+
|
|
139
|
+
const output = this.#outputMode
|
|
140
|
+
this.#outputMode = LINE_DISPLAY.CANONICAL
|
|
141
|
+
|
|
142
|
+
switch (output) {
|
|
143
|
+
case LINE_DISPLAY.CARTESIAN:
|
|
144
|
+
return this.getEquation().reorder().tex
|
|
145
|
+
case LINE_DISPLAY.MXH:
|
|
146
|
+
return this.slope.isInfinity() ?
|
|
147
|
+
'x=' + this.OA.x.tex :
|
|
148
|
+
'y=' + new Polynom().parse('x', this.slope, this.height).tex
|
|
149
|
+
case LINE_DISPLAY.PARAMETRIC:
|
|
150
|
+
case LINE_DISPLAY.SYSTEM: {
|
|
151
|
+
const d = this.d.clone().simplify()
|
|
152
|
+
|
|
153
|
+
if (output === LINE_DISPLAY.PARAMETRIC) {
|
|
154
|
+
return `${Vector.asTex('x', 'y')} = ${Vector.asTex(this.#OA.x.tex, this.#OA.y.tex)} + k\\cdot ${Vector.asTex(d.x.tex, d.y.tex)}`
|
|
155
|
+
} else {
|
|
156
|
+
return `\\left\\{\\begin{aligned}\n\tx &= ${(new Polynom(this.#OA.x)
|
|
157
|
+
.add(new Monom(d.x).multiply(new Monom('k'))))
|
|
158
|
+
.reorder('k', true)
|
|
159
|
+
.tex}\\\\\n\ty &= ${(new Polynom(this.#OA.y)
|
|
160
|
+
.add(new Monom(d.y).multiply(new Monom('k'))))
|
|
161
|
+
.reorder('k', true)
|
|
162
|
+
.tex}\n\\end{aligned}\\right.`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
default: {
|
|
166
|
+
const canonical = this.getEquation()
|
|
167
|
+
if (canonical.left.monoms[0].coefficient.isNegative()) {
|
|
168
|
+
canonical.multiply(-1)
|
|
169
|
+
}
|
|
170
|
+
return canonical.tex
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
get display(): string {
|
|
177
|
+
// canonical => ax + by + c = 0
|
|
178
|
+
// mxh => y = -a/b x - c/b
|
|
179
|
+
// parametric => (xy) = OA + k*d // not relevant in display mode.
|
|
180
|
+
const output = this.#outputMode
|
|
181
|
+
this.#outputMode = LINE_DISPLAY.CANONICAL
|
|
182
|
+
|
|
183
|
+
switch (output) {
|
|
184
|
+
case LINE_DISPLAY.CARTESIAN:
|
|
185
|
+
return this.getEquation().reorder().display
|
|
186
|
+
case LINE_DISPLAY.MXH:
|
|
187
|
+
return this.slope.isInfinity() ?
|
|
188
|
+
'x=' + this.OA.x.display :
|
|
189
|
+
'y=' + new Polynom().parse('x', this.slope, this.height).display
|
|
190
|
+
case LINE_DISPLAY.PARAMETRIC: {
|
|
191
|
+
const d = this.d.clone().simplify()
|
|
192
|
+
return `((x,y))=((${this.#OA.x.display},${this.#OA.y.display}))+k((${d.x.display},${d.y.display}))`
|
|
193
|
+
}
|
|
194
|
+
case LINE_DISPLAY.SYSTEM: {
|
|
195
|
+
const d = this.d.clone().simplify()
|
|
196
|
+
// {(2x,+,17y,=,23),(x,-,y,=,5):}
|
|
197
|
+
const px = (new Polynom(this.#OA.x).add(new Monom(d.x).multiply(new Monom('k'))))
|
|
198
|
+
.reorder('k', true)
|
|
199
|
+
const py = (new Polynom(this.#OA.y).add(new Monom(d.y).multiply(new Monom('k'))))
|
|
200
|
+
.reorder('k', true)
|
|
201
|
+
|
|
202
|
+
return `{(x,=,${px.display}),(y,=,${py.display}):}`
|
|
203
|
+
}
|
|
204
|
+
default: {
|
|
205
|
+
const canonical = this.getEquation()
|
|
206
|
+
// Make sur the first item is positive.
|
|
207
|
+
if (canonical.left.monoms[0].coefficient.isNegative()) {
|
|
208
|
+
canonical.multiply(-1)
|
|
209
|
+
}
|
|
210
|
+
return canonical.display
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get OA(): Vector {
|
|
217
|
+
return this.#OA
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
set OA(value: Vector | Point) {
|
|
221
|
+
this.fromPointAndNormal(value, this.n)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
get a(): Fraction {
|
|
225
|
+
return this.#a
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
set a(value: Fraction) {
|
|
229
|
+
this.#a = value
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
get asCanonical(): this {
|
|
233
|
+
this.#outputMode = LINE_DISPLAY.CANONICAL
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get asCartesian(): this {
|
|
238
|
+
this.#outputMode = LINE_DISPLAY.CARTESIAN
|
|
239
|
+
return this
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
get asMxh(): this {
|
|
243
|
+
this.#outputMode = LINE_DISPLAY.MXH
|
|
244
|
+
return this
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get asParametric(): this {
|
|
248
|
+
this.#outputMode = LINE_DISPLAY.PARAMETRIC
|
|
249
|
+
return this
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get asSystem(): this {
|
|
253
|
+
this.#outputMode = LINE_DISPLAY.SYSTEM
|
|
254
|
+
return this
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
get b(): Fraction {
|
|
258
|
+
return this.#b
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
set b(value: Fraction) {
|
|
262
|
+
this.#b = value
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
get c(): Fraction {
|
|
266
|
+
return this.#c
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
set c(value: Fraction) {
|
|
270
|
+
this.#c = value
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ------------------------------------------
|
|
274
|
+
canonicalAsFloatCoefficient(decimals = 2): string {
|
|
275
|
+
let canonical = ''
|
|
276
|
+
|
|
277
|
+
if (!this.#a.isZero()) {
|
|
278
|
+
if (this.#a.isOne()) {
|
|
279
|
+
canonical = 'x'
|
|
280
|
+
} else if (this.#a.clone().opposite().isOne()) {
|
|
281
|
+
canonical = '-x'
|
|
282
|
+
} else {
|
|
283
|
+
canonical = this.#a.value.toFixed(decimals) + 'x'
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!this.#b.isZero()) {
|
|
288
|
+
if (this.#b.isPositive()) {
|
|
289
|
+
canonical += '+'
|
|
290
|
+
}
|
|
291
|
+
canonical += this.#b.value.toFixed(decimals) + 'y'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!this.#c.isZero()) {
|
|
295
|
+
if (this.#c.isPositive()) {
|
|
296
|
+
canonical += '+'
|
|
297
|
+
}
|
|
298
|
+
canonical += this.#c.value.toFixed(decimals)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
return canonical + '=0'
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
get d(): Vector {
|
|
306
|
+
const director = new Vector(this.#b.clone(), this.#a.clone().opposite())
|
|
307
|
+
|
|
308
|
+
if (
|
|
309
|
+
this.#orientation === LINE_DIRECTION.LEFT_TO_RIGHT && director.x.isNegative() ||
|
|
310
|
+
this.#orientation === LINE_DIRECTION.RIGHT_TO_LEFT && director.x.isPositive()
|
|
311
|
+
) {
|
|
312
|
+
director.opposite()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return director
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
set d(value: Vector) {
|
|
319
|
+
this.fromPointAndDirection(this.OA, value)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
get director(): Vector {
|
|
323
|
+
return this.d
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
distanceTo(pt: Point): Root {
|
|
327
|
+
const numerator = pt.x.clone().multiply(this.#a)
|
|
328
|
+
.add(pt.y.clone().multiply(this.#b))
|
|
329
|
+
.add(this.#c).abs()
|
|
330
|
+
const d2 = this.normal.normSquare
|
|
331
|
+
|
|
332
|
+
// The denominator is null - shouldn't be possible
|
|
333
|
+
if (d2.isZero()) {
|
|
334
|
+
return new Root(0)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return new Root().from(2, d2.inverse(), numerator).reduce()
|
|
338
|
+
|
|
339
|
+
// The denominator is a perfect square - simplify the tex result
|
|
340
|
+
// const value = numerator.value / Math.sqrt(d2.value)
|
|
341
|
+
// const F = numerator.clone().divide(d2.clone().sqrt())
|
|
342
|
+
|
|
343
|
+
//
|
|
344
|
+
// if (d2.isSquare()) {
|
|
345
|
+
//
|
|
346
|
+
// return {
|
|
347
|
+
// value,
|
|
348
|
+
// tex: F.tex,
|
|
349
|
+
// fraction: F
|
|
350
|
+
// }
|
|
351
|
+
// }
|
|
352
|
+
// // Complete answer...
|
|
353
|
+
// return {
|
|
354
|
+
// value,
|
|
355
|
+
// tex: `\\frac{${numerator.tex}}{\\sqrt{${d2.tex}}}`,
|
|
356
|
+
// fraction: F
|
|
357
|
+
// }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
fromCoefficient(a: InputValue<Fraction>, b: InputValue<Fraction>, c: InputValue<Fraction>): this {
|
|
361
|
+
this.#a = new Fraction(a)
|
|
362
|
+
this.#b = new Fraction(b)
|
|
363
|
+
this.#c = new Fraction(c)
|
|
364
|
+
|
|
365
|
+
// make sure the coefficients are relative...
|
|
366
|
+
const lcm = [this.#a, this.#b, this.#c].every(k => k.exact)
|
|
367
|
+
? Numeric.lcm(this.#a.denominator, this.#b.denominator, this.#c.denominator)
|
|
368
|
+
: 1
|
|
369
|
+
|
|
370
|
+
if (lcm > 1) {
|
|
371
|
+
this.#a.multiply(lcm).reduce()
|
|
372
|
+
this.#b.multiply(lcm).reduce()
|
|
373
|
+
this.#c.multiply(lcm).reduce()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (this.#b.isZero()) {
|
|
377
|
+
// ax+c=0 => x = -c/a
|
|
378
|
+
this.#OA = new Vector(this.#c.clone().divide(this.#a).opposite(), 0)
|
|
379
|
+
return this
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ax+by+c=0 => x=0 => y = a/b x - c/b = (ax-c)/b
|
|
383
|
+
for (let x = 0; x < this.#b.value; x++) {
|
|
384
|
+
const y = this.#a.clone().divide(this.#b)
|
|
385
|
+
.multiply(x)
|
|
386
|
+
.subtract(this.#c.clone().divide(this.#b))
|
|
387
|
+
.reduce()
|
|
388
|
+
|
|
389
|
+
this.#OA = new Vector(x, y)
|
|
390
|
+
|
|
391
|
+
if (y.isRelative()) {
|
|
392
|
+
return this
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// no "nice" point... do it with 'x=0'
|
|
397
|
+
const y = this.#c.clone().divide(this.#b).opposite().reduce()
|
|
398
|
+
this.#OA = new Vector(0, y)
|
|
399
|
+
|
|
400
|
+
return this
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fromEquation(equ: Equation): this {
|
|
404
|
+
// Reorder the equation
|
|
405
|
+
equ.reorder(true)
|
|
406
|
+
|
|
407
|
+
// It must contain either x, y or both.
|
|
408
|
+
const letters = new Set(equ.letters())
|
|
409
|
+
|
|
410
|
+
// No 'x', no 'y' in the equations
|
|
411
|
+
if (!(letters.has('x') || letters.has('y'))) {
|
|
412
|
+
return this
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Another letter in the equation ?
|
|
416
|
+
for (const elem of ['x', 'y']) {
|
|
417
|
+
if (letters.has(elem)) {
|
|
418
|
+
letters.delete(elem)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (letters.size > 0) {
|
|
423
|
+
return this
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Everything should be ok now...
|
|
427
|
+
return this.fromCoefficient(
|
|
428
|
+
equ.left.monomByLetter('x').coefficient,
|
|
429
|
+
equ.left.monomByLetter('y').coefficient,
|
|
430
|
+
equ.left.monomByDegree(0).coefficient
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fromParallel(parallel: Line, point: Point): this {
|
|
435
|
+
return this.fromPointAndNormal(point, parallel.n)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
fromPerpendicular(perpendicular: Line, point: Point): this {
|
|
439
|
+
return this.fromPointAndNormal(point, perpendicular.d)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
fromPointAndDirection(P: Point | Vector, d: Vector): this {
|
|
443
|
+
this.#orientation = d.x.isPositive()
|
|
444
|
+
? LINE_DIRECTION.LEFT_TO_RIGHT
|
|
445
|
+
: LINE_DIRECTION.RIGHT_TO_LEFT
|
|
446
|
+
|
|
447
|
+
return this.fromPointAndNormal(P, d.clone().normal())
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
fromPointAndLine(P: Vector, L: Line, orientation: LinePropriety = LinePropriety.Parallel): this {
|
|
451
|
+
|
|
452
|
+
if (orientation === LinePropriety.Perpendicular) {
|
|
453
|
+
return this.fromPointAndNormal(P, L.director)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return this.fromPointAndNormal(P, L.normal)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fromPointAndNormal = (P: Point | Vector, n: Vector): this => {
|
|
460
|
+
if (n.isZero()) {
|
|
461
|
+
console.warn('Normal vector is null.')
|
|
462
|
+
return this
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.fromCoefficient(
|
|
466
|
+
n.x,
|
|
467
|
+
n.y,
|
|
468
|
+
P.x.clone().multiply(n.x)
|
|
469
|
+
.add(P.y.clone().multiply(n.y)).opposite()
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
this.#OA = new Vector(P.clone())
|
|
473
|
+
|
|
474
|
+
return this
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ------------------------------------------
|
|
478
|
+
// Creation / parsing functions
|
|
479
|
+
|
|
480
|
+
fromPoints(A: Point, B: Point) {
|
|
481
|
+
return this.fromPointAndNormal(A, new Vector(A, B).normal())
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ------------------------------------------
|
|
485
|
+
getEquation(): Equation {
|
|
486
|
+
const equ = new Equation(new Polynom().parse('xy', this.#a, this.#b, this.#c), new Polynom('0'))
|
|
487
|
+
return equ.simplify()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
getValueAtX = (value: Fraction | number): Fraction => {
|
|
491
|
+
const equ = this.getEquation().isolate('y'),
|
|
492
|
+
F = new Fraction(value)
|
|
493
|
+
|
|
494
|
+
if (equ instanceof Equation) {
|
|
495
|
+
return equ.right.evaluate({x: F}) as Fraction
|
|
496
|
+
}
|
|
497
|
+
return new Fraction().invalid()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getValueAtY = (value: Fraction | number): Fraction => {
|
|
501
|
+
const equ = this.getEquation().isolate('x'),
|
|
502
|
+
F = new Fraction(value)
|
|
503
|
+
|
|
504
|
+
if (equ instanceof Equation) {
|
|
505
|
+
return equ.right.evaluate({y: F}) as Fraction
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return new Fraction().invalid()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
get height(): Fraction {
|
|
512
|
+
return this.#c.clone().opposite().divide(this.#b)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
hitSegment(A: Point, B: Point): boolean {
|
|
516
|
+
const iPt = this.intersection(
|
|
517
|
+
new Line().fromPoints(A, B)
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
// There is an intersection point
|
|
521
|
+
if (iPt.hasIntersection) {
|
|
522
|
+
return iPt.point.x.value >= Math.min(A.x.value, B.x.value)
|
|
523
|
+
&& iPt.point.x.value <= Math.max(A.x.value, B.x.value)
|
|
524
|
+
&& iPt.point.y.value >= Math.min(A.y.value, B.y.value)
|
|
525
|
+
&& iPt.point.y.value <= Math.max(A.y.value, B.y.value)
|
|
526
|
+
}
|
|
527
|
+
return false
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
intersection = (line: Line): { point: Point, hasIntersection: boolean, isParallel: boolean, isSame: boolean } => {
|
|
531
|
+
// TODO: rework Line.intersection
|
|
532
|
+
const Pt = new Point()
|
|
533
|
+
let isParallel = false, isSame = false
|
|
534
|
+
|
|
535
|
+
// this => ax+by+c = 0
|
|
536
|
+
// line => dx+ey+f = 0
|
|
537
|
+
//
|
|
538
|
+
// aex + bey + ce = 0
|
|
539
|
+
// dbx + bey + bf = 0
|
|
540
|
+
// (ae-db)x + ce-bf = 0
|
|
541
|
+
//
|
|
542
|
+
// adx + bdy + cd = 0
|
|
543
|
+
// adx + aey + af = 0
|
|
544
|
+
// (bd-ae)y + (cd-af)
|
|
545
|
+
//
|
|
546
|
+
// x = (bf-ce)/(ae-db)
|
|
547
|
+
// y = (af-cd)/(bd-ae)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
// Theres is no 'y'
|
|
551
|
+
if (this.#b.isZero() || line.b.isZero()) {
|
|
552
|
+
// TODO : handle no y in the line canonical form
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (this.isParallelTo(line)) {
|
|
556
|
+
Pt.x = new Fraction().invalid()
|
|
557
|
+
Pt.y = new Fraction().invalid()
|
|
558
|
+
isParallel = true
|
|
559
|
+
} else if (this.isSameAs(line)) {
|
|
560
|
+
Pt.x = new Fraction().invalid()
|
|
561
|
+
Pt.y = new Fraction().invalid()
|
|
562
|
+
isSame = true
|
|
563
|
+
} else {
|
|
564
|
+
Pt.x = this.#b.clone().multiply(line.c).subtract(this.#c.clone().multiply(line.b))
|
|
565
|
+
.divide(this.#a.clone().multiply(line.b).subtract(this.#b.clone().multiply(line.a)))
|
|
566
|
+
Pt.y = this.#a.clone().multiply(line.c).subtract(this.#c.clone().multiply(line.a))
|
|
567
|
+
.divide(this.#b.clone().multiply(line.a).subtract(this.#a.clone().multiply(line.b)))
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
point: Pt,
|
|
572
|
+
hasIntersection: !(isParallel || isSame),
|
|
573
|
+
isParallel,
|
|
574
|
+
isSame
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
isHorizontal(): boolean {
|
|
579
|
+
return this.slope.value === 0
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ------------------------------------------
|
|
583
|
+
isOnLine(pt: Point): boolean {
|
|
584
|
+
return this.#a.clone()
|
|
585
|
+
.multiply(pt.x)
|
|
586
|
+
.add(
|
|
587
|
+
this.#b.clone()
|
|
588
|
+
.multiply(pt.y)
|
|
589
|
+
)
|
|
590
|
+
.add(this.#c)
|
|
591
|
+
.isZero()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
isParallelTo = (line: Line): boolean => {
|
|
595
|
+
// Do they have the isSame direction ?
|
|
596
|
+
return this.slope.isEqual(line.slope) && this.height.isNotEqual(line.height)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
isPerpendicularTo = (line: Line): boolean => {
|
|
600
|
+
return this.d.isNormalTo(line.d)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
isSameAs = (line: Line): boolean => {
|
|
604
|
+
return this.slope.isEqual(line.slope) && this.height.isEqual(line.height)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
isVertical = (): boolean => {
|
|
608
|
+
return this.slope.isInfinity()
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
get n(): Vector {
|
|
612
|
+
return this.d.normal()
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
get normal(): Vector {
|
|
616
|
+
return new Vector(this.#a, this.#b)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
randomNearPoint = (k?: number): Point => {
|
|
620
|
+
const pt = this.randomPoint(k)
|
|
621
|
+
|
|
622
|
+
let maxIterationTest = 10
|
|
623
|
+
while (this.isOnLine(pt) && maxIterationTest > 0) {
|
|
624
|
+
pt.x.add(randomIntSym(1, false))
|
|
625
|
+
pt.y.add(randomIntSym(1, false))
|
|
626
|
+
maxIterationTest--
|
|
627
|
+
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return pt
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
randomPoint = (k?: number): Point => {
|
|
634
|
+
// Return a random point on the line.
|
|
635
|
+
const pt = this.d
|
|
636
|
+
.clone()
|
|
637
|
+
.multiplyByScalar(randomIntSym((k === undefined || k <= 1) ? 3 : k, false))
|
|
638
|
+
.add(this.#OA)
|
|
639
|
+
|
|
640
|
+
return new Point(pt)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
simplify = (): this => {
|
|
644
|
+
const lcm = Numeric.lcm(this.#a.denominator, this.#b.denominator, this.#c.denominator),
|
|
645
|
+
gcd = Numeric.gcd(this.#a.numerator, this.#b.numerator, this.#c.numerator)
|
|
646
|
+
|
|
647
|
+
this.fromCoefficient(
|
|
648
|
+
this.#a.clone().multiply(lcm).divide(gcd),
|
|
649
|
+
this.#b.clone().multiply(lcm).divide(gcd),
|
|
650
|
+
this.#c.clone().multiply(lcm).divide(gcd),
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
return this
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
get slope(): Fraction {
|
|
657
|
+
return this.#a.clone().opposite().divide(this.#b)
|
|
658
|
+
}
|
|
655
659
|
}
|