pimath 0.1.39 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pimath.js +188 -159
- package/dist/pimath.js.map +1 -1
- package/package.json +4 -2
- package/src/algebra/equation.ts +556 -0
- package/src/algebra/equationSolver.ts +539 -0
- package/src/algebra/factor.ts +339 -0
- package/src/algebra/index.ts +11 -0
- package/src/algebra/linearSystem.ts +388 -0
- package/src/algebra/logicalset.ts +256 -0
- package/src/algebra/matrix.ts +474 -0
- package/src/algebra/monom.ts +1015 -0
- package/src/algebra/operations.ts +24 -0
- package/src/algebra/polyFactor.ts +668 -0
- package/src/algebra/polynom.ts +1394 -0
- package/src/analyze/solution.ts +115 -0
- package/src/analyze/tableOfSigns.ts +30 -0
- package/src/coefficients/fraction.ts +678 -0
- package/src/coefficients/index.ts +4 -0
- package/src/coefficients/nthRoot.ts +149 -0
- package/src/coefficients/root.ts +299 -0
- package/src/geometry/circle.ts +386 -0
- package/src/geometry/geomMath.ts +70 -0
- package/src/geometry/index.ts +10 -0
- package/src/geometry/line.ts +677 -0
- package/src/geometry/line3.ts +206 -0
- package/src/geometry/plane3.ts +170 -0
- package/src/geometry/point.ts +66 -0
- package/src/geometry/sphere3.ts +214 -0
- package/src/geometry/triangle.ts +354 -0
- package/src/geometry/vector.ts +341 -0
- package/src/helpers.ts +35 -0
- package/src/index.ts +60 -0
- package/src/numeric.ts +199 -0
- package/src/pimath.interface.ts +160 -0
- package/src/randomization/algebra/rndEquation.ts +41 -0
- package/src/randomization/algebra/rndMonom.ts +39 -0
- package/src/randomization/algebra/rndPolynom.ts +86 -0
- package/src/randomization/coefficient/rndFraction.ts +38 -0
- package/src/randomization/geometry/rndCircle.ts +27 -0
- package/src/randomization/geometry/rndLine.ts +37 -0
- package/src/randomization/geometry/rndLine3.ts +27 -0
- package/src/randomization/geometry/rndVector.ts +63 -0
- package/src/randomization/random.ts +91 -0
- package/src/randomization/rndHelpers.ts +102 -0
- package/src/randomization/rndTypes.ts +63 -0
- package/types/algebra/equationSolver.d.ts +3 -0
- package/types/algebra/equationSolver.d.ts.map +1 -1
- package/types/algebra/polyFactor.d.ts +5 -0
- package/types/algebra/polyFactor.d.ts.map +1 -1
- package/types/analyze/solution.d.ts +21 -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/root.d.ts +38 -0
- package/types/coefficients/root.d.ts.map +1 -0
- package/types/geometry/point.d.ts +1 -1
- package/types/geometry/point.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 +1 -0
- 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 +26 -26
- package/types/pimath.interface.d.ts.map +1 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { Fraction } from "../coefficients/fraction"
|
|
2
|
+
import { Line } from "./line"
|
|
3
|
+
import { Vector } from "./vector"
|
|
4
|
+
import { Point } from "./point"
|
|
5
|
+
import type {remarquableLines} from "../pimath.interface"
|
|
6
|
+
|
|
7
|
+
export class Triangle {
|
|
8
|
+
#A: Point = new Point()
|
|
9
|
+
#B: Point = new Point()
|
|
10
|
+
#C: Point = new Point()
|
|
11
|
+
#lines: { 'AB': Line, 'AC': Line, 'BC': Line } = {
|
|
12
|
+
'AB': new Line(),
|
|
13
|
+
'AC': new Line(),
|
|
14
|
+
'BC': new Line()
|
|
15
|
+
}
|
|
16
|
+
#middles: { 'AB': Point, 'AC': Point, 'BC': Point } = {
|
|
17
|
+
'AB': new Point(),
|
|
18
|
+
'AC': new Point(),
|
|
19
|
+
'BC': new Point()
|
|
20
|
+
}
|
|
21
|
+
#remarquables: remarquableLines | null = null
|
|
22
|
+
|
|
23
|
+
constructor(...values: unknown[]) {
|
|
24
|
+
|
|
25
|
+
if (values.length > 0) {
|
|
26
|
+
this.parse(...values)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return this
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ------------------------------------------
|
|
33
|
+
// Getter and setters
|
|
34
|
+
// ------------------------------------------
|
|
35
|
+
|
|
36
|
+
get A(): Point {
|
|
37
|
+
return this.#A
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get B(): Point {
|
|
41
|
+
return this.#B
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get C(): Point {
|
|
45
|
+
return this.#C
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get AB(): Vector {
|
|
49
|
+
return this.#getSegment('A', 'B')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get BA(): Vector {
|
|
53
|
+
return this.#getSegment('B', 'A')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get BC(): Vector {
|
|
57
|
+
return this.#getSegment('B', 'C')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get CB(): Vector {
|
|
61
|
+
return this.#getSegment('C', 'B')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get AC(): Vector {
|
|
65
|
+
return this.#getSegment('A', 'C')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get CA(): Vector {
|
|
69
|
+
return this.#getSegment('C', 'A')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get isRectangle(): boolean {
|
|
73
|
+
if (this.AB.isNormalTo(this.BC)) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
if (this.AB.isNormalTo(this.AC)) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
if (this.BC.isNormalTo(this.AC)) {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get isEquilateral(): boolean {
|
|
87
|
+
return this.AB.normSquare.isEqual(this.BC.normSquare) &&
|
|
88
|
+
this.AB.normSquare.isEqual(this.AC.normSquare)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get isIsocele(): boolean {
|
|
92
|
+
return this.AB.normSquare.isEqual(this.BC.normSquare) ||
|
|
93
|
+
this.AB.normSquare.isEqual(this.AC.normSquare) ||
|
|
94
|
+
this.BC.normSquare.isEqual(this.AC.normSquare)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get lines(): { 'AB': Line, 'BC': Line, 'AC': Line } {
|
|
98
|
+
return this.#lines
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get remarquables(): remarquableLines | null {
|
|
102
|
+
return this.#remarquables
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ------------------------------------------
|
|
106
|
+
// Creation / parsing functions
|
|
107
|
+
// ------------------------------------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse values to a triangle. Supported formats:
|
|
111
|
+
* Vector2D, Vector2D, Vector2D
|
|
112
|
+
* x1, y1, x2, y2, x3, y3
|
|
113
|
+
* @param values
|
|
114
|
+
*/
|
|
115
|
+
parse = (...values: unknown[]): Triangle => {
|
|
116
|
+
if (values.length === 6) {
|
|
117
|
+
// Check if all values are number or fractions.
|
|
118
|
+
const v: Fraction[] = values.map((x: unknown) => new Fraction(x as string))
|
|
119
|
+
|
|
120
|
+
if (v.some(x => x.isNaN())) {
|
|
121
|
+
throw new Error('One of the values is not a valid number')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return this.parse(
|
|
125
|
+
new Vector(v[0], v[1]),
|
|
126
|
+
new Vector(v[2], v[3]),
|
|
127
|
+
new Vector(v[4], v[5]),
|
|
128
|
+
)
|
|
129
|
+
} else if (values.length === 3) {
|
|
130
|
+
// Possibilities:
|
|
131
|
+
// - Three points (or part of points, only dict for example, or array
|
|
132
|
+
// - Three lines
|
|
133
|
+
// - Three lines as text.
|
|
134
|
+
if (values.every((x: unknown) => typeof x === 'string')) {
|
|
135
|
+
// Three lines as text.
|
|
136
|
+
return this.parse(
|
|
137
|
+
...values.map((x) => {
|
|
138
|
+
return new Line(x)
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
} else if (values.every((x: unknown) => x instanceof Line)) {
|
|
142
|
+
// We have three lines
|
|
143
|
+
const AB: Line = (values[0]).clone()
|
|
144
|
+
const BC: Line = (values[1]).clone()
|
|
145
|
+
const AC: Line = (values[2]).clone()
|
|
146
|
+
this.#lines = { AB, BC, AC }
|
|
147
|
+
|
|
148
|
+
// Get the intersection points -> build the triangle using these intersection points.
|
|
149
|
+
let intersect = AB.intersection(BC)
|
|
150
|
+
if (intersect.hasIntersection) {
|
|
151
|
+
this.#B = intersect.point.clone()
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error('Lines do not intersect !')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
intersect = BC.intersection(AC)
|
|
157
|
+
if (intersect.hasIntersection) {
|
|
158
|
+
this.#C = intersect.point.clone()
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error('Lines do not intersect !')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
intersect = AC.intersection(AB)
|
|
164
|
+
if (intersect.hasIntersection) {
|
|
165
|
+
this.#A = intersect.point.clone()
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error('Lines do not intersect !')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
} else if (values.every((x: unknown) => (x instanceof Point))) {
|
|
171
|
+
// We have three points.
|
|
172
|
+
this.#A = (values[0]).clone()
|
|
173
|
+
this.#B = (values[1]).clone()
|
|
174
|
+
this.#C = (values[2]).clone()
|
|
175
|
+
this.#lines = {
|
|
176
|
+
'AB': new Line(this.#A, this.#B),
|
|
177
|
+
'BC': new Line(this.#B, this.#C),
|
|
178
|
+
'AC': new Line(this.#A, this.#C)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else if (values.length === 1) {
|
|
182
|
+
if (values[0] instanceof Triangle) {
|
|
183
|
+
return values[0].clone()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.#updateTriangle()
|
|
188
|
+
return this
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Clone the Triangle class
|
|
193
|
+
*/
|
|
194
|
+
clone = (): Triangle => {
|
|
195
|
+
return new Triangle(
|
|
196
|
+
this.#A.clone(),
|
|
197
|
+
this.#B.clone(),
|
|
198
|
+
this.#C.clone()
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
// ------------------------------------------
|
|
204
|
+
// Triangle operations and properties
|
|
205
|
+
// ------------------------------------------
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Generate the Line object for the three segments of the triangle
|
|
209
|
+
*/
|
|
210
|
+
#updateTriangle = () => {
|
|
211
|
+
this.#A.asPoint = true
|
|
212
|
+
this.#B.asPoint = true
|
|
213
|
+
this.#C.asPoint = true
|
|
214
|
+
|
|
215
|
+
this.#middles = {
|
|
216
|
+
'AB': new Point().middleOf(this.#A, this.#B),
|
|
217
|
+
'AC': new Point().middleOf(this.#A, this.#C),
|
|
218
|
+
'BC': new Point().middleOf(this.#B, this.#C)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.#remarquables = this.#calculateRemarquableLines()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get the Vector2D class for the given name
|
|
227
|
+
* @param ptName
|
|
228
|
+
*/
|
|
229
|
+
#getPointByName = (ptName: string): Point => {
|
|
230
|
+
switch (ptName.toUpperCase()) {
|
|
231
|
+
case 'A':
|
|
232
|
+
return this.#A
|
|
233
|
+
case 'B':
|
|
234
|
+
return this.#B
|
|
235
|
+
case 'C':
|
|
236
|
+
return this.#C
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Something went wrong ! Return the first point
|
|
240
|
+
return this.#A
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the vector for the segment given by name.
|
|
244
|
+
* @param ptName1
|
|
245
|
+
* @param ptName2
|
|
246
|
+
*/
|
|
247
|
+
#getSegment = (ptName1: string, ptName2: string): Vector => {
|
|
248
|
+
return new Vector(
|
|
249
|
+
this.#getPointByName(ptName1),
|
|
250
|
+
this.#getPointByName(ptName2)
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#calculateRemarquableLines = (): remarquableLines => {
|
|
255
|
+
|
|
256
|
+
const medians = {
|
|
257
|
+
'A': new Line().fromPoints(this.#A, this.#middles.BC),
|
|
258
|
+
'B': new Line().fromPoints(this.#B, this.#middles.AC),
|
|
259
|
+
'C': new Line().fromPoints(this.#C, this.#middles.AB),
|
|
260
|
+
'intersection': null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const mediators = {
|
|
264
|
+
'AB': new Line().fromPointAndNormal(this.#middles.AB, new Vector(this.#A, this.#B).normal()),
|
|
265
|
+
'AC': new Line().fromPointAndNormal(this.#middles.AC, new Vector(this.#A, this.#C).normal()),
|
|
266
|
+
'BC': new Line().fromPointAndNormal(this.#middles.BC, new Vector(this.#B, this.#C).normal()),
|
|
267
|
+
'intersection': null
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const heights = {
|
|
271
|
+
'A': new Line().fromPointAndNormal(this.#A, new Vector(this.#B, this.#C).normal()),
|
|
272
|
+
'B': new Line().fromPointAndNormal(this.#B, new Vector(this.#A, this.#C).normal()),
|
|
273
|
+
'C': new Line().fromPointAndNormal(this.#C, new Vector(this.#A, this.#B).normal()),
|
|
274
|
+
'intersection': null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const bA = this.#calculateBisectors('A'),
|
|
278
|
+
bB = this.#calculateBisectors('B'),
|
|
279
|
+
bC = this.#calculateBisectors('C')
|
|
280
|
+
|
|
281
|
+
const bisectors = {
|
|
282
|
+
'A': bA.internal,
|
|
283
|
+
'B': bB.internal,
|
|
284
|
+
'C': bB.internal,
|
|
285
|
+
'intersection': null
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const externalBisectors = {
|
|
289
|
+
'A': bA.external,
|
|
290
|
+
'B': bB.external,
|
|
291
|
+
'C': bC.external,
|
|
292
|
+
'intersection': null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const remarquables: remarquableLines = {
|
|
296
|
+
medians,
|
|
297
|
+
mediators,
|
|
298
|
+
heights,
|
|
299
|
+
bisectors,
|
|
300
|
+
externalBisectors
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// As it's a triangle, we assume the lines are intersecting and aren't parallel or superposed.
|
|
304
|
+
remarquables.medians.intersection = remarquables.medians.A.intersection(remarquables.medians.B).point
|
|
305
|
+
remarquables.mediators.intersection = remarquables.mediators.AB.intersection(remarquables.mediators.BC).point
|
|
306
|
+
remarquables.heights.intersection = remarquables.heights.A.intersection(remarquables.heights.B).point
|
|
307
|
+
remarquables.bisectors.intersection = remarquables.bisectors.A.intersection(remarquables.bisectors.B).point
|
|
308
|
+
|
|
309
|
+
// Everything was calculated for the remarquable lines.
|
|
310
|
+
return remarquables
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#calculateBisectors = (pt: string): { internal: Line, external: Line } => {
|
|
314
|
+
const tlines = this.lines
|
|
315
|
+
let d1, d2
|
|
316
|
+
|
|
317
|
+
if (pt === 'A') {
|
|
318
|
+
d1 = tlines.AB
|
|
319
|
+
d2 = tlines.AC
|
|
320
|
+
} else if (pt === 'B') {
|
|
321
|
+
d1 = tlines.AB
|
|
322
|
+
d2 = tlines.BC
|
|
323
|
+
} else if (pt === 'C') {
|
|
324
|
+
d1 = tlines.BC
|
|
325
|
+
d2 = tlines.AC
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (d1 === undefined || d2 === undefined) {
|
|
329
|
+
throw new Error(`The point ${pt} does not exist`)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const d1n = d1.n.simplify().norm
|
|
333
|
+
const d2n = d2.n.simplify().norm
|
|
334
|
+
const d1Equ = d1.getEquation().multiply(d2n)
|
|
335
|
+
const d2Equ = d2.getEquation().multiply(d1n)
|
|
336
|
+
|
|
337
|
+
const b1: Line = new Line(d1Equ.clone().subtract(d2Equ).simplify())
|
|
338
|
+
const b2: Line = new Line(d2Equ.clone().subtract(d1Equ).simplify())
|
|
339
|
+
|
|
340
|
+
// Must determine which bisectors is in the triangle
|
|
341
|
+
if (pt === 'A') {
|
|
342
|
+
return b1.hitSegment(this.B, this.C) ? { internal: b1, external: b2 } : { internal: b2, external: b1 }
|
|
343
|
+
}
|
|
344
|
+
if (pt === 'B') {
|
|
345
|
+
return b1.hitSegment(this.A, this.C) ? { internal: b1, external: b2 } : { internal: b2, external: b1 }
|
|
346
|
+
}
|
|
347
|
+
if (pt === 'C') {
|
|
348
|
+
return b1.hitSegment(this.B, this.A) ? { internal: b1, external: b2 } : { internal: b2, external: b1 }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Default returns the first bisector
|
|
352
|
+
return { internal: b1, external: b2 }
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
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/fraction"
|
|
7
|
+
import { Numeric } from "../numeric"
|
|
8
|
+
import { areVectorsColinears, areVectorsEquals, dotProduct } from "./geomMath"
|
|
9
|
+
|
|
10
|
+
export class Vector implements
|
|
11
|
+
IPiMathObject<Vector> {
|
|
12
|
+
#array: Fraction[] = []
|
|
13
|
+
#asPoint = false
|
|
14
|
+
|
|
15
|
+
constructor(...values: Vector[] | InputValue<Fraction>[]) {
|
|
16
|
+
if (values.length > 0) {
|
|
17
|
+
this.parse(...values)
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ------------------------------------------
|
|
22
|
+
// Getter and setter
|
|
23
|
+
// ------------------------------------------
|
|
24
|
+
get array(): Fraction[] {
|
|
25
|
+
return this.#array
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set array(value: Fraction[]) {
|
|
29
|
+
this.#array = value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get x(): Fraction {
|
|
33
|
+
return this.#array[0]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set x(value: Fraction | number | string) {
|
|
37
|
+
this.#array[0] = new Fraction(value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get y(): Fraction {
|
|
41
|
+
return this.#array[1]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set y(value: Fraction | number | string) {
|
|
45
|
+
this.#array[1] = new Fraction(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get z(): Fraction {
|
|
49
|
+
if (this.dimension < 3) { throw new Error('Vector is not 3D') }
|
|
50
|
+
return this.#array[2]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set z(value: Fraction | number | string) {
|
|
54
|
+
if (this.dimension < 3) { throw new Error('Vector is not 3D') }
|
|
55
|
+
this.#array[2] = new Fraction(value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get asPoint(): boolean {
|
|
59
|
+
return this.#asPoint
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
set asPoint(value: boolean) {
|
|
63
|
+
this.#asPoint = value
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
get normSquare(): Fraction {
|
|
68
|
+
// Get the norm square of the vector
|
|
69
|
+
return this.array.reduce((acc, x) => acc.add(x.clone().pow(2)), new Fraction(0))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get norm(): number {
|
|
73
|
+
return Math.sqrt(this.normSquare.value)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get tex(): string {
|
|
77
|
+
if (this.#asPoint) {
|
|
78
|
+
return `\\left(${this.array.map(x => x.tex).join(';')}\\right)`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return `\\begin{pmatrix} ${this.array.map(x => x.tex).join(' \\\\ ')} \\end{pmatrix}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get display(): string {
|
|
85
|
+
if (this.#asPoint) {
|
|
86
|
+
return `(${this.array.map(x => x.display).join(';')})`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return `((${this.array.map(x => x.display).join(',')}))`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setDimension(value = 2): this{
|
|
93
|
+
if (value < 2) {
|
|
94
|
+
throw new Error('Dimension must be at least 2')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (value < this.dimension) {
|
|
98
|
+
this.#array = this.#array.slice(0, value)
|
|
99
|
+
} else if(value > this.dimension) {
|
|
100
|
+
for(let i = this.dimension; i < value; i++) {
|
|
101
|
+
this.#array.push(new Fraction(0))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return this
|
|
106
|
+
}
|
|
107
|
+
get dimension(): number {
|
|
108
|
+
return this.array.length
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ------------------------------------------
|
|
112
|
+
// Creation / parsing functions
|
|
113
|
+
// ------------------------------------------
|
|
114
|
+
get isNull(): boolean {
|
|
115
|
+
return this.array.every(x => x.isZero())
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static asTex(...values: string[]): string {
|
|
119
|
+
return `\\begin{pmatrix} ${values.join(' \\\\ ')} \\end{pmatrix}`
|
|
120
|
+
}
|
|
121
|
+
static asDisplay(...values: string[]): string {
|
|
122
|
+
return `((${values.join(',')}))`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public defineAsPoint(value?: boolean): this {
|
|
126
|
+
this.#asPoint = value !== false
|
|
127
|
+
return this
|
|
128
|
+
}
|
|
129
|
+
public parse(...values: Vector[] | InputValue<Fraction>[]): this {
|
|
130
|
+
if (values.length === 0) {
|
|
131
|
+
throw new Error(`Invalid value`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (values.length === 1) {
|
|
135
|
+
if (values[0] instanceof Vector) {
|
|
136
|
+
return values[0].clone() as this
|
|
137
|
+
} else if (typeof values[0] === 'string') {
|
|
138
|
+
return this.fromString(values[0])
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error(`Invalid value`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Two values are given
|
|
145
|
+
if (values.length === 2) {
|
|
146
|
+
const [A, B] = values
|
|
147
|
+
|
|
148
|
+
// The two values are vectors
|
|
149
|
+
if (A instanceof Vector && B instanceof Vector) {
|
|
150
|
+
if (A.dimension !== B.dimension) { throw new Error('Vectors must have the same dimension') }
|
|
151
|
+
|
|
152
|
+
this.#array = B.array.map((x, index) => x.clone().subtract(A.array[index]))
|
|
153
|
+
return this
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Two ore more values as number, string, fraction...
|
|
158
|
+
this.#array = values.map(x => new Fraction(x as InputValue<Fraction>))
|
|
159
|
+
|
|
160
|
+
return this
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public clone(): Vector {
|
|
164
|
+
const V = new Vector()
|
|
165
|
+
V.array = this.copy()
|
|
166
|
+
V.asPoint = this.asPoint
|
|
167
|
+
return V
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public copy(): Fraction[] {
|
|
171
|
+
return this.#array.map(x => x.clone())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
zero = (): this => {
|
|
175
|
+
this.#array.forEach(x => x.zero())
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
one = (): this => {
|
|
180
|
+
this.zero()
|
|
181
|
+
this.x.one()
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
opposite = (): this => {
|
|
186
|
+
this.#array.forEach(x => x.opposite())
|
|
187
|
+
return this
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
add = (V: Vector): this => {
|
|
191
|
+
this.#array.forEach((x, index) => x.add(V.array[index]))
|
|
192
|
+
return this
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
subtract = (V: Vector): this => {
|
|
196
|
+
return this.add(V.clone().opposite())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
unit = (): this => {
|
|
200
|
+
const norm = this.norm
|
|
201
|
+
if (norm === 0) {
|
|
202
|
+
return this
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this.divideByScalar(norm)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
middleOf(V1: Vector, V2: Vector): this {
|
|
209
|
+
if (V1.dimension !== V2.dimension) { throw new Error('Vectors must be the same dimension') }
|
|
210
|
+
|
|
211
|
+
this.array = []
|
|
212
|
+
V1.array.forEach((x, index) => {
|
|
213
|
+
this.array.push(x.clone().add(V2.array[index]).divide(2))
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return this
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
translate(...values: Fraction[]): this {
|
|
220
|
+
this.array.forEach((x, index) => x.add(values[index]))
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
dot = (V: Vector): Fraction => {
|
|
226
|
+
return dotProduct(this, V)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
cross(value: Vector): Vector {
|
|
230
|
+
if (this.dimension !== 3 || value.dimension !== 3) {
|
|
231
|
+
throw new Error('Cross product can only be determined in 3D')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return new Vector(
|
|
235
|
+
this.y.clone().multiply(value.z).subtract(this.z.clone().multiply(value.y)),
|
|
236
|
+
this.z.clone().multiply(value.x).subtract(this.x.clone().multiply(value.z)),
|
|
237
|
+
this.x.clone().multiply(value.y).subtract(this.y.clone().multiply(value.x))
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
normal = (): this => {
|
|
242
|
+
if (this.dimension >= 3) { throw new Error('Normal vector can only be determined in 2D') }
|
|
243
|
+
|
|
244
|
+
const x = this.x.clone().opposite(),
|
|
245
|
+
y = this.y.clone()
|
|
246
|
+
this.#array[0] = y
|
|
247
|
+
this.#array[1] = x
|
|
248
|
+
return this
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
isZero(): boolean {
|
|
252
|
+
return this.array.every(x => x.isZero())
|
|
253
|
+
}
|
|
254
|
+
isOne(): boolean {
|
|
255
|
+
return this.array.every((x, index) => index === 0 ? x.isOne() : x.isZero())
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
isEqual = (v: Vector): boolean => {
|
|
259
|
+
return areVectorsEquals(this, v)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isColinearTo = (v: Vector): boolean => {
|
|
263
|
+
return areVectorsColinears(this, v)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
isNormalTo = (v: Vector): boolean => {
|
|
267
|
+
return this.dot(v).isZero()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
multiplyByScalar = (k: InputValue<Fraction>): this => {
|
|
271
|
+
const scalar = new Fraction(k)
|
|
272
|
+
this.array.forEach(x => x.multiply(scalar))
|
|
273
|
+
return this
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
divideByScalar = (k: InputValue<Fraction>): this => {
|
|
277
|
+
return this.multiplyByScalar(new Fraction(k).inverse())
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
simplify = (): this => {
|
|
281
|
+
// Multiply by the lcm of denominators.
|
|
282
|
+
return this
|
|
283
|
+
.multiplyByScalar(
|
|
284
|
+
Numeric.lcm(...this.array.map(x => x.denominator))
|
|
285
|
+
)
|
|
286
|
+
.divideByScalar(
|
|
287
|
+
Numeric.gcd(...this.array.map(x => x.numerator))
|
|
288
|
+
).
|
|
289
|
+
multiplyByScalar(
|
|
290
|
+
this.x.isNegative() ? -1 : 1
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
angle = (V: Vector, sharp?: boolean, radian?: boolean): number => {
|
|
295
|
+
|
|
296
|
+
let scalar = this.dot(V).value
|
|
297
|
+
if (sharp) {
|
|
298
|
+
scalar = Math.abs(scalar)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const toDegree = radian ? 1 : 180 / Math.PI
|
|
302
|
+
|
|
303
|
+
return toDegree * Math.acos(scalar / (this.norm * V.norm))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
fromString = (value: string): this => {
|
|
308
|
+
// Remove the first letter if it's a parenthesis.
|
|
309
|
+
if (value.startsWith('(')) {
|
|
310
|
+
value = value.substring(1)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Remove the last letter if it's a parenthesis.
|
|
314
|
+
if (value.endsWith(')')) {
|
|
315
|
+
value = value.substring(0, value.length - 1)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Split comma, semi colon or single space.
|
|
319
|
+
const components = value.split(/[,;\s]/g)
|
|
320
|
+
.filter((v) => v.trim() !== '')
|
|
321
|
+
|
|
322
|
+
// there must be at least two components.
|
|
323
|
+
if (components.length < 2) {
|
|
324
|
+
return this
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Validate the fraction values.
|
|
328
|
+
this.#array = components.map(x => new Fraction(x))
|
|
329
|
+
return this
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
distanceTo(item: Vector): { value: number, fraction: Fraction, tex: string } {
|
|
333
|
+
const V = new Vector(this, item)
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
value: V.norm,
|
|
337
|
+
fraction: V.normSquare,
|
|
338
|
+
tex: V.tex
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|