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/triangle.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|