pimath 0.2.14 → 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.
Files changed (59) hide show
  1. package/dist/pimath.js +850 -1879
  2. package/dist/pimath.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/algebra/equation.ts +562 -558
  5. package/src/algebra/equationSolver.ts +489 -489
  6. package/src/algebra/factor.ts +396 -392
  7. package/src/algebra/linearSystem.ts +446 -442
  8. package/src/algebra/matrix.ts +515 -511
  9. package/src/algebra/monom.ts +932 -928
  10. package/src/algebra/polyFactor.ts +724 -720
  11. package/src/algebra/polynom.ts +1233 -1229
  12. package/src/coefficients/fraction.ts +907 -748
  13. package/src/coefficients/root.ts +500 -359
  14. package/src/geometry/TupleN.ts +129 -129
  15. package/src/geometry/circle.ts +455 -455
  16. package/src/geometry/line.ts +658 -654
  17. package/src/geometry/point.ts +105 -105
  18. package/src/geometry/sphere3.ts +2 -2
  19. package/src/geometry/triangle.ts +488 -488
  20. package/src/geometry/vector.ts +230 -226
  21. package/src/numeric.ts +136 -51
  22. package/src/pimath.interface.ts +2 -0
  23. package/types/algebra/equation.d.ts +1 -0
  24. package/types/algebra/equation.d.ts.map +1 -1
  25. package/types/algebra/equationSolver.d.ts.map +1 -1
  26. package/types/algebra/factor.d.ts +1 -0
  27. package/types/algebra/factor.d.ts.map +1 -1
  28. package/types/algebra/linearSystem.d.ts +1 -0
  29. package/types/algebra/linearSystem.d.ts.map +1 -1
  30. package/types/algebra/matrix.d.ts +6 -5
  31. package/types/algebra/matrix.d.ts.map +1 -1
  32. package/types/algebra/monom.d.ts +1 -0
  33. package/types/algebra/monom.d.ts.map +1 -1
  34. package/types/algebra/polyFactor.d.ts +1 -0
  35. package/types/algebra/polyFactor.d.ts.map +1 -1
  36. package/types/algebra/polynom.d.ts +1 -0
  37. package/types/algebra/polynom.d.ts.map +1 -1
  38. package/types/coefficients/fraction.d.ts +62 -53
  39. package/types/coefficients/fraction.d.ts.map +1 -1
  40. package/types/coefficients/root.d.ts +3 -0
  41. package/types/coefficients/root.d.ts.map +1 -1
  42. package/types/geometry/TupleN.d.ts +1 -1
  43. package/types/geometry/TupleN.d.ts.map +1 -1
  44. package/types/geometry/circle.d.ts.map +1 -1
  45. package/types/geometry/line.d.ts +1 -0
  46. package/types/geometry/line.d.ts.map +1 -1
  47. package/types/geometry/point.d.ts.map +1 -1
  48. package/types/geometry/sphere3.d.ts +2 -2
  49. package/types/geometry/sphere3.d.ts.map +1 -1
  50. package/types/geometry/triangle.d.ts.map +1 -1
  51. package/types/geometry/vector.d.ts +1 -0
  52. package/types/geometry/vector.d.ts.map +1 -1
  53. package/types/index.d.ts +3 -3
  54. package/types/numeric.d.ts +4 -4
  55. package/types/numeric.d.ts.map +1 -1
  56. package/types/pimath.interface.d.ts +1 -0
  57. package/types/pimath.interface.d.ts.map +1 -1
  58. package/types/primes.data.d.ts +2 -0
  59. package/types/primes.data.d.ts.map +1 -0
