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,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This class works for 2d line in a plane.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Fraction } from "../coefficients/fraction"
|
|
6
|
+
import { Polynom } from "../algebra/polynom"
|
|
7
|
+
import { Monom } from "../algebra/monom"
|
|
8
|
+
import { randomIntSym } from "../randomization/rndHelpers"
|
|
9
|
+
import { Vector } from "./vector"
|
|
10
|
+
import { Point } from "./point"
|
|
11
|
+
import {Line3Propriety} from "../pimath.interface"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class Line3 {
|
|
15
|
+
// A line is defined as the canonical form
|
|
16
|
+
static PERPENDICULAR = Line3Propriety.Perpendicular
|
|
17
|
+
static PARALLEL = Line3Propriety.Parallel
|
|
18
|
+
// ax + by + c = 0
|
|
19
|
+
#OA: Point = new Point()
|
|
20
|
+
#d: Vector = new Vector()
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Value can be a mix of:
|
|
24
|
+
*
|
|
25
|
+
* @param values
|
|
26
|
+
*/
|
|
27
|
+
constructor(A: Point, B: Point)
|
|
28
|
+
constructor(A: Point, d: Vector)
|
|
29
|
+
constructor(A: Point, d: Vector | Point) {
|
|
30
|
+
this.#OA = A.clone()
|
|
31
|
+
this.#d = d.asPoint ? new Vector(A, d) : d.clone()
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get OA(): Point {
|
|
36
|
+
return this.#OA
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set OA(value: Point) {
|
|
40
|
+
this.#OA = value
|
|
41
|
+
}
|
|
42
|
+
get point(): Point {
|
|
43
|
+
return this.#OA.clone()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get d(): Vector {
|
|
47
|
+
return this.#d
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
set d(value: Vector) {
|
|
51
|
+
this.#d = value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get tex(): { parametric: string, system: string, cartesian: string } {
|
|
55
|
+
return {
|
|
56
|
+
parametric: `${Vector.asTex('x', 'y', 'z')} = ${Vector.asTex(this.#OA.x.tex, this.#OA.y.tex, this.#OA.z.tex)} + k\\cdot ${Vector.asTex(this.#d.x.tex, this.#d.y.tex, this.#d.z.tex)}`,
|
|
57
|
+
system: `\\left\\{\\begin{aligned}
|
|
58
|
+
x &= ${(new Polynom(this.#OA.x)
|
|
59
|
+
.add(new Monom(this.#d.x).multiply(new Monom('k'))))
|
|
60
|
+
.reorder('k', true)
|
|
61
|
+
.tex}\\\\
|
|
62
|
+
y &= ${(new Polynom(this.#OA.y)
|
|
63
|
+
.add(new Monom(this.#d.y).multiply(new Monom('k'))))
|
|
64
|
+
.reorder('k', true)
|
|
65
|
+
.tex}\\\\
|
|
66
|
+
z &= ${(new Polynom(this.#OA.z)
|
|
67
|
+
.add(new Monom(this.#d.z).multiply(new Monom('k'))))
|
|
68
|
+
.reorder('k', true)
|
|
69
|
+
.tex}
|
|
70
|
+
\\end{aligned}\\right.`,
|
|
71
|
+
cartesian: `\\frac{ ${new Polynom('x', 1, this.#OA.x.clone().opposite()).tex} }{ ${this.direction.x.tex} } = \\frac{ ${new Polynom('y', 1, this.#OA.y.clone().opposite()).tex} }{ ${this.direction.y.tex} } = \\frac{ ${new Polynom('z', 1, this.#OA.z.clone().opposite()).tex} }{ ${this.direction.z.tex} }`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get display(): { parametric: string, system: string, cartesian: string } {
|
|
76
|
+
const OAx = this.#OA.x.display
|
|
77
|
+
const OAy = this.#OA.y.display
|
|
78
|
+
const OAz = this.#OA.z.display
|
|
79
|
+
const n = this.direction.simplify()
|
|
80
|
+
const nx = n.x.display
|
|
81
|
+
const ny = n.y.display
|
|
82
|
+
const nz = n.z.display
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
parametric: `${Vector.asDisplay('x', 'y', 'z')} = ${Vector.asDisplay(this.#OA.x.display, this.#OA.y.display, this.#OA.z.display)} + k\\cdot ${Vector.asDisplay(this.#d.x.display, this.#d.y.display, this.#d.z.display)}`,
|
|
86
|
+
system: '',
|
|
87
|
+
cartesian: `(x-${OAx})/${nx} = (y-${OAy})/${ny} = (z-${OAz})/${nz}`
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get direction(): Vector {
|
|
92
|
+
return this.#d.clone()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
clone = (): this => {
|
|
96
|
+
this.#d = this.#d.clone()
|
|
97
|
+
this.#OA = this.#OA.clone()
|
|
98
|
+
|
|
99
|
+
return this
|
|
100
|
+
}
|
|
101
|
+
// ------------------------------------------
|
|
102
|
+
// Mathematical operations
|
|
103
|
+
// ------------------------------------------
|
|
104
|
+
isOnLine = (pt: Point): boolean => {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
isParallelTo = (line: Line3): boolean => {
|
|
109
|
+
// Do they have the isSame direction ?
|
|
110
|
+
throw new Error('Method not implemented.')
|
|
111
|
+
}
|
|
112
|
+
isSameAs = (line: Line3): boolean => {
|
|
113
|
+
throw new Error('Method not implemented.')
|
|
114
|
+
}
|
|
115
|
+
isPerpendicularTo = (line: Line3): boolean => {
|
|
116
|
+
throw new Error('Method not implemented.')
|
|
117
|
+
}
|
|
118
|
+
isVertical = (): boolean => {
|
|
119
|
+
throw new Error('Method not implemented.')
|
|
120
|
+
}
|
|
121
|
+
simplify = (): this => {
|
|
122
|
+
throw new Error('Method not implemented.')
|
|
123
|
+
// const lcm = Numeric.lcm(this.#a.denominator, this.#b.denominator, this.#c.denominator),
|
|
124
|
+
// gcd = Numeric.gcd(this.#a.numerator, this.#b.numerator, this.#c.numerator)
|
|
125
|
+
|
|
126
|
+
// this.fromCoefficient(
|
|
127
|
+
// this.#a.clone().multiply(lcm).divide(gcd),
|
|
128
|
+
// this.#b.clone().multiply(lcm).divide(gcd),
|
|
129
|
+
// this.#c.clone().multiply(lcm).divide(gcd),
|
|
130
|
+
// )
|
|
131
|
+
|
|
132
|
+
// return this
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
intersection = (line: Line3): { point: Vector, hasIntersection: boolean, isParallel: boolean, isSame: boolean } => {
|
|
136
|
+
|
|
137
|
+
throw new Error('Method not implemented.')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
distanceTo(pt: Point): { value: number, fraction: Fraction, tex: string } {
|
|
141
|
+
// Distance is:
|
|
142
|
+
// |(x - x0) x d| / |d|
|
|
143
|
+
const AP = new Vector(this.#OA, pt),
|
|
144
|
+
d = this.direction,
|
|
145
|
+
d2 = this.direction.normSquare,
|
|
146
|
+
num2 = AP.cross(d).normSquare,
|
|
147
|
+
num2d2 = num2.clone().divide(d2),
|
|
148
|
+
dnum = num2d2.clone().sqrt()
|
|
149
|
+
|
|
150
|
+
console.log('CROSS', AP.cross(d).display)
|
|
151
|
+
return {
|
|
152
|
+
value: Math.sqrt(num2d2.value),
|
|
153
|
+
fraction: num2d2.clone().sqrt(),
|
|
154
|
+
tex: dnum.isExact() ? dnum.tex : `\\sqrt{${num2d2.tex}}`
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
hitSegment(A: Point, B: Point): boolean {
|
|
159
|
+
const iPt = this.intersection(
|
|
160
|
+
new Line3(A, B)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
// There is an intersection point
|
|
164
|
+
if (iPt.hasIntersection) {
|
|
165
|
+
return iPt.point.x.value >= Math.min(A.x.value, B.x.value)
|
|
166
|
+
&& iPt.point.x.value <= Math.max(A.x.value, B.x.value)
|
|
167
|
+
&& iPt.point.y.value >= Math.min(A.y.value, B.y.value)
|
|
168
|
+
&& iPt.point.y.value <= Math.max(A.y.value, B.y.value)
|
|
169
|
+
&& iPt.point.z.value >= Math.min(A.z.value, B.z.value)
|
|
170
|
+
&& iPt.point.z.value <= Math.max(A.z.value, B.z.value)
|
|
171
|
+
}
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// getValueAtX = (value: Fraction | number): Fraction => {
|
|
176
|
+
// const equ = this.equation.clone().isolate('y'),
|
|
177
|
+
// F = new Fraction(value)
|
|
178
|
+
|
|
179
|
+
// if (equ instanceof Equation) {
|
|
180
|
+
// return equ.right.evaluate({ x: F }) as Fraction
|
|
181
|
+
// }
|
|
182
|
+
// return new Fraction().invalid()
|
|
183
|
+
// }
|
|
184
|
+
|
|
185
|
+
// getValueAtY = (value: Fraction | number): Fraction => {
|
|
186
|
+
// const equ = this.equation.clone().isolate('x'),
|
|
187
|
+
// F = new Fraction(value)
|
|
188
|
+
|
|
189
|
+
// if (equ instanceof Equation) {
|
|
190
|
+
// return equ.right.evaluate({ y: F }) as Fraction
|
|
191
|
+
// }
|
|
192
|
+
|
|
193
|
+
// return new Fraction().invalid()
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
randomPoint = (max = 5): Point => {
|
|
197
|
+
const A = this.#OA.clone(),
|
|
198
|
+
k = new Fraction(randomIntSym(max, false))
|
|
199
|
+
|
|
200
|
+
return new Point(
|
|
201
|
+
A.x.clone().add(this.#d.x.clone().multiply(k)),
|
|
202
|
+
A.y.clone().add(this.#d.y.clone().multiply(k)),
|
|
203
|
+
A.z.clone().add(this.#d.z.clone().multiply(k))
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Equation } from "../algebra/equation"
|
|
2
|
+
import { Polynom } from "../algebra/polynom"
|
|
3
|
+
import { Fraction } from "../coefficients/fraction"
|
|
4
|
+
import { Line3 } from "./line3"
|
|
5
|
+
import { Point } from "./point"
|
|
6
|
+
import { Vector } from "./vector"
|
|
7
|
+
import type {Plane3Config} from "../pimath.interface"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export class Plane3 {
|
|
12
|
+
#normal: Vector = new Vector(0, 0, 1)
|
|
13
|
+
#point: Point = new Point(0, 0, 0)
|
|
14
|
+
|
|
15
|
+
constructor(config?: Plane3Config) {
|
|
16
|
+
if (config) {
|
|
17
|
+
this.parse(config)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get normal(): Vector {
|
|
24
|
+
return this.#normal
|
|
25
|
+
}
|
|
26
|
+
set normal(value: Vector) {
|
|
27
|
+
this.#normal = value
|
|
28
|
+
this.#normal.asPoint = false
|
|
29
|
+
}
|
|
30
|
+
get point(): Point {
|
|
31
|
+
return this.#point
|
|
32
|
+
}
|
|
33
|
+
set point(value: Point) {
|
|
34
|
+
this.#point = value
|
|
35
|
+
this.#point.asPoint = true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get a(): Fraction {
|
|
39
|
+
return this.#normal.x
|
|
40
|
+
}
|
|
41
|
+
get b(): Fraction {
|
|
42
|
+
return this.#normal.y
|
|
43
|
+
}
|
|
44
|
+
get c(): Fraction {
|
|
45
|
+
return this.#normal.z
|
|
46
|
+
}
|
|
47
|
+
get d(): Fraction {
|
|
48
|
+
return this.#normal.dot(this.#point).opposite()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get tex(): string {
|
|
52
|
+
// return the cartesian equation of the plane
|
|
53
|
+
return new Equation(
|
|
54
|
+
new Polynom('xyz', this.a, this.b, this.c, this.d),
|
|
55
|
+
new Polynom(0)
|
|
56
|
+
).reduce().tex
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get display(): string {
|
|
60
|
+
// return the cartesian equation of the plane
|
|
61
|
+
return new Equation(
|
|
62
|
+
new Polynom('xyz', this.a, this.b, this.c, this.d),
|
|
63
|
+
new Polynom(0)
|
|
64
|
+
).reduce().display
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
parse(config: Plane3Config) {
|
|
68
|
+
if (config.point && config.normal) {
|
|
69
|
+
this.point = config.point
|
|
70
|
+
this.normal = config.normal
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (config.point && config.directions?.length === 2) {
|
|
75
|
+
this.point = config.point
|
|
76
|
+
const [v1, v2] = config.directions
|
|
77
|
+
this.normal = v1.cross(v2)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (config.equation) {
|
|
82
|
+
const cartesian = config.equation.moveLeft().reduce().left
|
|
83
|
+
|
|
84
|
+
const a = cartesian.monomByLetter('x').coefficient
|
|
85
|
+
const b = cartesian.monomByLetter('y').coefficient
|
|
86
|
+
const c = cartesian.monomByLetter('z').coefficient
|
|
87
|
+
const d = cartesian.monomByDegree(0).coefficient
|
|
88
|
+
|
|
89
|
+
// Get the normal vector
|
|
90
|
+
this.normal = new Vector(a, b, c)
|
|
91
|
+
|
|
92
|
+
// Get a point on the plane
|
|
93
|
+
if (a.isNotZero()) {
|
|
94
|
+
this.point = new Point(d.clone().divide(a).opposite(), 0, 0)
|
|
95
|
+
} else if (b.isNotZero()) {
|
|
96
|
+
this.point = new Point(0, d.clone().divide(b).opposite(), 0)
|
|
97
|
+
} else {
|
|
98
|
+
this.point = new Point(0, 0, d.clone().divide(c).opposite())
|
|
99
|
+
}
|
|
100
|
+
// Make sure it's considered as point
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (config.points?.length === 3 && config.points.every(p => p instanceof Vector)) {
|
|
105
|
+
const A = config.points[0]
|
|
106
|
+
const B = config.points[1]
|
|
107
|
+
const C = config.points[2]
|
|
108
|
+
|
|
109
|
+
const AB = new Vector(A, B)
|
|
110
|
+
const AC = new Vector(A, C)
|
|
111
|
+
this.normal = AB.cross(AC)
|
|
112
|
+
this.point = A
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (config.coefficients?.length === 4) {
|
|
117
|
+
const [a, b, c, d] = config.coefficients
|
|
118
|
+
this.normal = new Vector(a, b, c)
|
|
119
|
+
this.point = new Point(0, 0, -d)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
angle(vector: Vector, sharp?: boolean, radian?: boolean): number
|
|
125
|
+
angle(line: Line3, sharp?: boolean, radian?: boolean): number
|
|
126
|
+
angle(plane: Plane3, sharp?: boolean, radian?: boolean): number
|
|
127
|
+
angle(value: Plane3 | Line3 | Vector, sharp?: boolean, radian?: boolean): number {
|
|
128
|
+
if (value instanceof Plane3) {
|
|
129
|
+
return this.normal.angle(value.normal, sharp, radian)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let direction: Vector
|
|
133
|
+
if (value instanceof Vector) {
|
|
134
|
+
if (value.dimension !== 3) {
|
|
135
|
+
throw new Error('Vector is not 3D')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
direction = value
|
|
139
|
+
} else {
|
|
140
|
+
direction = value.direction
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const a90 = radian ? Math.PI / 2 : 90
|
|
144
|
+
return a90 - this.normal.angle(direction, true, radian)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
distanceTo(point: Vector): number {
|
|
148
|
+
return this.normal.dot(point).add(this.d).abs().value / this.normal.norm
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
intersectWithLine(line: Line3): Point {
|
|
152
|
+
const { point, direction } = line
|
|
153
|
+
const t = this.normal.dot(point).add(this.d).divide(this.normal.dot(direction).opposite())
|
|
154
|
+
return point.clone().add(direction.clone().multiplyByScalar(t))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
intersectWithPlane(plane: Plane3): Line3 {
|
|
158
|
+
const direction = this.normal.cross(plane.normal)
|
|
159
|
+
|
|
160
|
+
// Solve the system:
|
|
161
|
+
// p1 // p2 // z=0
|
|
162
|
+
const pt = new Point(0, 0, 0)
|
|
163
|
+
throw new Error('Intersection with plane not yet implemented !')
|
|
164
|
+
return new Line3(pt, direction)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
isPointOnPlane(pt: Point): boolean {
|
|
168
|
+
return this.normal.dot(pt).add(this.d).isZero()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector2D module contains everything necessary to handle 2d vectors.
|
|
3
|
+
* @module Point
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {Fraction} from "../coefficients"
|
|
7
|
+
import type {InputValue} from "../pimath.interface"
|
|
8
|
+
import {Vector} from "./vector"
|
|
9
|
+
|
|
10
|
+
export class Point extends Vector {
|
|
11
|
+
|
|
12
|
+
constructor()
|
|
13
|
+
constructor(value: Vector)
|
|
14
|
+
constructor(start: Vector, end: Vector)
|
|
15
|
+
constructor(...values: InputValue<Fraction>[])
|
|
16
|
+
constructor(...values: Vector[] | InputValue<Fraction>[]) {
|
|
17
|
+
super()
|
|
18
|
+
|
|
19
|
+
this.asPoint = true
|
|
20
|
+
|
|
21
|
+
// Initialize the vector
|
|
22
|
+
if (values.length > 0) {
|
|
23
|
+
this.parse(...values)
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
public override parse(...values: Vector[] | InputValue<Fraction>[]): this {
|
|
28
|
+
if (values.length === 1) {
|
|
29
|
+
if (values[0] instanceof Vector) {
|
|
30
|
+
this.array = values[0].copy()
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof values[0] === 'string') {
|
|
35
|
+
this.fromString(values[0])
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if (values.length > 1) {
|
|
42
|
+
if (values.some(x => x instanceof Vector)) {
|
|
43
|
+
throw new Error('Creating a point with multiple argument requires an input fraction')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const nbs: Fraction[] = values.map(x => new Fraction(x as InputValue<Fraction>))
|
|
47
|
+
|
|
48
|
+
if (nbs.some(x => x.isNaN())) {
|
|
49
|
+
throw new Error('The value is not a valid point sting (a,b): ' + values.join(','))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.array = nbs
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
public override clone(): Point {
|
|
60
|
+
const V = new Point()
|
|
61
|
+
V.array = this.copy()
|
|
62
|
+
V.asPoint = true
|
|
63
|
+
return V
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {Point} from "./point"
|
|
2
|
+
import {Fraction} from "../coefficients"
|
|
3
|
+
import {Equation, Polynom} from "../algebra"
|
|
4
|
+
import type {InputValue} from "../pimath.interface"
|
|
5
|
+
|
|
6
|
+
enum SPHERE3_FORMAT {
|
|
7
|
+
DEVELOPPED,
|
|
8
|
+
CENTER_RADIUS
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export enum SPHERE3_RELATIVE_POSITION {
|
|
12
|
+
INTERIOR,
|
|
13
|
+
EXTERIOR,
|
|
14
|
+
SECANT,
|
|
15
|
+
TANGENT_INSIDE,
|
|
16
|
+
TANGENT_OUTSIDE,
|
|
17
|
+
SUPERPOSED,
|
|
18
|
+
CONCENTRIC
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Sphere3 {
|
|
22
|
+
#center: Point | undefined = undefined
|
|
23
|
+
#squareRadius: Fraction | undefined = undefined
|
|
24
|
+
#equation: Equation | undefined = undefined
|
|
25
|
+
#format: SPHERE3_FORMAT = SPHERE3_FORMAT.CENTER_RADIUS
|
|
26
|
+
|
|
27
|
+
constructor(center?: Point, radius?: InputValue<Fraction>) {
|
|
28
|
+
if (center && radius) {
|
|
29
|
+
this.#center = center
|
|
30
|
+
this.#squareRadius = new Fraction(radius).clone().pow(2)
|
|
31
|
+
this.#computeEquation()
|
|
32
|
+
}
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fromEquation(equation: Equation | string): this {
|
|
37
|
+
const equ = new Equation(equation).moveLeft().reduce()
|
|
38
|
+
|
|
39
|
+
// Check that x, y, z has the same power and same coefficient.
|
|
40
|
+
const letters = ['x', 'y', 'z']
|
|
41
|
+
|
|
42
|
+
if (letters.some((letter) => equ.degree(letter).value !== 2)){
|
|
43
|
+
return this.makeUndefined()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const coefficient = equ.left.monomByDegree(2, 'x').coefficient
|
|
47
|
+
if (letters.some((letter) => equ.left.monomByDegree(2, letter).coefficient.isNotEqual(coefficient))) {
|
|
48
|
+
return this.makeUndefined()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.#center = new Point(
|
|
52
|
+
equ.left.monomByDegree(1, 'x').coefficient.clone().opposite().divide(2),
|
|
53
|
+
equ.left.monomByDegree(1, 'y').coefficient.clone().opposite().divide(2),
|
|
54
|
+
equ.left.monomByDegree(1, 'z').coefficient.clone().opposite().divide(2)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
this.#squareRadius = equ.left.monomByDegree(0)
|
|
58
|
+
.coefficient.clone().opposite()
|
|
59
|
+
.add(this.#center.x.clone().pow(2))
|
|
60
|
+
.add(this.#center.y.clone().pow(2))
|
|
61
|
+
.add(this.#center.z.clone().pow(2))
|
|
62
|
+
|
|
63
|
+
this.#computeEquation()
|
|
64
|
+
return this
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get center(): Point {
|
|
68
|
+
if (this.#center === undefined) {
|
|
69
|
+
throw new Error('Sphere3 is undefined')
|
|
70
|
+
}
|
|
71
|
+
return this.#center
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get squareRadius(): Fraction {
|
|
75
|
+
if (this.#squareRadius === undefined) {
|
|
76
|
+
throw new Error('Sphere3 is undefined')
|
|
77
|
+
}
|
|
78
|
+
return this.#squareRadius
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get radius(): { tex: string, display: string, value: number } {
|
|
82
|
+
if (this.#squareRadius === undefined) {
|
|
83
|
+
throw new Error('Sphere3 is undefined')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this.#squareRadius.isSquare()) {
|
|
87
|
+
return {
|
|
88
|
+
tex: this.#squareRadius.clone().sqrt().tex,
|
|
89
|
+
display: this.#squareRadius.clone().sqrt().display,
|
|
90
|
+
value: this.#squareRadius.clone().sqrt().value
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
return {
|
|
94
|
+
tex: `\\sqrt{${this.#squareRadius.tex}}`,
|
|
95
|
+
display: `sqrt(${this.#squareRadius.display})`,
|
|
96
|
+
value: this.#squareRadius.clone().sqrt().value
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get equation(): Equation {
|
|
102
|
+
if (this.#equation === undefined) {
|
|
103
|
+
throw new Error('Sphere3 is undefined')
|
|
104
|
+
}
|
|
105
|
+
return this.#equation
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
makeUndefined(): this {
|
|
109
|
+
this.#center = undefined
|
|
110
|
+
this.#squareRadius = undefined
|
|
111
|
+
this.#equation = undefined
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get centerRadius(): this {
|
|
116
|
+
this.#format = SPHERE3_FORMAT.CENTER_RADIUS
|
|
117
|
+
return this
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get developped(): this {
|
|
121
|
+
this.#format = SPHERE3_FORMAT.DEVELOPPED
|
|
122
|
+
return this
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
get tex(): string {
|
|
127
|
+
return this.#output(true)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get display(): string {
|
|
131
|
+
return this.#output(false)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#output = (asTex: boolean): string => {
|
|
135
|
+
if (this.#equation === undefined) {
|
|
136
|
+
throw new Error('Sphere3 is undefined')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.#format === SPHERE3_FORMAT.DEVELOPPED) {
|
|
140
|
+
return asTex ? this.#equation.tex : this.#equation.display
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const output: string[] = []
|
|
144
|
+
const letters: ('x' | 'y' | 'z')[] = ['x', 'y', 'z']
|
|
145
|
+
|
|
146
|
+
letters.forEach((letter: 'x' | 'y' | 'z') => {
|
|
147
|
+
if (this.center[letter].isZero()) {
|
|
148
|
+
output.push(`${letter}^2`)
|
|
149
|
+
} else {
|
|
150
|
+
const P = new Polynom(letter).subtract(this.center[letter])
|
|
151
|
+
output.push(
|
|
152
|
+
asTex ?
|
|
153
|
+
`\\(${P.tex}\\)^2` :
|
|
154
|
+
`(${P.display})^2`
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return output.join('+') + '=' + (asTex ? this.squareRadius.tex : this.squareRadius.display)
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#computeEquation(): void {
|
|
164
|
+
this.#equation = new Equation(
|
|
165
|
+
new Polynom('x').subtract(this.center.x).pow(2)
|
|
166
|
+
.add(
|
|
167
|
+
new Polynom('y').subtract(this.center.y).pow(2)
|
|
168
|
+
)
|
|
169
|
+
.add(
|
|
170
|
+
new Polynom('z').subtract(this.center.z).pow(2)
|
|
171
|
+
),
|
|
172
|
+
new Polynom(this.squareRadius)
|
|
173
|
+
).reduce()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static RELATIVE_POSITION = SPHERE3_RELATIVE_POSITION
|
|
177
|
+
relativePosition = (S: Sphere3): SPHERE3_RELATIVE_POSITION => {
|
|
178
|
+
const distance = this.center.distanceTo(S.center).value
|
|
179
|
+
const r1 = this.radius.value
|
|
180
|
+
const r2 = S.radius.value
|
|
181
|
+
|
|
182
|
+
if (distance > r1 + r2) {
|
|
183
|
+
return SPHERE3_RELATIVE_POSITION.EXTERIOR
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (distance === r1 + r2) {
|
|
187
|
+
return SPHERE3_RELATIVE_POSITION.TANGENT_OUTSIDE
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if(distance===0) {
|
|
191
|
+
return r1===r2 ? SPHERE3_RELATIVE_POSITION.SUPERPOSED : SPHERE3_RELATIVE_POSITION.CONCENTRIC
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (distance === Math.abs(r1 - r2)) {
|
|
195
|
+
return SPHERE3_RELATIVE_POSITION.TANGENT_INSIDE
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if (distance < Math.abs(r1 - r2)) {
|
|
200
|
+
return SPHERE3_RELATIVE_POSITION.INTERIOR
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return SPHERE3_RELATIVE_POSITION.SECANT
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
isPointOnSphere = (P: Point): boolean => {
|
|
208
|
+
return this.#equation?.test({
|
|
209
|
+
x: P.x,
|
|
210
|
+
y: P.y,
|
|
211
|
+
z: P.z
|
|
212
|
+
}) ?? false
|
|
213
|
+
}
|
|
214
|
+
}
|