wt-huatu 1.3.4
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/Readme.md +223 -0
- package/component/HuatuComponentCache.js +89 -0
- package/component/HuatuComponentDef.js +249 -0
- package/component/HuatuComponentPlacement.js +172 -0
- package/component/HuatuComponentPort.js +58 -0
- package/component/IHuatuComponent.js +2 -0
- package/for-node/node-cli-funcs.js +36 -0
- package/geometry/AngDeg.js +68 -0
- package/geometry/Angle.js +77 -0
- package/geometry/Bezier.js +623 -0
- package/geometry/Circle.js +143 -0
- package/geometry/Ellipse.js +546 -0
- package/geometry/EllipticalArc.js +337 -0
- package/geometry/IGeometry.js +1 -0
- package/geometry/Intersection.js +601 -0
- package/geometry/Line.js +136 -0
- package/geometry/LineSeg.js +179 -0
- package/geometry/Point.js +88 -0
- package/geometry/PolyLine.js +97 -0
- package/geometry/Polygon.js +122 -0
- package/geometry/Rect.js +149 -0
- package/geometry/Ring.js +91 -0
- package/geometry/SegmentModel.js +206 -0
- package/geometry/Triangle.js +168 -0
- package/geometry/Vector.js +92 -0
- package/huatu/Huatu.js +4279 -0
- package/huatu/HuatuAngleMark.js +400 -0
- package/huatu/HuatuBlock.js +26 -0
- package/huatu/HuatuClipPath.js +29 -0
- package/huatu/HuatuCompDrawing.js +77 -0
- package/huatu/HuatuDimMark.js +579 -0
- package/huatu/HuatuDrawing.js +185 -0
- package/huatu/HuatuDrawingGroup.js +52 -0
- package/huatu/HuatuJob.js +347 -0
- package/huatu/HuatuLabel.js +311 -0
- package/huatu/HuatuLabelStyle.js +190 -0
- package/huatu/HuatuLayer.js +10 -0
- package/huatu/HuatuMarker.js +209 -0
- package/huatu/HuatuParser.js +435 -0
- package/huatu/HuatuPath.js +131 -0
- package/huatu/HuatuPathSeries.js +51 -0
- package/huatu/HuatuPattern.js +54 -0
- package/huatu/HuatuPoint.js +20 -0
- package/huatu/HuatuPointSeries.js +67 -0
- package/huatu/HuatuStyle.js +394 -0
- package/huatu/HuatuSymbol.js +81 -0
- package/huatu/HuatuSymbolArray.js +235 -0
- package/huatu/HuatuTransform.js +113 -0
- package/huatu/HuatuVar.js +17 -0
- package/huatu/HuatuYml.js +271 -0
- package/huatu/IHuatu.js +2 -0
- package/huatu/IHuatuYml.js +1 -0
- package/huatu/IndentParsingStack.js +82 -0
- package/index.d.ts +1747 -0
- package/index.js +247 -0
- package/math/Complex.js +72 -0
- package/math/Matrix.js +31 -0
- package/math/NumberRange.js +38 -0
- package/math/VarFuncs.js +24 -0
- package/math/WtMath.js +217 -0
- package/package.json +34 -0
- package/path/PathBezier2.js +75 -0
- package/path/PathBezier3.js +89 -0
- package/path/PathCircle.js +82 -0
- package/path/PathConfig.js +81 -0
- package/path/PathContinuousSegmentedPath.js +390 -0
- package/path/PathEllipse.js +99 -0
- package/path/PathEllipticalArc.js +111 -0
- package/path/PathGeneric.js +59 -0
- package/path/PathLine.js +75 -0
- package/path/PathLines.js +46 -0
- package/path/PathPolygon.js +142 -0
- package/path/PathPolyline.js +266 -0
- package/path/PathRect.js +125 -0
- package/path/PathRing.js +51 -0
- package/path/PathSegmentedPath.js +199 -0
- package/path/PathTriangle.js +90 -0
- package/presets/marker.js +37 -0
- package/presets/pattern.js +85 -0
- package/shape/SvgShapeArrowHead.js +92 -0
- package/shape/SvgShapeCircle.js +26 -0
- package/shape/SvgShapeCross.js +43 -0
- package/shape/SvgShapeEllipse.js +28 -0
- package/shape/SvgShapeGeneric.js +50 -0
- package/shape/SvgShapeHeart.js +40 -0
- package/shape/SvgShapeIsoscelesTriangle.js +52 -0
- package/shape/SvgShapePolygon.js +39 -0
- package/shape/SvgShapeRect.js +26 -0
- package/shape/SvgShapeRhombus.js +27 -0
- package/shape/SvgShapeStar.js +103 -0
- package/svg/ISvg.js +1 -0
- package/svg/SvgBBox.js +149 -0
- package/svg/SvgConstants.js +14 -0
- package/svg/SvgGradient.js +97 -0
- package/svg/SvgMarker.js +221 -0
- package/svg/SvgPattern.js +228 -0
- package/svg/SvgPoint.js +33 -0
- package/svg/SvgPointSet.js +41 -0
- package/templates/lines.js +35 -0
- package/tools/gen-id.js +2 -0
- package/tools/html.js +2 -0
- package/tools/katex.js +75 -0
- package/tools/rand-str.js +122 -0
- package/tools/regex.js +15 -0
- package/tools/utils.js +438 -0
- package/tools/webColor.js +700 -0
- package/wire/HuatuNet.js +22 -0
- package/wire/HuatuWire.js +415 -0
- package/wire/HuatuWireDrawing.js +17 -0
- package/wire/IHuatuWire.js +1 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Point from './Point.js';
|
|
2
|
+
import Line from './Line.js';
|
|
3
|
+
import { AngDeg } from './AngDeg.js';
|
|
4
|
+
import LineSeg from './LineSeg.js';
|
|
5
|
+
import { WtMath } from '../math/WtMath.js';
|
|
6
|
+
import { Bezier3 } from './Bezier.js';
|
|
7
|
+
export default class Circle {
|
|
8
|
+
cx;
|
|
9
|
+
cy;
|
|
10
|
+
r;
|
|
11
|
+
center;
|
|
12
|
+
circum;
|
|
13
|
+
area;
|
|
14
|
+
constructor(cx, cy, r) {
|
|
15
|
+
this.cx = cx;
|
|
16
|
+
this.cy = cy;
|
|
17
|
+
this.center = new Point(cx, cy);
|
|
18
|
+
this.r = Math.abs(r);
|
|
19
|
+
this.circum = 2 * Math.PI * r;
|
|
20
|
+
this.area = Math.PI * r * r;
|
|
21
|
+
}
|
|
22
|
+
calcPoint(theta) {
|
|
23
|
+
return new Point(this.cx + this.r * Math.cos(theta), this.cy + this.r * Math.sin(theta));
|
|
24
|
+
}
|
|
25
|
+
getPoint(pos) {
|
|
26
|
+
if (typeof pos === "string") {
|
|
27
|
+
pos = pos.toLowerCase();
|
|
28
|
+
if (pos === "c" || pos === "center")
|
|
29
|
+
return this.center;
|
|
30
|
+
if (pos === "n")
|
|
31
|
+
return this.calcPoint(AngDeg.DEG270);
|
|
32
|
+
if (pos === "s")
|
|
33
|
+
return this.calcPoint(AngDeg.DEG90);
|
|
34
|
+
if (pos === "e")
|
|
35
|
+
return this.calcPoint(AngDeg.DEG0);
|
|
36
|
+
if (pos === "w")
|
|
37
|
+
return this.calcPoint(AngDeg.DEG180);
|
|
38
|
+
if (pos === "nw")
|
|
39
|
+
return this.calcPoint(AngDeg.DEG225);
|
|
40
|
+
if (pos === "sw")
|
|
41
|
+
return this.calcPoint(AngDeg.DEG135);
|
|
42
|
+
if (pos === "ne")
|
|
43
|
+
return this.calcPoint(AngDeg.DEG315);
|
|
44
|
+
if (pos === "se")
|
|
45
|
+
return this.calcPoint(AngDeg.DEG45);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
// It pos is a number, then it should be a degree
|
|
49
|
+
return this.calcPoint(AngDeg.deg2Ang(pos));
|
|
50
|
+
}
|
|
51
|
+
getParam(name) {
|
|
52
|
+
if (!name)
|
|
53
|
+
return undefined;
|
|
54
|
+
name = name.toLowerCase();
|
|
55
|
+
if (name === "r")
|
|
56
|
+
return this.r;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
getPointAng(ang) {
|
|
60
|
+
return this.calcPoint(ang);
|
|
61
|
+
}
|
|
62
|
+
getThetaOfPoint(point) {
|
|
63
|
+
const ray = new LineSeg(this.center, point);
|
|
64
|
+
if (WtMath.isZero(ray.len))
|
|
65
|
+
return undefined;
|
|
66
|
+
return ray.ang;
|
|
67
|
+
}
|
|
68
|
+
getTangentLine(theta) {
|
|
69
|
+
return new Line(this.calcPoint(theta), AngDeg.fitAng(theta - AngDeg.DEG90));
|
|
70
|
+
}
|
|
71
|
+
isIdenticalWith(c) {
|
|
72
|
+
return WtMath.isEqual(this.cx, c.cx)
|
|
73
|
+
&& WtMath.isEqual(this.cy, c.cy)
|
|
74
|
+
&& WtMath.isEqual(this.r, c.r);
|
|
75
|
+
}
|
|
76
|
+
buildBezier3Simulate(ccw) {
|
|
77
|
+
return Circle.buildBezier3Simulate(this, ccw);
|
|
78
|
+
}
|
|
79
|
+
moveBy(dx, dy) {
|
|
80
|
+
const newCenter = Point.moveBy(this.center, dx, dy);
|
|
81
|
+
return new Circle(newCenter.x, newCenter.y, this.r);
|
|
82
|
+
}
|
|
83
|
+
rotateBy(rotDeg, at) {
|
|
84
|
+
const newCenter = Point.rotateBy(this.center, rotDeg, at);
|
|
85
|
+
return new Circle(newCenter.x, newCenter.y, this.r);
|
|
86
|
+
}
|
|
87
|
+
duplicateTo(distance, dir) {
|
|
88
|
+
distance = Math.abs(distance);
|
|
89
|
+
if (distance >= this.r && dir === "in")
|
|
90
|
+
return undefined;
|
|
91
|
+
return new Circle(this.cx, this.cy, this.r + distance * (dir === "in" ? -1 : 1));
|
|
92
|
+
}
|
|
93
|
+
// Static methods
|
|
94
|
+
static byCenterAndRadius(center, radius) {
|
|
95
|
+
return new Circle(center.x, center.y, radius);
|
|
96
|
+
}
|
|
97
|
+
static calcPoint(radius, theta) {
|
|
98
|
+
return new Point(radius * Math.cos(theta), radius * Math.sin(theta));
|
|
99
|
+
}
|
|
100
|
+
static intersectionsOf(c1, c2) {
|
|
101
|
+
if (WtMath.isZero(c1.r) || WtMath.isZero(c2.r))
|
|
102
|
+
return undefined;
|
|
103
|
+
if (c1.isIdenticalWith(c2))
|
|
104
|
+
return undefined;
|
|
105
|
+
const dx = c2.cx - c1.cx, dy = c2.cy - c1.cy, L2 = dx ** 2 + dy ** 2, A = (c1.r ** 2 - c2.r ** 2 - L2) / (2 * c2.r), E = L2 - A ** 2, Adx = A + dx;
|
|
106
|
+
if (WtMath.isZero(Adx)) {
|
|
107
|
+
return [new Point(c2.cx - c2.r, c2.cy)];
|
|
108
|
+
}
|
|
109
|
+
else if (WtMath.isZero(E)) {
|
|
110
|
+
const z = dy / Adx;
|
|
111
|
+
const cos = (1 - z ** 2) / (1 + z ** 2), sin = (2 * z) / (1 + z ** 2);
|
|
112
|
+
return [new Point(c2.r * cos + c2.cx, c2.r * sin + c2.cy)];
|
|
113
|
+
}
|
|
114
|
+
else if (E < 0) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return [(dy + Math.sqrt(E)) / Adx, (dy - Math.sqrt(E)) / Adx]
|
|
119
|
+
.map(z => ({
|
|
120
|
+
cos: (1 - z ** 2) / (1 + z ** 2),
|
|
121
|
+
sin: (2 * z) / (1 + z ** 2)
|
|
122
|
+
}))
|
|
123
|
+
.map(x => new Point(c2.r * x.cos + c2.cx, c2.r * x.sin + c2.cy));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
static buildBezier3Simulate(circle, ccw) {
|
|
127
|
+
let d = Bezier3.K * circle.r;
|
|
128
|
+
const pE = circle.calcPoint(AngDeg.DEG0), pEu = new Point(pE.x, pE.y - d), pEd = new Point(pE.x, pE.y + d), pN = circle.calcPoint(AngDeg.DEG270), pNe = new Point(pN.x + d, pN.y), pNw = new Point(pN.x - d, pN.y), pW = circle.calcPoint(AngDeg.DEG180), pWu = new Point(pW.x, pW.y - d), pWd = new Point(pW.x, pW.y + d), pS = circle.calcPoint(AngDeg.DEG90), pSe = new Point(pS.x + d, pS.y), pSw = new Point(pS.x - d, pS.y);
|
|
129
|
+
return ccw
|
|
130
|
+
? [
|
|
131
|
+
new Bezier3(pE, pEu, pNe, pN),
|
|
132
|
+
new Bezier3(pN, pNw, pWu, pW),
|
|
133
|
+
new Bezier3(pW, pWd, pSw, pS),
|
|
134
|
+
new Bezier3(pS, pSe, pEd, pE),
|
|
135
|
+
]
|
|
136
|
+
: [
|
|
137
|
+
new Bezier3(pE, pEd, pSe, pS),
|
|
138
|
+
new Bezier3(pS, pSw, pWd, pW),
|
|
139
|
+
new Bezier3(pW, pWu, pNw, pN),
|
|
140
|
+
new Bezier3(pN, pNe, pEu, pE),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { AngDeg } from './AngDeg.js';
|
|
2
|
+
import { WtMath } from '../math/WtMath.js';
|
|
3
|
+
import Point from './Point.js';
|
|
4
|
+
import Line from './Line.js';
|
|
5
|
+
import LineSeg from './LineSeg.js';
|
|
6
|
+
import { Intersection } from './Intersection.js';
|
|
7
|
+
;
|
|
8
|
+
export default class Ellipse {
|
|
9
|
+
center;
|
|
10
|
+
cx;
|
|
11
|
+
cy;
|
|
12
|
+
rx;
|
|
13
|
+
ry;
|
|
14
|
+
rot;
|
|
15
|
+
rLong;
|
|
16
|
+
rShort;
|
|
17
|
+
rf;
|
|
18
|
+
isCircle;
|
|
19
|
+
focalOnX = false;
|
|
20
|
+
focalOnY = false;
|
|
21
|
+
focalLeft;
|
|
22
|
+
focalRight;
|
|
23
|
+
circum;
|
|
24
|
+
area;
|
|
25
|
+
thetaE = AngDeg.DEG0;
|
|
26
|
+
thetaW = AngDeg.DEG180;
|
|
27
|
+
thetaN = AngDeg.DEG90;
|
|
28
|
+
thetaS = AngDeg.DEG270;
|
|
29
|
+
pE;
|
|
30
|
+
pW;
|
|
31
|
+
pN;
|
|
32
|
+
pS;
|
|
33
|
+
constructor(cx, cy, rx, ry, rot) {
|
|
34
|
+
this.cx = cx;
|
|
35
|
+
this.cy = cy;
|
|
36
|
+
this.center = new Point(cx, cy);
|
|
37
|
+
this.rx = rx;
|
|
38
|
+
this.ry = ry;
|
|
39
|
+
this.rLong = Math.max(rx, ry);
|
|
40
|
+
this.rShort = Math.min(rx, ry);
|
|
41
|
+
this.rot = AngDeg.fitAng(rot);
|
|
42
|
+
this.area = rx * ry * Math.PI;
|
|
43
|
+
this.circum = Ellipse.calculateCircum(rx, ry);
|
|
44
|
+
if (WtMath.isEqual(rx, ry)) {
|
|
45
|
+
this.isCircle = true;
|
|
46
|
+
this.focalLeft = this.center;
|
|
47
|
+
this.focalRight = this.center;
|
|
48
|
+
this.rf = 0;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.isCircle = false;
|
|
52
|
+
this.rf = Math.sqrt(this.rLong ** 2 - this.rShort ** 2);
|
|
53
|
+
if (rx > ry) {
|
|
54
|
+
this.focalLeft = this.center.moveByLenAndAng(this.rf, rot + AngDeg.DEG180);
|
|
55
|
+
this.focalRight = this.center.moveByLenAndAng(this.rf, rot);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.focalLeft = this.center.moveByLenAndAng(this.rf, rot + AngDeg.DEG270);
|
|
59
|
+
this.focalRight = this.center.moveByLenAndAng(this.rf, rot + AngDeg.DEG90);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.thetaE = this.getThetaByTangentAngle(AngDeg.DEG90);
|
|
63
|
+
this.thetaN = this.getThetaByTangentAngle(AngDeg.DEG0);
|
|
64
|
+
this.thetaW = this.getThetaByTangentAngle(AngDeg.DEG270);
|
|
65
|
+
this.thetaS = this.getThetaByTangentAngle(AngDeg.DEG180);
|
|
66
|
+
this.pE = this.calcPoint(this.thetaE);
|
|
67
|
+
this.pN = this.calcPoint(this.thetaN);
|
|
68
|
+
this.pW = this.calcPoint(this.thetaW);
|
|
69
|
+
this.pS = this.calcPoint(this.thetaS);
|
|
70
|
+
}
|
|
71
|
+
calcPoint(theta) {
|
|
72
|
+
return new Point(this.rx * Math.cos(theta), this.ry * Math.sin(theta))
|
|
73
|
+
.rotate(this.rot)
|
|
74
|
+
.moveByXY(this.cx, this.cy);
|
|
75
|
+
}
|
|
76
|
+
getPoint(pos) {
|
|
77
|
+
if (typeof pos === "string") {
|
|
78
|
+
pos = pos.toLowerCase();
|
|
79
|
+
if (pos === "c" || pos === "center")
|
|
80
|
+
return this.center;
|
|
81
|
+
if (pos === "n")
|
|
82
|
+
return this.pN;
|
|
83
|
+
if (pos === "s")
|
|
84
|
+
return this.pS;
|
|
85
|
+
if (pos === "e")
|
|
86
|
+
return this.pE;
|
|
87
|
+
if (pos === "w")
|
|
88
|
+
return this.pW;
|
|
89
|
+
// For NW/SW/NE/SE, return the point at real direction
|
|
90
|
+
if (pos === "nw")
|
|
91
|
+
return Intersection.pickPoint(Intersection.line2Ellipse(new Line(this.center, AngDeg.DEG45), this), "w");
|
|
92
|
+
if (pos === "sw")
|
|
93
|
+
return Intersection.pickPoint(Intersection.line2Ellipse(new Line(this.center, -AngDeg.DEG45), this), "s");
|
|
94
|
+
if (pos === "ne")
|
|
95
|
+
return Intersection.pickPoint(Intersection.line2Ellipse(new Line(this.center, -AngDeg.DEG45), this), "n");
|
|
96
|
+
if (pos === "se")
|
|
97
|
+
return Intersection.pickPoint(Intersection.line2Ellipse(new Line(this.center, AngDeg.DEG45), this), "e");
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
// It pos is a number, then it should be a degree
|
|
101
|
+
return this.calcPoint(AngDeg.deg2Ang(pos));
|
|
102
|
+
}
|
|
103
|
+
getPointAng(ang) {
|
|
104
|
+
return this.calcPoint(ang);
|
|
105
|
+
}
|
|
106
|
+
getParam(name) {
|
|
107
|
+
if (!name)
|
|
108
|
+
return undefined;
|
|
109
|
+
name = name.toLowerCase();
|
|
110
|
+
if (name === "rx")
|
|
111
|
+
return this.rx;
|
|
112
|
+
if (name === "ry")
|
|
113
|
+
return this.ry;
|
|
114
|
+
if (name === "rotdeg")
|
|
115
|
+
return AngDeg.ang2Deg(this.rot);
|
|
116
|
+
if (name === "rotang")
|
|
117
|
+
return this.rot;
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
getVectorAngle(theta) {
|
|
121
|
+
return Ellipse.getVectorAngleByTheta(this.rx, this.ry, theta);
|
|
122
|
+
}
|
|
123
|
+
getThetaByTangentAngle(ang) {
|
|
124
|
+
return Ellipse.getThetaByTangentAngle(this.rx, this.ry, AngDeg.fitAng(ang - this.rot));
|
|
125
|
+
}
|
|
126
|
+
getTangentLine(theta) {
|
|
127
|
+
const p = this.calcPoint(theta);
|
|
128
|
+
const ang = Ellipse.getTangentAngleByTheta(this.rx, this.ry, theta);
|
|
129
|
+
return new Line(p, ang + this.rot);
|
|
130
|
+
}
|
|
131
|
+
getTangentLineWithPoint(point) {
|
|
132
|
+
const ls = new LineSeg(this.center, point);
|
|
133
|
+
const theta = Ellipse.getThetaByVectorAngle(this.rx, this.ry, ls.ang - this.rot);
|
|
134
|
+
const lenTheta = Ellipse.getDistanceToCenter(this.rx, this.ry, theta);
|
|
135
|
+
if (ls.len < lenTheta)
|
|
136
|
+
return null;
|
|
137
|
+
if (WtMath.isEqual(ls.len, lenTheta))
|
|
138
|
+
return [this.getTangentLine(theta)];
|
|
139
|
+
// Now calculates the tangent lines, which are two
|
|
140
|
+
const Y = this.cy - point.y, X = this.cx - point.x, Cxs = this.rx * Math.sin(this.rot), Cyc = this.ry * Math.cos(this.rot), Cys = this.ry * Math.sin(this.rot), Cxc = this.rx * Math.cos(this.rot);
|
|
141
|
+
const A = Y * Cxc - X * Cxs, B = Y * Cys + X * Cyc, M = Cys * Cxs + Cyc * Cxc;
|
|
142
|
+
const delta = A ** 2 + B ** 2 - M ** 2;
|
|
143
|
+
if (delta < 0 || M === B)
|
|
144
|
+
return null;
|
|
145
|
+
return [
|
|
146
|
+
(-A + Math.sqrt(delta)) / (M - B),
|
|
147
|
+
(-A - Math.sqrt(delta)) / (M - B)
|
|
148
|
+
].map(z => {
|
|
149
|
+
return {
|
|
150
|
+
sin: 2 * z / (1 + z ** 2),
|
|
151
|
+
cos: (1 - z ** 2) / (1 + z ** 2)
|
|
152
|
+
};
|
|
153
|
+
}).map(t => new Point(this.cx + this.rx * t.cos * Math.cos(this.rot) - this.ry * t.sin * Math.sin(this.rot), this.cy + this.rx * t.cos * Math.sin(this.rot) + this.ry * t.sin * Math.cos(this.rot))).map(p => new LineSeg(p, point).toLine());
|
|
154
|
+
}
|
|
155
|
+
isPointIn(point) {
|
|
156
|
+
const ls = new LineSeg(this.center, point);
|
|
157
|
+
const theta = Ellipse.getThetaByVectorAngle(this.rx, this.ry, ls.ang - this.rot);
|
|
158
|
+
return ls.len < Ellipse.getDistanceToCenter(this.rx, this.ry, theta);
|
|
159
|
+
}
|
|
160
|
+
isPointOut(point) {
|
|
161
|
+
const ls = new LineSeg(this.center, point);
|
|
162
|
+
const theta = Ellipse.getThetaByVectorAngle(this.rx, this.ry, ls.ang - this.rot);
|
|
163
|
+
return ls.len > Ellipse.getDistanceToCenter(this.rx, this.ry, theta);
|
|
164
|
+
}
|
|
165
|
+
isPointOn(point) {
|
|
166
|
+
const ls = new LineSeg(this.center, point);
|
|
167
|
+
const theta = Ellipse.getThetaByVectorAngle(this.rx, this.ry, ls.ang - this.rot);
|
|
168
|
+
return WtMath.isEqual(ls.len, Ellipse.getDistanceToCenter(this.rx, this.ry, theta));
|
|
169
|
+
}
|
|
170
|
+
getThetaOfVector(point) {
|
|
171
|
+
const ls = new LineSeg(this.center, point);
|
|
172
|
+
if (WtMath.isZero(ls.len))
|
|
173
|
+
return undefined;
|
|
174
|
+
else {
|
|
175
|
+
return Ellipse.getThetaByVectorAngle(this.rx, this.ry, ls.ang - this.rot);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
moveBy(dx, dy) {
|
|
179
|
+
const newCenter = Point.moveBy(this.center, dx, dy);
|
|
180
|
+
return new Ellipse(newCenter.x, newCenter.y, this.rx, this.ry, this.rot);
|
|
181
|
+
}
|
|
182
|
+
rotateBy(rotDeg, at) {
|
|
183
|
+
const newCenter = Point.rotateBy(this.center, rotDeg, at);
|
|
184
|
+
const newRotAng = this.rot + AngDeg.deg2Ang(rotDeg);
|
|
185
|
+
return new Ellipse(newCenter.x, newCenter.y, this.rx, this.ry, newRotAng);
|
|
186
|
+
}
|
|
187
|
+
duplicateTo(distance, dir) {
|
|
188
|
+
distance = Math.abs(distance);
|
|
189
|
+
if ((distance >= this.rx || distance >= this.ry) && dir === "in")
|
|
190
|
+
return undefined;
|
|
191
|
+
let rd = distance * (dir === "in" ? -1 : 1), rx = this.rx + rd, ry = this.ry + rd;
|
|
192
|
+
return new Ellipse(this.cx, this.cy, rx, ry, this.rot);
|
|
193
|
+
}
|
|
194
|
+
// Static methods
|
|
195
|
+
static calcPoint(x, y, rx, ry, theta, rot) {
|
|
196
|
+
return new Point(rx * Math.cos(theta), ry * Math.sin(theta))
|
|
197
|
+
.rotate(rot)
|
|
198
|
+
.moveByXY(x, y);
|
|
199
|
+
}
|
|
200
|
+
static getDistanceToCenter(rx, ry, theta) {
|
|
201
|
+
return Math.sqrt(rx ** 2 * Math.cos(theta) ** 2 + ry ** 2 * Math.sin(theta) ** 2);
|
|
202
|
+
}
|
|
203
|
+
static calculateCircum(rx, ry) {
|
|
204
|
+
return 4 * (rx + ry) - 4 * (4 - Math.PI + (0.1218 * (rx - ry) ** 2) / ((rx + ry) ** 2 + 2.8 * rx * ry)) * rx * ry / (rx + ry);
|
|
205
|
+
}
|
|
206
|
+
static getVectorAngleByTheta(rx, ry, theta) {
|
|
207
|
+
theta = AngDeg.fitAng(theta);
|
|
208
|
+
if (WtMath.isEqual(theta, AngDeg.DEG0) ||
|
|
209
|
+
WtMath.isEqual(theta, AngDeg.DEG90) ||
|
|
210
|
+
WtMath.isEqual(theta, AngDeg.DEG180) ||
|
|
211
|
+
WtMath.isEqual(theta, AngDeg.DEG270)) {
|
|
212
|
+
return theta;
|
|
213
|
+
}
|
|
214
|
+
const slope = ry / rx * Math.tan(theta);
|
|
215
|
+
let slopeAngle = Math.atan(slope);
|
|
216
|
+
if (theta > AngDeg.DEG90 && theta < AngDeg.DEG270) {
|
|
217
|
+
slopeAngle += AngDeg.DEG180;
|
|
218
|
+
}
|
|
219
|
+
return AngDeg.fitAng(slopeAngle);
|
|
220
|
+
}
|
|
221
|
+
static getThetaByVectorAngle(rx, ry, ang) {
|
|
222
|
+
ang = AngDeg.fitAng(ang);
|
|
223
|
+
if (WtMath.isEqual(ang, AngDeg.DEG0) ||
|
|
224
|
+
WtMath.isEqual(ang, AngDeg.DEG90) ||
|
|
225
|
+
WtMath.isEqual(ang, AngDeg.DEG180) ||
|
|
226
|
+
WtMath.isEqual(ang, AngDeg.DEG270)) {
|
|
227
|
+
return ang;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The slope of the vector is `tan(ang) while it equals to `Ry * tan(theta) / Rx`
|
|
231
|
+
*/
|
|
232
|
+
const slope = Math.tan(ang);
|
|
233
|
+
let theta = Math.atan(slope * rx / ry);
|
|
234
|
+
if (ang > AngDeg.DEG90 && ang < AngDeg.DEG270) {
|
|
235
|
+
theta += AngDeg.DEG180;
|
|
236
|
+
}
|
|
237
|
+
return AngDeg.fitAng(theta);
|
|
238
|
+
}
|
|
239
|
+
static getTangentAngleByTheta(rx, ry, theta) {
|
|
240
|
+
theta = AngDeg.fitAng(theta);
|
|
241
|
+
if (WtMath.isEqual(theta, AngDeg.DEG0) ||
|
|
242
|
+
WtMath.isEqual(theta, AngDeg.DEG90) ||
|
|
243
|
+
WtMath.isEqual(theta, AngDeg.DEG180) ||
|
|
244
|
+
WtMath.isEqual(theta, AngDeg.DEG270)) {
|
|
245
|
+
return AngDeg.fitAng(theta + AngDeg.DEG90);
|
|
246
|
+
}
|
|
247
|
+
const tangentSlope = -ry / rx / Math.tan(theta);
|
|
248
|
+
let slopeAngle = Math.atan(tangentSlope);
|
|
249
|
+
if (theta > AngDeg.DEG0 && theta < AngDeg.DEG180) {
|
|
250
|
+
slopeAngle += AngDeg.DEG180;
|
|
251
|
+
}
|
|
252
|
+
return AngDeg.fitAng(slopeAngle);
|
|
253
|
+
}
|
|
254
|
+
static getThetaByTangentAngle(rx, ry, ang) {
|
|
255
|
+
ang = AngDeg.fitAng(ang);
|
|
256
|
+
if (ang === 0 || ang === AngDeg.DEG90 || ang === AngDeg.DEG180 || ang === AngDeg.DEG270) {
|
|
257
|
+
return AngDeg.fitAng(ang - AngDeg.DEG90);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const tangentSlope = Math.tan(ang);
|
|
261
|
+
const thetaTan = -ry / rx / tangentSlope;
|
|
262
|
+
let theta = Math.atan(thetaTan);
|
|
263
|
+
if (ang > AngDeg.DEG180 && ang < AngDeg.DEG360) {
|
|
264
|
+
theta += AngDeg.DEG180;
|
|
265
|
+
}
|
|
266
|
+
return AngDeg.fitAng(theta);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
static calcEllipseByTwoPoints(p1, p2, rx, ry, isCW, largeArc, rotDeg) {
|
|
270
|
+
if (rx <= 0 || ry <= 0)
|
|
271
|
+
return null;
|
|
272
|
+
// The line direction is from P2 (end point) to P1 (start point)
|
|
273
|
+
const lsEndPoints = new LineSeg(p2, p1);
|
|
274
|
+
if (WtMath.isZero(lsEndPoints.len))
|
|
275
|
+
return null;
|
|
276
|
+
// Check if the given Ellipse size is too small, if so, enlarge it by some scale factor
|
|
277
|
+
const r = AngDeg.fitDeg(rotDeg ?? 0);
|
|
278
|
+
const vecAng = lsEndPoints.ang - r;
|
|
279
|
+
const thetaOfLine = Ellipse.getThetaByVectorAngle(rx, ry, vecAng);
|
|
280
|
+
const distToCenter = Ellipse.getDistanceToCenter(rx, ry, thetaOfLine);
|
|
281
|
+
if (distToCenter <= lsEndPoints.len / 2) {
|
|
282
|
+
const scaleFactor = lsEndPoints.len / 2 / distToCenter;
|
|
283
|
+
return {
|
|
284
|
+
rx: rx * scaleFactor,
|
|
285
|
+
ry: ry * scaleFactor,
|
|
286
|
+
cx: lsEndPoints.middle.x,
|
|
287
|
+
cy: lsEndPoints.middle.y,
|
|
288
|
+
rot: r,
|
|
289
|
+
angStart: thetaOfLine,
|
|
290
|
+
angEnd: AngDeg.fitAng(thetaOfLine + AngDeg.DEG180),
|
|
291
|
+
pStart: p1,
|
|
292
|
+
pEnd: p2,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const A = (p1.x - p2.x) * Math.cos(r) + (p1.y - p2.y) * Math.sin(r);
|
|
296
|
+
const B = -(p1.x - p2.x) * Math.sin(r) + (p1.y - p2.y) * Math.cos(r);
|
|
297
|
+
// console.log(`r: ${r}, A: ${A}, B: ${B}`);
|
|
298
|
+
if (WtMath.isZero(A) && WtMath.isZero(B))
|
|
299
|
+
return null;
|
|
300
|
+
let alphaBetas = [];
|
|
301
|
+
if (WtMath.isZero(A)) {
|
|
302
|
+
alphaBetas.push({ alpha: AngDeg.DEG0, beta: Math.asin(B / 2 / ry) }, { alpha: AngDeg.DEG0, beta: AngDeg.DEG180 - Math.asin(B / 2 / ry) }, { alpha: AngDeg.DEG180, beta: Math.asin(-B / 2 / ry) }, { alpha: AngDeg.DEG180, beta: AngDeg.DEG180 - Math.asin(-B / 2 / ry) });
|
|
303
|
+
}
|
|
304
|
+
else if (WtMath.isZero(B)) {
|
|
305
|
+
alphaBetas.push({ alpha: AngDeg.DEG90, beta: Math.asin(-A / 2 / rx) }, { alpha: AngDeg.DEG90, beta: AngDeg.DEG180 - Math.asin(-A / 2 / rx) }, { alpha: AngDeg.DEG270, beta: Math.asin(A / 2 / rx) }, { alpha: AngDeg.DEG270, beta: AngDeg.DEG180 - Math.asin(A / 2 / rx) });
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const alphaTan = Math.atan(-A * ry / B / rx);
|
|
309
|
+
let sinBeta = -A / 2 / rx / Math.sin(alphaTan);
|
|
310
|
+
sinBeta = sinBeta > 1 ? 1 : sinBeta < -1 ? -1 : sinBeta;
|
|
311
|
+
// console.log(-A / 2 / rx / Math.sin(alphaTan));
|
|
312
|
+
const betaSin = Math.asin(sinBeta);
|
|
313
|
+
alphaBetas.push({ alpha: alphaTan, beta: betaSin }, { alpha: alphaTan, beta: AngDeg.DEG180 - betaSin }, { alpha: alphaTan + AngDeg.DEG180, beta: -betaSin }, { alpha: alphaTan + AngDeg.DEG180, beta: betaSin + AngDeg.DEG180 });
|
|
314
|
+
}
|
|
315
|
+
// console.log(alphaBetas);
|
|
316
|
+
let thetas = alphaBetas.map(ab => {
|
|
317
|
+
return {
|
|
318
|
+
theta1: AngDeg.fitAng(ab.alpha + ab.beta),
|
|
319
|
+
theta2: AngDeg.fitAng(ab.alpha - ab.beta)
|
|
320
|
+
};
|
|
321
|
+
})
|
|
322
|
+
.reduce((final, th) => {
|
|
323
|
+
if (final.filter(x => WtMath.isEqual(x.theta1, th.theta1) && WtMath.isEqual(x.theta2, th.theta2)).length < 1) {
|
|
324
|
+
return [...final, th];
|
|
325
|
+
}
|
|
326
|
+
else
|
|
327
|
+
return final;
|
|
328
|
+
}, []);
|
|
329
|
+
// console.log(thetas);
|
|
330
|
+
thetas = thetas.length === 1
|
|
331
|
+
? thetas
|
|
332
|
+
: (largeArc && !isCW) || (!largeArc && isCW)
|
|
333
|
+
? thetas.filter(x => AngDeg.fitAng(x.theta1 - x.theta2) > AngDeg.DEG180)
|
|
334
|
+
: thetas.filter(x => AngDeg.fitAng(x.theta1 - x.theta2) < AngDeg.DEG180);
|
|
335
|
+
const ang1 = thetas[0].theta1;
|
|
336
|
+
const cx = p1.x - (rx * Math.cos(ang1) * Math.cos(r) - ry * Math.sin(ang1) * Math.sin(r));
|
|
337
|
+
const cy = p1.y - (rx * Math.cos(ang1) * Math.sin(r) + ry * Math.sin(ang1) * Math.cos(r));
|
|
338
|
+
const ellipse = new Ellipse(cx, cy, rx, ry, r);
|
|
339
|
+
return {
|
|
340
|
+
rx: ellipse.rx,
|
|
341
|
+
ry: ellipse.ry,
|
|
342
|
+
cx: ellipse.cx,
|
|
343
|
+
cy: ellipse.cy,
|
|
344
|
+
rot: ellipse.rot,
|
|
345
|
+
angStart: thetas[0].theta1,
|
|
346
|
+
angEnd: thetas[0].theta2,
|
|
347
|
+
pStart: p1,
|
|
348
|
+
pEnd: p2,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
static getXPoints(ell1, ell2) {
|
|
352
|
+
return Ellipse.getXPointsOfTwo(ell1, ell2);
|
|
353
|
+
}
|
|
354
|
+
static getXPointsOfTwo(ell1, ell2) {
|
|
355
|
+
const rotDiff = AngDeg.fitAng(ell2.rot - ell1.rot);
|
|
356
|
+
const xAxisParallel = WtMath.isZero(rotDiff) || WtMath.isEqual(AngDeg.DEG180, rotDiff);
|
|
357
|
+
const xAxisPerpendicular = WtMath.isEqual(AngDeg.DEG90, rotDiff) || WtMath.isEqual(AngDeg.DEG270, rotDiff);
|
|
358
|
+
// If the two ellipse are identical (long/short axis are equal), then null will be returned, because there will be tremendous intersection points
|
|
359
|
+
if (WtMath.isEqual(ell1.cx, ell2.cx) &&
|
|
360
|
+
WtMath.isEqual(ell1.cy, ell2.cy) &&
|
|
361
|
+
((WtMath.isEqual(ell1.rx, ell2.rx) && WtMath.isEqual(ell1.ry, ell2.ry) && xAxisParallel) ||
|
|
362
|
+
(WtMath.isEqual(ell1.rx, ell2.ry) && WtMath.isEqual(ell1.ry, ell2.rx) && xAxisPerpendicular))) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const A = (ell2.cx - ell1.cx) * Math.cos(ell1.rot) + (ell2.cy - ell1.cy) * Math.sin(ell1.rot), B = -(ell2.cx - ell1.cx) * Math.sin(ell1.rot) + (ell2.cy - ell1.cy) * Math.cos(ell1.rot), M = A / ell1.rx, k1 = ell2.rx * Math.cos(rotDiff) / ell1.rx, k2 = -ell2.ry * Math.sin(rotDiff) / ell1.rx, N = B / ell1.ry, l1 = ell2.rx * Math.sin(rotDiff) / ell1.ry, l2 = ell2.ry * Math.cos(rotDiff) / ell1.ry;
|
|
366
|
+
const a = M ** 2 + k1 ** 2 - 2 * M * k1 + N ** 2 + l1 ** 2 - 2 * N * l1 - 1, b = 4 * M * k2 - 4 * k1 * k2 + 4 * N * l2 - 4 * l1 * l2, c = 2 * M ** 2 - 2 * k1 ** 2 + 4 * k2 ** 2 + 2 * N ** 2 - 2 * l1 ** 2 + 4 * l2 ** 2 - 2, d = 4 * M * k2 + 4 * k1 * k2 + 4 * N * l2 + 4 * l1 * l2, e = M ** 2 + k1 ** 2 + 2 * M * k1 + N ** 2 + l1 ** 2 + 2 * N * l1 - 1;
|
|
367
|
+
const xps = WtMath.solveQuarticToSinCos(a, b, c, d, e)
|
|
368
|
+
.map(theta => new Point(ell2.rx * theta.cos, ell2.ry * theta.sin)
|
|
369
|
+
.rotate(ell2.rot)
|
|
370
|
+
.moveByXY(ell2.cx, ell2.cy));
|
|
371
|
+
return Point.deDupPoints(xps);
|
|
372
|
+
}
|
|
373
|
+
static getTouchingPointsOfCircle(ell1, cCircle) {
|
|
374
|
+
const deltaX = ell1.cx - cCircle.x, deltaY = ell1.cy - cCircle.y, v1 = deltaY * Math.cos(ell1.rot) - deltaX * Math.sin(ell1.rot), v2 = ell1.ry ** 2 - ell1.rx ** 2, v3 = deltaY * Math.sin(ell1.rot) + deltaX * Math.cos(ell1.rot), a = ell1.ry * v1, b = 2 * v2 + 2 * ell1.rx * v3, c = -2 * v2 + 2 * ell1.rx * v3;
|
|
375
|
+
const points = WtMath.solveQuarticToSinCos(a, b, 0, c, -a)
|
|
376
|
+
.map(t => new Point(ell1.cx + ell1.rx * t.cos * Math.cos(ell1.rot) - ell1.ry * t.sin * Math.sin(ell1.rot), ell1.cy + ell1.rx * t.cos * Math.sin(ell1.rot) + ell1.ry * t.sin * Math.cos(ell1.rot)));
|
|
377
|
+
return Point.deDupPoints(points);
|
|
378
|
+
}
|
|
379
|
+
static getRotateAngleOnTouch(ell1, ell2, keepExternal) {
|
|
380
|
+
const possibleRadius = Ellipse.getTouchingPointsOfCircle(ell1, new Point(ell2.cx, ell2.cy));
|
|
381
|
+
if (!possibleRadius)
|
|
382
|
+
return [];
|
|
383
|
+
const minRadius = possibleRadius.map(p => ({
|
|
384
|
+
p: p,
|
|
385
|
+
r: Math.sqrt((p.x - ell2.cx) ** 2 + (p.y - ell2.cy) ** 2)
|
|
386
|
+
}))
|
|
387
|
+
.reduce((final, val) => {
|
|
388
|
+
if (final.r < 0) {
|
|
389
|
+
return {
|
|
390
|
+
p: val.p,
|
|
391
|
+
r: val.r
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
return val.r <= final.r ? val : final;
|
|
396
|
+
}
|
|
397
|
+
}, {
|
|
398
|
+
p: new Point(0, 0),
|
|
399
|
+
r: -1
|
|
400
|
+
});
|
|
401
|
+
// When Ellipse 2 is circle, it will have the only point that calculated by previous step and only on the condition
|
|
402
|
+
// that the radiuses are identical
|
|
403
|
+
if (WtMath.isEqual(ell2.ry / ell2.rx, 1)) {
|
|
404
|
+
return WtMath.isEqual(minRadius.r, ell2.rx)
|
|
405
|
+
? [{
|
|
406
|
+
theta: 0,
|
|
407
|
+
rot: 0,
|
|
408
|
+
point: minRadius.p,
|
|
409
|
+
}] : [];
|
|
410
|
+
}
|
|
411
|
+
const ell2Rmin = Math.min(ell2.rx, ell2.ry);
|
|
412
|
+
const ell2Rmax = Math.max(ell2.rx, ell2.ry);
|
|
413
|
+
// If Ellipse 2 is always intersect with Ellipse 1 or never intersect with Ellipse 1, then no results returned.
|
|
414
|
+
if (minRadius.r < ell2Rmin || minRadius.r > ell2Rmax)
|
|
415
|
+
return [];
|
|
416
|
+
// Now calculate the normal case
|
|
417
|
+
const dcx = ell2.cx - ell1.cx, dcy = ell2.cy - ell1.cy, cr1 = Math.cos(ell1.rot), sr1 = Math.sin(ell1.rot), A = dcx * cr1 + dcy * sr1, B = -dcx * sr1 + dcy * cr1, k0 = ell2.rx / ell1.rx, k1 = ell1.ry / ell1.rx, k2 = ell2.ry / ell2.rx, M = A / k0 / ell1.rx, N = B / k0 / ell1.rx, k = (1 - k2) / (1 + k2), m = 2 * M / (1 + k2), n = 2 * N / (1 + k2), k12 = k1 ** 2, mk12 = m * k12, mk1 = 1 - k12, pk1 = 1 + k12;
|
|
418
|
+
const f1 = (x, y) => {
|
|
419
|
+
const cx = Math.cos(x);
|
|
420
|
+
const sx = Math.sin(x);
|
|
421
|
+
const cy = Math.cos(y);
|
|
422
|
+
const sy = Math.sin(y);
|
|
423
|
+
return n * cx - mk12 * sx
|
|
424
|
+
- n * k * cy + mk12 * k * sy
|
|
425
|
+
+ mk1 * sx * cx
|
|
426
|
+
- k * pk1 * sx * cy
|
|
427
|
+
+ k * pk1 * cx * sy
|
|
428
|
+
- k ** 2 * mk1 * sy * cy;
|
|
429
|
+
};
|
|
430
|
+
const f1dx = (x, y) => {
|
|
431
|
+
const cx = Math.cos(x);
|
|
432
|
+
const sx = Math.sin(x);
|
|
433
|
+
const cy = Math.cos(y);
|
|
434
|
+
const sy = Math.sin(y);
|
|
435
|
+
return -n * sx - mk12 * cx
|
|
436
|
+
+ mk1 * (cx * cx - sx * sx)
|
|
437
|
+
- k * pk1 * cx * cy
|
|
438
|
+
- k * pk1 * sx * sy;
|
|
439
|
+
};
|
|
440
|
+
const f1dy = (x, y) => {
|
|
441
|
+
const cx = Math.cos(x);
|
|
442
|
+
const sx = Math.sin(x);
|
|
443
|
+
const cy = Math.cos(y);
|
|
444
|
+
const sy = Math.sin(y);
|
|
445
|
+
return n * k * sy + mk12 * k * cy
|
|
446
|
+
+ k * pk1 * sx * sy
|
|
447
|
+
+ k * pk1 * cx * cy
|
|
448
|
+
- k ** 2 * mk1 * (cy * cy - sy * sy);
|
|
449
|
+
};
|
|
450
|
+
const f2 = (x, y) => {
|
|
451
|
+
const cx = Math.cos(x);
|
|
452
|
+
const sx = Math.sin(x);
|
|
453
|
+
const cy = Math.cos(y);
|
|
454
|
+
const sy = Math.sin(y);
|
|
455
|
+
return 2 * mk12 * cx + 2 * n * sx
|
|
456
|
+
+ 2 * mk12 * k * cy + 2 * n * k * sy
|
|
457
|
+
+ mk1 * sx * sx
|
|
458
|
+
+ k ** 2 * mk1 * sy * sy
|
|
459
|
+
+ 2 * k * k12 * cx * cy + 2 * k * sx * sy
|
|
460
|
+
+ m ** 2 * k12 + n ** 2 + k12 + k ** 2 * k12 - 4 * k12 / (k0 ** 2 * (1 + k2) ** 2);
|
|
461
|
+
};
|
|
462
|
+
const f2dx = (x, y) => {
|
|
463
|
+
const cx = Math.cos(x);
|
|
464
|
+
const sx = Math.sin(x);
|
|
465
|
+
const cy = Math.cos(y);
|
|
466
|
+
const sy = Math.sin(y);
|
|
467
|
+
return -2 * mk12 * sx + 2 * n * cx
|
|
468
|
+
+ mk1 * 2 * sx * cx
|
|
469
|
+
- 2 * k * k12 * sx * cy + 2 * k * cx * sy;
|
|
470
|
+
};
|
|
471
|
+
const f2dy = (x, y) => {
|
|
472
|
+
const cx = Math.cos(x);
|
|
473
|
+
const sx = Math.sin(x);
|
|
474
|
+
const cy = Math.cos(y);
|
|
475
|
+
const sy = Math.sin(y);
|
|
476
|
+
return -2 * mk12 * k * sy + 2 * n * k * cy
|
|
477
|
+
+ k ** 2 * mk1 * 2 * sy * cy
|
|
478
|
+
- 2 * k * k12 * cx * sy + 2 * k * sx * cy;
|
|
479
|
+
};
|
|
480
|
+
const results = [];
|
|
481
|
+
const angInitials = [
|
|
482
|
+
[AngDeg.deg2Ang(6), AngDeg.deg2Ang(15)],
|
|
483
|
+
[AngDeg.deg2Ang(112), AngDeg.deg2Ang(-20)],
|
|
484
|
+
[AngDeg.deg2Ang(173), AngDeg.deg2Ang(13)],
|
|
485
|
+
[AngDeg.deg2Ang(194), AngDeg.deg2Ang(-201)],
|
|
486
|
+
[AngDeg.deg2Ang(267), AngDeg.deg2Ang(280)],
|
|
487
|
+
[AngDeg.deg2Ang(272), AngDeg.deg2Ang(-160)],
|
|
488
|
+
[AngDeg.deg2Ang(316), AngDeg.deg2Ang(142)],
|
|
489
|
+
[AngDeg.deg2Ang(358), AngDeg.deg2Ang(46)],
|
|
490
|
+
[AngDeg.deg2Ang(-22), AngDeg.deg2Ang(342)],
|
|
491
|
+
[AngDeg.deg2Ang(-66), AngDeg.deg2Ang(-280)],
|
|
492
|
+
[AngDeg.deg2Ang(-88), AngDeg.deg2Ang(15)],
|
|
493
|
+
[AngDeg.deg2Ang(-111), AngDeg.deg2Ang(-20)],
|
|
494
|
+
[AngDeg.deg2Ang(-167), AngDeg.deg2Ang(13)],
|
|
495
|
+
[AngDeg.deg2Ang(-232), AngDeg.deg2Ang(-201)],
|
|
496
|
+
[AngDeg.deg2Ang(-265), AngDeg.deg2Ang(280)],
|
|
497
|
+
[AngDeg.deg2Ang(-288), AngDeg.deg2Ang(-70)],
|
|
498
|
+
[AngDeg.deg2Ang(-311), AngDeg.deg2Ang(142)],
|
|
499
|
+
[AngDeg.deg2Ang(-342), AngDeg.deg2Ang(46)],
|
|
500
|
+
];
|
|
501
|
+
angInitials.forEach(ang => {
|
|
502
|
+
const res = WtMath.newtonMethodSolveXY(f1, f1dx, f1dy, f2, f2dx, f2dy, ang[0], ang[1], 1000);
|
|
503
|
+
if (res.succeeded) {
|
|
504
|
+
const alpha = res.value[0];
|
|
505
|
+
const beta = res.value[1];
|
|
506
|
+
let rot2 = AngDeg.fitAng((alpha + beta) / 2 + ell1.rot);
|
|
507
|
+
let theta2 = AngDeg.fitAng((alpha - beta) / 2);
|
|
508
|
+
if (rot2 > AngDeg.DEG180) {
|
|
509
|
+
rot2 -= AngDeg.DEG180;
|
|
510
|
+
theta2 = AngDeg.fitAng(theta2 - AngDeg.DEG180);
|
|
511
|
+
}
|
|
512
|
+
// console.log(`ITER COUNT: ${res.iterCount}, ROT: ${rot2}, THETA: ${theta2}`);
|
|
513
|
+
if (results.filter(r => Math.abs(r.theta - theta2) < 1e-8 &&
|
|
514
|
+
Math.abs(r.rot - rot2) < 1e-8).length < 1) {
|
|
515
|
+
results.push({
|
|
516
|
+
theta: theta2,
|
|
517
|
+
rot: rot2,
|
|
518
|
+
point: Ellipse.calcPoint(ell2.cx, ell2.cy, ell2.rx, ell2.ry, theta2, rot2)
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
if (keepExternal && results.length > 2) {
|
|
524
|
+
const c1 = new Point(ell1.cx, ell1.cy);
|
|
525
|
+
const centerLine = new LineSeg(c1, new Point(ell2.cx, ell2.cy));
|
|
526
|
+
const angDiffsCW = results.map((r, idx) => {
|
|
527
|
+
let angDiff = AngDeg.fitAng(new LineSeg(c1, r.point).ang - centerLine.ang);
|
|
528
|
+
return {
|
|
529
|
+
diff: angDiff,
|
|
530
|
+
idx: idx,
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
const angDiffsCCW = results.map((r, idx) => {
|
|
534
|
+
let angDiff = AngDeg.fitAng(centerLine.ang - new LineSeg(c1, r.point).ang);
|
|
535
|
+
return {
|
|
536
|
+
diff: angDiff,
|
|
537
|
+
idx: idx,
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
angDiffsCW.sort((a, b) => a.diff - b.diff);
|
|
541
|
+
angDiffsCCW.sort((a, b) => a.diff - b.diff);
|
|
542
|
+
return [results[angDiffsCCW[0].idx], results[angDiffsCW[0].idx]];
|
|
543
|
+
}
|
|
544
|
+
return results;
|
|
545
|
+
}
|
|
546
|
+
}
|