pimath 0.1.39 → 0.1.40

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