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