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