@@ -1,488 +1,488 @@
1
- import {Fraction} from "../coefficients"
2
- import {Line} from "./line"
3
- import {Vector} from "./vector"
4
- import {Point} from "./point"
5
- import type {InputValue, remarquableLines} from "../pimath.interface"
6
- import {Numeric} from "../numeric"
7
- import {IndexError, InvalidArgumentError} from "../errors"
8
-
9
- type TRIANGLE_SIDES = 'AB' | 'AC' | 'BC'
10
-
11
- // TODO: add a check if it's a triangle or not.
12
- export class Triangle {
13
- // This defines the triangle
14
- #A: Point = new Point()
15
- #B: Point = new Point()
16
- #C: Point = new Point()
17
- #isValid = false
18
- // This is calculated
19
- #lines: Record<TRIANGLE_SIDES, Line> | null = null
20
- #radians = true
21
- #remarquables: remarquableLines = {
22
- mediators: null, medians: null, heights: null, externalBisectors: null, bisectors: null
23
- }
24
-
25
- constructor(...values: unknown[]) {
26
- if (values.length > 0) {
27
- this.parse(...values)
28
- }
29
-
30
- return this
31
- }
32
-
33
- parse = (...values: unknown[]): this => {
34
-
35
- if (values.length === 1) {
36
- if (values[0] instanceof Triangle) {
37
- return this.copy(values[0])
38
- }
39
- }
40
-
41
- if (values.length === 3) {
42
- // Possibilities:
43
- // - Three points (or part of points, only dict for example, or array
44
- // - Three lines
45
- // - Three lines as text.
46
- if (values.every((x: unknown) => typeof x === 'string')) {
47
- // Three lines as text.
48
- const [a, b, c] = values
49
- return this.fromLines(a, b, c)
50
- }
51
-
52
- if (values.every((x: unknown) => x instanceof Line)) {
53
- // We have three lines
54
- return this.fromLines(values[0], values[1], values[2])
55
- }
56
-
57
- if (values.every((x: unknown) => (x instanceof Point))) {
58
- return this.fromPoints(values[0], values[1], values[2])
59
- }
60
- }
61
-
62
- if (values.length === 6) {
63
- const v: Fraction[] = values.map((x: unknown) => new Fraction(x as string))
64
-
65
- if (v.some(x => x.isNaN())) {
66
- throw new InvalidArgumentError('One of the values is not a valid number')
67
- }
68
-
69
- return this.fromCoordinates(v[0], v[1], v[2], v[3], v[4], v[5])
70
- }
71
-
72
- return this
73
- }
74
-
75
- /**
76
- * Clone the Triangle class
77
- */
78
- clone = (): Triangle => {
79
- return new Triangle(
80
- this.#A.clone(),
81
- this.#B.clone(),
82
- this.#C.clone()
83
- )
84
- }
85
-
86
- /**
87
- * Copy the values from another triangle
88
- * @param value
89
- */
90
- copy(value: Triangle): this {
91
- this.#A = value.A.clone()
92
- this.#B = value.B.clone()
93
- this.#C = value.C.clone()
94
-
95
- return this
96
- }
97
-
98
- get A(): Point {
99
- return this.#A
100
- }
101
-
102
- set A(value: Point) {
103
- this.#A = value
104
- this.#A.onChange = () => this.reset()
105
- this.reset()
106
- }
107
-
108
- get AB(): Vector {
109
- return this.#getSegment('A', 'B')
110
- }
111
-
112
- get AC(): Vector {
113
- return this.#getSegment('A', 'C')
114
- }
115
-
116
- get B(): Point {
117
- return this.#B
118
- }
119
-
120
- set B(value: Point) {
121
- this.#B = value
122
- this.#B.onChange = () => this.reset()
123
- this.reset()
124
- }
125
-
126
- get BA(): Vector {
127
- return this.#getSegment('B', 'A')
128
- }
129
-
130
- get BC(): Vector {
131
- return this.#getSegment('B', 'C')
132
- }
133
-
134
- get C(): Point {
135
- return this.#C
136
- }
137
-
138
- set C(value: Point) {
139
- this.#C = value
140
- this.#C.onChange = () => this.reset()
141
- this.reset()
142
- }
143
-
144
- get CA(): Vector {
145
- return this.#getSegment('C', 'A')
146
- }
147
-
148
- get CB(): Vector {
149
- return this.#getSegment('C', 'B')
150
- }
151
-
152
- get asDegree(): this {
153
- this.#radians = false
154
- return this
155
- }
156
-
157
- get asRadians(): this {
158
- this.#radians = true
159
- return this
160
- }
161
-
162
- fromCoordinates(
163
- x1: InputValue<Fraction>, y1: InputValue<Fraction>,
164
- x2: InputValue<Fraction>, y2: InputValue<Fraction>,
165
- x3: InputValue<Fraction>, y3: InputValue<Fraction>): this {
166
-
167
- return this.fromPoints(
168
- new Point(x1, y1),
169
- new Point(x2, y2),
170
- new Point(x3, y3),
171
- )
172
- }
173
-
174
- fromLines(line1: Line | string, line2: Line | string, line3: Line | string): this {
175
- const AB: Line = new Line(line1).clone()
176
- const BC: Line = new Line(line2).clone()
177
- const AC: Line = new Line(line3).clone()
178
-
179
- // Get the intersection points -> build the triangle using these intersection points.
180
- let intersect = AB.intersection(BC)
181
- if (intersect.hasIntersection) {
182
- this.#B = intersect.point
183
- } else {
184
- this.#isValid = false
185
- return this
186
- }
187
-
188
- intersect = BC.intersection(AC)
189
- if (intersect.hasIntersection) {
190
- this.#C = intersect.point
191
- } else {
192
- this.#isValid = false
193
- return this
194
- }
195
-
196
- intersect = AC.intersection(AB)
197
- if (intersect.hasIntersection) {
198
- this.#A = intersect.point
199
- } else {
200
- this.#isValid = false
201
- return this
202
- }
203
-
204
- // reset the remarquables lines.
205
- this.reset()
206
-
207
- // Force the use of the given lines.
208
- this.#lines = {AB, AC, BC}
209
-
210
- return this
211
-
212
- }
213
-
214
- fromPoints(A: Point, B: Point, C: Point): this {
215
- // We have three points.
216
- this.#A = A.clone()
217
- this.#B = B.clone()
218
- this.#C = C.clone()
219
-
220
- this.reset()
221
-
222
- return this
223
- }
224
-
225
- getAngle(name: 'A' | 'B' | 'C'): number {
226
- const a = this.BC.norm
227
- const b = this.AC.norm
228
- const c = this.AB.norm
229
-
230
- if (name === 'A') {
231
- return this.#cosThm(a, b, c)
232
- }
233
-
234
- if (name === 'C') {
235
- return this.#cosThm(c, b, a)
236
- }
237
-
238
- return this.#cosThm(b, a, c)
239
- }
240
-
241
-
242
- getBisectors(internal = true): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
243
-
244
- if (internal && this.#remarquables.bisectors) return this.#remarquables.bisectors
245
- if (!internal && this.#remarquables.externalBisectors) return this.#remarquables.externalBisectors
246
-
247
- const A = this.#calculateBisectors('A', internal)
248
- const B = this.#calculateBisectors('B', internal)
249
- const C = this.#calculateBisectors('C', internal)
250
-
251
- const intersection = A.intersection(B).point
252
- if (internal) {
253
- this.#remarquables.bisectors = {A, B, C, intersection}
254
- } else {
255
- this.#remarquables.externalBisectors = {A, B, C, intersection}
256
- }
257
-
258
- return {A, B, C, intersection}
259
- }
260
-
261
- getHeights(): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
262
-
263
- if (!this.#remarquables.heights) {
264
- const A = new Line().fromPointAndNormal(this.#A, new Vector(this.#B, this.#C))
265
- const B = new Line().fromPointAndNormal(this.#B, new Vector(this.#A, this.#C))
266
- const C = new Line().fromPointAndNormal(this.#C, new Vector(this.#A, this.#B))
267
-
268
- const intersection = A.intersection(B).point
269
- this.#remarquables.heights = {A, B, C, intersection}
270
- }
271
-
272
- return this.#remarquables.heights
273
- }
274
-
275
- getMedians(): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
276
- const middles = this.getMiddles()
277
-
278
- if (!this.#remarquables.medians) {
279
- const A = new Line().fromPoints(this.#A, middles.BC)
280
- const B = new Line().fromPoints(this.#B, middles.AC)
281
- const C = new Line().fromPoints(this.#C, middles.AB)
282
-
283
- const intersection = A.intersection(B).point
284
- this.#remarquables.medians = {A, B, C, intersection}
285
- }
286
-
287
- return this.#remarquables.medians
288
- }
289
-
290
- getMediators(): { 'a': Line, 'b': Line, 'c': Line, 'intersection': Point | null } {
291
- const middles = this.getMiddles()
292
-
293
- if (!this.#remarquables.mediators) {
294
- const a = new Line().fromPointAndNormal(middles.BC, new Vector(this.#B, this.#C))
295
- const b = new Line().fromPointAndNormal(middles.AC, new Vector(this.#A, this.#C))
296
- const c = new Line().fromPointAndNormal(middles.AB, new Vector(this.#A, this.#B))
297
-
298
- const intersection = a.intersection(b).point
299
- this.#remarquables.mediators = {a, b, c, intersection}
300
- }
301
-
302
- return this.#remarquables.mediators
303
- }
304
-
305
- getMiddles() {
306
- return {
307
- 'AB': new Point().middleOf(this.#A, this.#B),
308
- 'AC': new Point().middleOf(this.#A, this.#C),
309
- 'BC': new Point().middleOf(this.#B, this.#C)
310
- }
311
- }
312
-
313
- getPoints(): Point[] {
314
- return [this.A, this.B, this.C]
315
- }
316
-
317
- getSortedPoints(): Point[] {
318
- return this.getPoints().sort((a, b) => {
319
- return a.x.value === b.x.value
320
- ? a.y.value - b.y.value
321
- : a.x.value - b.x.value
322
- })
323
- }
324
-
325
- isEqual(T: Triangle): boolean {
326
- if (!this.#isValid || !T.isValid) return false
327
-
328
- // TODO: compare points in a particular order.
329
- const tri1 = this.getSortedPoints()
330
- const tri2 = T.getSortedPoints()
331
-
332
- return tri1.every((_, index) => tri1[index].isEqual(tri2[index]))
333
-
334
- }
335
-
336
- isEquilateral(): boolean {
337
- if (!this.#isValid) return false
338
-
339
- const dAB = this.AB.normSquare.value
340
- const dBC = this.BC.normSquare.value
341
- const dAC = this.AC.normSquare.value
342
-
343
- return (dAB === dBC) && (dAB === dAC)
344
- }
345
-
346
- isIsocele(): boolean {
347
- if (!this.#isValid) return false
348
-
349
- const dAB = this.AB.normSquare.value
350
- const dBC = this.BC.normSquare.value
351
- const dAC = this.AC.normSquare.value
352
-
353
- return dAB === dBC ||
354
- dAB === dAC ||
355
- dBC === dAC
356
- }
357
-
358
- isRectangle(): boolean {
359
- if (!this.#isValid) return false
360
-
361
- return this.AB.isNormalTo(this.BC) ||
362
- this.AB.isNormalTo(this.AC) ||
363
- this.BC.isNormalTo(this.AC)
364
- }
365
-
366
- get isValid(): boolean {
367
- return this.#isValid
368
- }
369
-
370
- set isValid(value: boolean) {
371
- this.#isValid = value
372
- }
373
-
374
- get lines(): Record<TRIANGLE_SIDES, Line> {
375
- if (this.#lines === null) {
376
- this.#lines = {
377
- 'AB': new Line(this.#A, this.#B),
378
- 'BC': new Line(this.#B, this.#C),
379
- 'AC': new Line(this.#A, this.#C)
380
- }
381
- }
382
-
383
- return this.#lines
384
- }
385
-
386
- medianA(): Line {
387
- return this.getMedians().A
388
- }
389
-
390
- medianB(): Line {
391
- return this.getMedians().B
392
- }
393
-
394
- medianC(): Line {
395
- return this.getMedians().C
396
- }
397
-
398
- get remarquables(): remarquableLines | null {
399
- return this.#remarquables
400
- }
401
-
402
- public reset(): this {
403
- // Check if the triangle is valid
404
- // the three points must NOT be aligned.
405
- this.#isValid = !this.AB.isColinearTo(this.AC)
406
-
407
- this.#lines = null
408
- this.#remarquables = {
409
- mediators: null,
410
- medians: null,
411
- heights: null,
412
- externalBisectors: null,
413
- bisectors: null
414
- }
415
-
416
- return this
417
- }
418
-
419
- #calculateBisectors(pt: string, internal = true): Line {
420
- const tlines = this.lines
421
- let d1: Vector = new Vector()
422
- let d2: Vector = new Vector()
423
- let P: Point = new Point()
424
-
425
- if (pt === 'A') {
426
- P = this.A.clone()
427
- d1 = tlines.AB.clone().d
428
- d2 = tlines.AC.clone().d
429
- } else if (pt === 'B') {
430
- P = this.B.clone()
431
- d1 = tlines.AB.clone().d.opposite()
432
- d2 = tlines.BC.clone().d
433
- } else if (pt === 'C') {
434
- P = this.C.clone()
435
- d1 = tlines.BC.clone().d.opposite()
436
- d2 = tlines.AC.clone().d.opposite()
437
- }
438
-
439
- if (d1 === undefined || d2 === undefined) {
440
- throw new IndexError(`The point ${pt} does not exist`)
441
- }
442
-
443
- const director = internal
444
- ? d1.unit().add(d2.unit())
445
- : d1.unit().subtract(d2.unit())
446
-
447
- return new Line().fromPointAndDirection(P, director)
448
- }
449
-
450
- #cosThm(opposite: number, adjacent1: number, adjacent2: number): number {
451
- const ratio = ((adjacent1 ** 2 + adjacent2 ** 2) - opposite ** 2) / (2 * adjacent1 * adjacent2)
452
-
453
- return this.#radians
454
- ? Math.acos(ratio)
455
- : Numeric.numberCorrection(Math.acos(ratio) * 180 / Math.PI)
456
- }
457
-
458
- /**
459
- * Get the Point class for the given name
460
- * @param ptName
461
- */
462
- #getPointByName = (ptName: string): Point => {
463
- switch (ptName.toUpperCase()) {
464
- case 'A':
465
- return this.#A
466
- case 'B':
467
- return this.#B
468
- case 'C':
469
- return this.#C
470
- }
471
-
472
- // Something went wrong ! Return the first point
473
- return this.#A
474
- }
475
-
476
- /**
477
- * Get the vector for the segment given by name.
478
- * @param ptName1
479
- * @param ptName2
480
- */
481
- #getSegment = (ptName1: string, ptName2: string): Vector => {
482
- return new Vector(
483
- this.#getPointByName(ptName1),
484
- this.#getPointByName(ptName2)
485
- )
486
- }
487
-
488
- }
1
+ import {Fraction} from "../coefficients"
2
+ import {Line} from "./line"
3
+ import {Vector} from "./vector"
4
+ import {Point} from "./point"
5
+ import type {InputValue, remarquableLines} from "../pimath.interface"
6
+ import {Numeric} from "../numeric"
7
+ import {IndexError, InvalidArgumentError} from "../errors"
8
+
9
+ type TRIANGLE_SIDES = 'AB' | 'AC' | 'BC'
10
+
11
+ // TODO: add a check if it's a triangle or not.
12
+ export class Triangle {
13
+ // This defines the triangle
14
+ #A: Point = new Point()
15
+ #B: Point = new Point()
16
+ #C: Point = new Point()
17
+ #isValid = false
18
+ // This is calculated
19
+ #lines: Record<TRIANGLE_SIDES, Line> | null = null
20
+ #radians = true
21
+ #remarquables: remarquableLines = {
22
+ mediators: null, medians: null, heights: null, externalBisectors: null, bisectors: null
23
+ }
24
+
25
+ constructor(...values: unknown[]) {
26
+ if (values.length > 0) {
27
+ this.parse(...values)
28
+ }
29
+
30
+ return this
31
+ }
32
+
33
+ parse = (...values: unknown[]): this => {
34
+
35
+ if (values.length === 1) {
36
+ if (values[0] instanceof Triangle) {
37
+ return this.copy(values[0])
38
+ }
39
+ }
40
+
41
+ if (values.length === 3) {
42
+ // Possibilities:
43
+ // - Three points (or part of points, only dict for example, or array
44
+ // - Three lines
45
+ // - Three lines as text.
46
+ if (values.every((x: unknown) => typeof x === 'string')) {
47
+ // Three lines as text.
48
+ const [a, b, c] = values
49
+ return this.fromLines(a, b, c)
50
+ }
51
+
52
+ if (values.every((x: unknown) => x instanceof Line)) {
53
+ // We have three lines
54
+ return this.fromLines(values[0], values[1], values[2])
55
+ }
56
+
57
+ if (values.every((x: unknown) => (x instanceof Point))) {
58
+ return this.fromPoints(values[0], values[1], values[2])
59
+ }
60
+ }
61
+
62
+ if (values.length === 6) {
63
+ const v: Fraction[] = values.map((x: unknown) => new Fraction(x as string))
64
+
65
+ if (v.some(x => x.isNaN())) {
66
+ throw new InvalidArgumentError('One of the values is not a valid number')
67
+ }
68
+
69
+ return this.fromCoordinates(v[0], v[1], v[2], v[3], v[4], v[5])
70
+ }
71
+
72
+ return this
73
+ }
74
+
75
+ /**
76
+ * Clone the Triangle class
77
+ */
78
+ clone = (): Triangle => {
79
+ return new Triangle(
80
+ this.#A.clone(),
81
+ this.#B.clone(),
82
+ this.#C.clone()
83
+ )
84
+ }
85
+
86
+ /**
87
+ * Copy the values from another triangle
88
+ * @param value
89
+ */
90
+ copy(value: Triangle): this {
91
+ this.#A = value.A.clone()
92
+ this.#B = value.B.clone()
93
+ this.#C = value.C.clone()
94
+
95
+ return this
96
+ }
97
+
98
+ get A(): Point {
99
+ return this.#A
100
+ }
101
+
102
+ set A(value: Point) {
103
+ this.#A = value
104
+ this.#A.onChange = () => this.reset()
105
+ this.reset()
106
+ }
107
+
108
+ get AB(): Vector {
109
+ return this.#getSegment('A', 'B')
110
+ }
111
+
112
+ get AC(): Vector {
113
+ return this.#getSegment('A', 'C')
114
+ }
115
+
116
+ get B(): Point {
117
+ return this.#B
118
+ }
119
+
120
+ set B(value: Point) {
121
+ this.#B = value
122
+ this.#B.onChange = () => this.reset()
123
+ this.reset()
124
+ }
125
+
126
+ get BA(): Vector {
127
+ return this.#getSegment('B', 'A')
128
+ }
129
+
130
+ get BC(): Vector {
131
+ return this.#getSegment('B', 'C')
132
+ }
133
+
134
+ get C(): Point {
135
+ return this.#C
136
+ }
137
+
138
+ set C(value: Point) {
139
+ this.#C = value
140
+ this.#C.onChange = () => this.reset()
141
+ this.reset()
142
+ }
143
+
144
+ get CA(): Vector {
145
+ return this.#getSegment('C', 'A')
146
+ }
147
+
148
+ get CB(): Vector {
149
+ return this.#getSegment('C', 'B')
150
+ }
151
+
152
+ get asDegree(): this {
153
+ this.#radians = false
154
+ return this
155
+ }
156
+
157
+ get asRadians(): this {
158
+ this.#radians = true
159
+ return this
160
+ }
161
+
162
+ fromCoordinates(
163
+ x1: InputValue<Fraction>, y1: InputValue<Fraction>,
164
+ x2: InputValue<Fraction>, y2: InputValue<Fraction>,
165
+ x3: InputValue<Fraction>, y3: InputValue<Fraction>): this {
166
+
167
+ return this.fromPoints(
168
+ new Point(x1, y1),
169
+ new Point(x2, y2),
170
+ new Point(x3, y3),
171
+ )
172
+ }
173
+
174
+ fromLines(line1: Line | string, line2: Line | string, line3: Line | string): this {
175
+ const AB: Line = new Line(line1).clone()
176
+ const BC: Line = new Line(line2).clone()
177
+ const AC: Line = new Line(line3).clone()
178
+
179
+ // Get the intersection points -> build the triangle using these intersection points.
180
+ let intersect = AB.intersection(BC)
181
+ if (intersect.hasIntersection) {
182
+ this.#B = intersect.point
183
+ } else {
184
+ this.#isValid = false
185
+ return this
186
+ }
187
+
188
+ intersect = BC.intersection(AC)
189
+ if (intersect.hasIntersection) {
190
+ this.#C = intersect.point
191
+ } else {
192
+ this.#isValid = false
193
+ return this
194
+ }
195
+
196
+ intersect = AC.intersection(AB)
197
+ if (intersect.hasIntersection) {
198
+ this.#A = intersect.point
199
+ } else {
200
+ this.#isValid = false
201
+ return this
202
+ }
203
+
204
+ // reset the remarquables lines.
205
+ this.reset()
206
+
207
+ // Force the use of the given lines.
208
+ this.#lines = {AB, AC, BC}
209
+
210
+ return this
211
+
212
+ }
213
+
214
+ fromPoints(A: Point, B: Point, C: Point): this {
215
+ // We have three points.
216
+ this.#A = A.clone()
217
+ this.#B = B.clone()
218
+ this.#C = C.clone()
219
+
220
+ this.reset()
221
+
222
+ return this
223
+ }
224
+
225
+ getAngle(name: 'A' | 'B' | 'C'): number {
226
+ const a = this.BC.norm
227
+ const b = this.AC.norm
228
+ const c = this.AB.norm
229
+
230
+ if (name === 'A') {
231
+ return this.#cosThm(a, b, c)
232
+ }
233
+
234
+ if (name === 'C') {
235
+ return this.#cosThm(c, b, a)
236
+ }
237
+
238
+ return this.#cosThm(b, a, c)
239
+ }
240
+
241
+
242
+ getBisectors(internal = true): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
243
+
244
+ if (internal && this.#remarquables.bisectors) return this.#remarquables.bisectors
245
+ if (!internal && this.#remarquables.externalBisectors) return this.#remarquables.externalBisectors
246
+
247
+ const A = this.#calculateBisectors('A', internal)
248
+ const B = this.#calculateBisectors('B', internal)
249
+ const C = this.#calculateBisectors('C', internal)
250
+
251
+ const intersection = A.intersection(B).point
252
+ if (internal) {
253
+ this.#remarquables.bisectors = {A, B, C, intersection}
254
+ } else {
255
+ this.#remarquables.externalBisectors = {A, B, C, intersection}
256
+ }
257
+
258
+ return {A, B, C, intersection}
259
+ }
260
+
261
+ getHeights(): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
262
+
263
+ if (!this.#remarquables.heights) {
264
+ const A = new Line().fromPointAndNormal(this.#A, new Vector(this.#B, this.#C))
265
+ const B = new Line().fromPointAndNormal(this.#B, new Vector(this.#A, this.#C))
266
+ const C = new Line().fromPointAndNormal(this.#C, new Vector(this.#A, this.#B))
267
+
268
+ const intersection = A.intersection(B).point
269
+ this.#remarquables.heights = {A, B, C, intersection}
270
+ }
271
+
272
+ return this.#remarquables.heights
273
+ }
274
+
275
+ getMedians(): { 'A': Line, 'B': Line, 'C': Line, 'intersection': Point | null } {
276
+ const middles = this.getMiddles()
277
+
278
+ if (!this.#remarquables.medians) {
279
+ const A = new Line().fromPoints(this.#A, middles.BC)
280
+ const B = new Line().fromPoints(this.#B, middles.AC)
281
+ const C = new Line().fromPoints(this.#C, middles.AB)
282
+
283
+ const intersection = A.intersection(B).point
284
+ this.#remarquables.medians = {A, B, C, intersection}
285
+ }
286
+
287
+ return this.#remarquables.medians
288
+ }
289
+
290
+ getMediators(): { 'a': Line, 'b': Line, 'c': Line, 'intersection': Point | null } {
291
+ const middles = this.getMiddles()
292
+
293
+ if (!this.#remarquables.mediators) {
294
+ const a = new Line().fromPointAndNormal(middles.BC, new Vector(this.#B, this.#C))
295
+ const b = new Line().fromPointAndNormal(middles.AC, new Vector(this.#A, this.#C))
296
+ const c = new Line().fromPointAndNormal(middles.AB, new Vector(this.#A, this.#B))
297
+
298
+ const intersection = a.intersection(b).point
299
+ this.#remarquables.mediators = {a, b, c, intersection}
300
+ }
301
+
302
+ return this.#remarquables.mediators
303
+ }
304
+
305
+ getMiddles() {
306
+ return {
307
+ 'AB': new Point().middleOf(this.#A, this.#B),
308
+ 'AC': new Point().middleOf(this.#A, this.#C),
309
+ 'BC': new Point().middleOf(this.#B, this.#C)
310
+ }
311
+ }
312
+
313
+ getPoints(): Point[] {
314
+ return [this.A, this.B, this.C]
315
+ }
316
+
317
+ getSortedPoints(): Point[] {
318
+ return this.getPoints().sort((a, b) => {
319
+ return a.x.value === b.x.value
320
+ ? a.y.value - b.y.value
321
+ : a.x.value - b.x.value
322
+ })
323
+ }
324
+
325
+ isEqual(T: Triangle): boolean {
326
+ if (!this.#isValid || !T.isValid) return false
327
+
328
+ // TODO: compare points in a particular order.
329
+ const tri1 = this.getSortedPoints()
330
+ const tri2 = T.getSortedPoints()
331
+
332
+ return tri1.every((_, index) => tri1[index].isEqual(tri2[index]))
333
+
334
+ }
335
+
336
+ isEquilateral(): boolean {
337
+ if (!this.#isValid) return false
338
+
339
+ const dAB = this.AB.normSquare.value
340
+ const dBC = this.BC.normSquare.value
341
+ const dAC = this.AC.normSquare.value
342
+
343
+ return (dAB === dBC) && (dAB === dAC)
344
+ }
345
+
346
+ isIsocele(): boolean {
347
+ if (!this.#isValid) return false
348
+
349
+ const dAB = this.AB.normSquare.value
350
+ const dBC = this.BC.normSquare.value
351
+ const dAC = this.AC.normSquare.value
352
+
353
+ return dAB === dBC ||
354
+ dAB === dAC ||
355
+ dBC === dAC
356
+ }
357
+
358
+ isRectangle(): boolean {
359
+ if (!this.#isValid) return false
360
+
361
+ return this.AB.isNormalTo(this.BC) ||
362
+ this.AB.isNormalTo(this.AC) ||
363
+ this.BC.isNormalTo(this.AC)
364
+ }
365
+
366
+ get isValid(): boolean {
367
+ return this.#isValid
368
+ }
369
+
370
+ set isValid(value: boolean) {
371
+ this.#isValid = value
372
+ }
373
+
374
+ get lines(): Record<TRIANGLE_SIDES, Line> {
375
+ if (this.#lines === null) {
376
+ this.#lines = {
377
+ 'AB': new Line(this.#A, this.#B),
378
+ 'BC': new Line(this.#B, this.#C),
379
+ 'AC': new Line(this.#A, this.#C)
380
+ }
381
+ }
382
+
383
+ return this.#lines
384
+ }
385
+
386
+ medianA(): Line {
387
+ return this.getMedians().A
388
+ }
389
+
390
+ medianB(): Line {
391
+ return this.getMedians().B
392
+ }
393
+
394
+ medianC(): Line {
395
+ return this.getMedians().C
396
+ }
397
+
398
+ get remarquables(): remarquableLines | null {
399
+ return this.#remarquables
400
+ }
401
+
402
+ reset(): this {
403
+ // Check if the triangle is valid
404
+ // the three points must NOT be aligned.
405
+ this.#isValid = !this.AB.isColinearTo(this.AC)
406
+
407
+ this.#lines = null
408
+ this.#remarquables = {
409
+ mediators: null,
410
+ medians: null,
411
+ heights: null,
412
+ externalBisectors: null,
413
+ bisectors: null
414
+ }
415
+
416
+ return this
417
+ }
418
+
419
+ #calculateBisectors(pt: string, internal = true): Line {
420
+ const tlines = this.lines
421
+ let d1: Vector = new Vector()
422
+ let d2: Vector = new Vector()
423
+ let P: Point = new Point()
424
+
425
+ if (pt === 'A') {
426
+ P = this.A.clone()
427
+ d1 = tlines.AB.clone().d
428
+ d2 = tlines.AC.clone().d
429
+ } else if (pt === 'B') {
430
+ P = this.B.clone()
431
+ d1 = tlines.AB.clone().d.opposite()
432
+ d2 = tlines.BC.clone().d
433
+ } else if (pt === 'C') {
434
+ P = this.C.clone()
435
+ d1 = tlines.BC.clone().d.opposite()
436
+ d2 = tlines.AC.clone().d.opposite()
437
+ }
438
+
439
+ if (d1 === undefined || d2 === undefined) {
440
+ throw new IndexError(`The point ${pt} does not exist`)
441
+ }
442
+
443
+ const director = internal
444
+ ? d1.unit().add(d2.unit())
445
+ : d1.unit().subtract(d2.unit())
446
+
447
+ return new Line().fromPointAndDirection(P, director)
448
+ }
449
+
450
+ #cosThm(opposite: number, adjacent1: number, adjacent2: number): number {
451
+ const ratio = ((adjacent1 ** 2 + adjacent2 ** 2) - opposite ** 2) / (2 * adjacent1 * adjacent2)
452
+
453
+ return this.#radians
454
+ ? Math.acos(ratio)
455
+ : Numeric.numberCorrection(Math.acos(ratio) * 180 / Math.PI)
456
+ }
457
+
458
+ /**
459
+ * Get the Point class for the given name
460
+ * @param ptName
461
+ */
462
+ #getPointByName = (ptName: string): Point => {
463
+ switch (ptName.toUpperCase()) {
464
+ case 'A':
465
+ return this.#A
466
+ case 'B':
467
+ return this.#B
468
+ case 'C':
469
+ return this.#C
470
+ }
471
+
472
+ // Something went wrong ! Return the first point
473
+ return this.#A
474
+ }
475
+
476
+ /**
477
+ * Get the vector for the segment given by name.
478
+ * @param ptName1
479
+ * @param ptName2
480
+ */
481
+ #getSegment = (ptName1: string, ptName2: string): Vector => {
482
+ return new Vector(
483
+ this.#getPointByName(ptName1),
484
+ this.#getPointByName(ptName2)
485
+ )
486
+ }
487
+
488
+ }