plotboilerplate 1.27.1 → 1.28.1
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/index.esm.js +135 -36
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/plotboilerplate.browser.min.js +1 -1
- package/dist/plotboilerplate.browser.min.js.map +1 -1
- package/dist/plotboilerplate.js +96 -17
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +2 -1
- package/src/cjs/PlotBoilerplate.d.ts +11 -1
- package/src/cjs/PlotBoilerplate.js +23 -7
- package/src/cjs/PlotBoilerplate.js.map +1 -1
- package/src/cjs/VertexListeners.d.ts +2 -1
- package/src/cjs/VertexListeners.js +5 -1
- package/src/cjs/VertexListeners.js.map +1 -1
- package/src/cjs/draw.d.ts +18 -2
- package/src/cjs/draw.js +21 -2
- package/src/cjs/draw.js.map +1 -1
- package/src/cjs/drawgl.d.ts +18 -3
- package/src/cjs/drawgl.js +19 -2
- package/src/cjs/drawgl.js.map +1 -1
- package/src/cjs/drawutilssvg.d.ts +19 -1
- package/src/cjs/drawutilssvg.js +28 -5
- package/src/cjs/drawutilssvg.js.map +1 -1
- package/src/cjs/interfaces/DrawLib.d.ts +18 -1
- package/src/cjs/interfaces/DrawLib.js +2 -0
- package/src/cjs/interfaces/DrawLib.js.map +1 -1
- package/src/cjs/utils/Params.d.ts +3 -2
- package/src/cjs/utils/Params.js +11 -3
- package/src/cjs/utils/Params.js.map +1 -1
- package/src/cjs/utils/WebColors.d.ts +2 -1
- package/src/cjs/utils/WebColors.js +15 -2
- package/src/cjs/utils/WebColors.js.map +1 -1
- package/src/cjs/utils/algorithms/HobbyPath.js +13 -16
- package/src/cjs/utils/algorithms/HobbyPath.js.map +1 -1
- package/src/cjs/utils/helpers/BezierResizeHelper.d.ts +144 -0
- package/src/cjs/utils/helpers/BezierResizeHelper.js +319 -0
- package/src/cjs/utils/helpers/BezierResizeHelper.js.map +1 -0
- package/src/esm/PlotBoilerplate.d.ts +11 -1
- package/src/esm/PlotBoilerplate.js +23 -7
- package/src/esm/PlotBoilerplate.js.map +1 -1
- package/src/esm/VertexListeners.d.ts +2 -1
- package/src/esm/VertexListeners.js +5 -1
- package/src/esm/VertexListeners.js.map +1 -1
- package/src/esm/draw.d.ts +18 -2
- package/src/esm/draw.js +21 -2
- package/src/esm/draw.js.map +1 -1
- package/src/esm/drawgl.d.ts +18 -3
- package/src/esm/drawgl.js +19 -2
- package/src/esm/drawgl.js.map +1 -1
- package/src/esm/drawutilssvg.d.ts +19 -1
- package/src/esm/drawutilssvg.js +28 -5
- package/src/esm/drawutilssvg.js.map +1 -1
- package/src/esm/interfaces/DrawLib.d.ts +18 -1
- package/src/esm/interfaces/DrawLib.js +2 -0
- package/src/esm/interfaces/DrawLib.js.map +1 -1
- package/src/esm/utils/Params.d.ts +3 -2
- package/src/esm/utils/Params.js +11 -3
- package/src/esm/utils/Params.js.map +1 -1
- package/src/esm/utils/WebColors.d.ts +2 -1
- package/src/esm/utils/WebColors.js +15 -2
- package/src/esm/utils/WebColors.js.map +1 -1
- package/src/esm/utils/algorithms/HobbyPath.js +13 -16
- package/src/esm/utils/algorithms/HobbyPath.js.map +1 -1
- package/src/esm/utils/helpers/BezierResizeHelper.d.ts +144 -0
- package/src/esm/utils/helpers/BezierResizeHelper.js +314 -0
- package/src/esm/utils/helpers/BezierResizeHelper.js.map +1 -0
- package/src/ts/PlotBoilerplate.ts +37 -11
- package/src/ts/VertexListeners.ts +5 -1
- package/src/ts/draw.ts +32 -2
- package/src/ts/drawgl.ts +38 -3
- package/src/ts/drawutilssvg.ts +33 -6
- package/src/ts/interfaces/DrawLib.ts +19 -1
- package/src/ts/utils/Params.ts +11 -3
- package/src/ts/utils/WebColors.ts +15 -2
- package/src/ts/utils/algorithms/HobbyPath.ts +248 -272
- package/src/ts/utils/helpers/BezierResizeHelper.ts +390 -0
- package/src/cjs/utils/_gup.js_ +0 -13
|
@@ -20,10 +20,9 @@ import { CubicBezierCurve } from "../../CubicBezierCurve";
|
|
|
20
20
|
import { Vertex } from "../../Vertex";
|
|
21
21
|
|
|
22
22
|
interface IControlPoints {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
23
|
+
startControlPoints: Array<Vertex>;
|
|
24
|
+
endControlPoints: Array<Vertex>;
|
|
25
|
+
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* @classdesc A HobbyCurve/HobbyPath calculation class: compute a set of optimal
|
|
@@ -33,281 +32,258 @@ interface IControlPoints {
|
|
|
33
32
|
* @requires Vertex
|
|
34
33
|
*/
|
|
35
34
|
export class HobbyPath {
|
|
35
|
+
/**
|
|
36
|
+
* @member {Array<Vertex>} vertices
|
|
37
|
+
* @memberof HobbyPath
|
|
38
|
+
* @type {Array<Vertex>}
|
|
39
|
+
* @instance
|
|
40
|
+
**/
|
|
41
|
+
private vertices: Array<Vertex>;
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @constructor
|
|
47
|
-
* @name HobbyPath
|
|
48
|
-
* @param {Array<Vertex>=} vertices? - An optional array of vertices to initialize the path with.
|
|
49
|
-
**/
|
|
50
|
-
constructor( vertices?:Array<Vertex> ) {
|
|
51
|
-
this.vertices = vertices ? vertices : [];
|
|
52
|
-
};
|
|
43
|
+
/**
|
|
44
|
+
* @constructor
|
|
45
|
+
* @name HobbyPath
|
|
46
|
+
* @param {Array<Vertex>=} vertices? - An optional array of vertices to initialize the path with.
|
|
47
|
+
**/
|
|
48
|
+
constructor(vertices?: Array<Vertex>) {
|
|
49
|
+
this.vertices = vertices ? vertices : [];
|
|
50
|
+
}
|
|
53
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Add a new point to the end of the vertex sequence.
|
|
54
|
+
*
|
|
55
|
+
* @name addPoint
|
|
56
|
+
* @memberof HobbyPath
|
|
57
|
+
* @instance
|
|
58
|
+
* @param {Vertex} p - The vertex (point) to add.
|
|
59
|
+
**/
|
|
60
|
+
addPoint(p: Vertex): void {
|
|
61
|
+
this.vertices.push(p);
|
|
62
|
+
}
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Generate a sequence of cubic Bézier curves from the point set.
|
|
66
|
+
*
|
|
67
|
+
* @name generateCurve
|
|
68
|
+
* @memberof HobbyPath
|
|
69
|
+
* @instance
|
|
70
|
+
* @param {boolean=} circular - Specify if the path should be closed.
|
|
71
|
+
* @param {number=0} omega - (default=0) An optional tension parameter.
|
|
72
|
+
* @return Array<CubicBezierCurve>
|
|
73
|
+
**/
|
|
74
|
+
generateCurve(circular?: boolean, omega?: number): Array<CubicBezierCurve> {
|
|
75
|
+
let n: number = this.vertices.length;
|
|
76
|
+
if (n > 1) {
|
|
77
|
+
if (n == 2) {
|
|
78
|
+
// for two points, just draw a straight line
|
|
79
|
+
return [new CubicBezierCurve(this.vertices[0], this.vertices[1], this.vertices[0], this.vertices[1])];
|
|
80
|
+
} else {
|
|
81
|
+
const curves: Array<CubicBezierCurve> = [];
|
|
82
|
+
let controlPoints = this.hobbyControls(circular, omega);
|
|
83
|
+
for (let i = 0; i < n - (circular ? 0 : 1); i++) {
|
|
84
|
+
// if i is n-1, the "next" point is the first one
|
|
85
|
+
let j: number = (i + 1) % n; // Use a succ function here?
|
|
86
|
+
curves.push(
|
|
87
|
+
new CubicBezierCurve(
|
|
88
|
+
this.vertices[i],
|
|
89
|
+
this.vertices[j],
|
|
90
|
+
controlPoints.startControlPoints[i],
|
|
91
|
+
controlPoints.endControlPoints[i]
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return curves;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
66
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Computes the control point coordinates for a Hobby curve through
|
|
104
|
+
* the points given.
|
|
105
|
+
*
|
|
106
|
+
* @name hobbyControls
|
|
107
|
+
* @memberof HobbyPath
|
|
108
|
+
* @instance
|
|
109
|
+
* @param {boolean} circular - If true, then the path will be closed.
|
|
110
|
+
* @param {number=0} omega - The 'curl' or the path.
|
|
111
|
+
* @return {IControlPoints} An object with two members: startControlPoints and endControlPoints (Array<Vertex>).
|
|
112
|
+
**/
|
|
113
|
+
private hobbyControls(circular?: boolean, omega?: number): IControlPoints {
|
|
114
|
+
// This is a version that works for both, closed and non-closed paths.
|
|
67
115
|
|
|
68
|
-
|
|
69
|
-
* Generate a sequence of cubic Bézier curves from the point set.
|
|
70
|
-
*
|
|
71
|
-
* @name generateCurve
|
|
72
|
-
* @memberof HobbyPath
|
|
73
|
-
* @instance
|
|
74
|
-
* @param {boolean=} circular - Specify if the path should be closed.
|
|
75
|
-
* @param {number=0} omega - (default=0) An optional tension parameter.
|
|
76
|
-
* @return Array<CubicBezierCurve>
|
|
77
|
-
**/
|
|
78
|
-
generateCurve( circular?:boolean, omega?:number ) : Array<CubicBezierCurve> {
|
|
79
|
-
let n : number = this.vertices.length;
|
|
80
|
-
if (n > 1) {
|
|
81
|
-
if (n == 2) {
|
|
82
|
-
// for two points, just draw a straight line
|
|
83
|
-
return [ new CubicBezierCurve(
|
|
84
|
-
this.vertices[0],
|
|
85
|
-
this.vertices[1],
|
|
86
|
-
this.vertices[0],
|
|
87
|
-
this.vertices[1]
|
|
88
|
-
) ];
|
|
89
|
-
} else {
|
|
90
|
-
const curves : Array<CubicBezierCurve> = [];
|
|
91
|
-
let controlPoints = this.hobbyControls(circular,omega);
|
|
92
|
-
for (let i = 0; i < n - (circular?0:1); i++) {
|
|
93
|
-
// if i is n-1, the "next" point is the first one
|
|
94
|
-
let j : number = (i+1) % n; // Use a succ function here?
|
|
95
|
-
curves.push( new CubicBezierCurve(
|
|
96
|
-
this.vertices[i],
|
|
97
|
-
this.vertices[j],
|
|
98
|
-
controlPoints.startControlPoints[i],
|
|
99
|
-
controlPoints.endControlPoints[i]
|
|
100
|
-
) );
|
|
101
|
-
}
|
|
102
|
-
return curves;
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
116
|
+
if (typeof omega === "undefined") omega = 0;
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* the points given.
|
|
113
|
-
*
|
|
114
|
-
* @name hobbyControls
|
|
115
|
-
* @memberof HobbyPath
|
|
116
|
-
* @instance
|
|
117
|
-
* @param {boolean} circular - If true, then the path will be closed.
|
|
118
|
-
* @param {number=0} omega - The 'curl' or the path.
|
|
119
|
-
* @return {IControlPoints} An object with two members: startControlPoints and endControlPoints (Array<Vertex>).
|
|
120
|
-
**/
|
|
121
|
-
private hobbyControls(circular?:boolean, omega?:number) : IControlPoints {
|
|
122
|
-
// This is a version that works for both, closed and non-closed paths.
|
|
118
|
+
let n: number = this.vertices.length - (circular ? 0 : 1);
|
|
119
|
+
let D: Array<number> = new Array<number>(n);
|
|
123
120
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
let ds: Array<Vertex> = new Array<Vertex>(n);
|
|
122
|
+
var succ = (i: number): number => {
|
|
123
|
+
return circular ? (i + 1) % n : i + 1;
|
|
124
|
+
};
|
|
125
|
+
var pred = (i: number): number => {
|
|
126
|
+
return circular ? (i + n - 1) % n : i - 1;
|
|
127
|
+
};
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// compute "beta" angles from "alpha" angles
|
|
194
|
-
beta = new Array<number>(n);
|
|
195
|
-
for (let i = 0; i < n - 1; i++)
|
|
196
|
-
beta[i] = -gamma[i+1]-alpha[i+1];
|
|
197
|
-
// again, see Jackowski article
|
|
198
|
-
beta[n-1] = -alpha[n];
|
|
199
|
-
}
|
|
200
|
-
let startControlPoints = new Array<Vertex>(n);
|
|
201
|
-
let endControlPoints = new Array<Vertex>(n);
|
|
202
|
-
for (let i = 0; i < n; i++) {
|
|
203
|
-
let j : number = succ(i);
|
|
204
|
-
let a : number = HobbyPath.utils.rho(alpha[i], beta[i]) * D[i] / 3;
|
|
205
|
-
let b : number = HobbyPath.utils.rho(beta[i], alpha[i]) * D[i] / 3;
|
|
206
|
-
let v : Vertex = HobbyPath.utils.normalize(HobbyPath.utils.rotateAngle(ds[i], alpha[i]));
|
|
207
|
-
startControlPoints[i] = new Vertex( this.vertices[i].x + a * v.x, this.vertices[i].y + a * v.y );
|
|
208
|
-
v = HobbyPath.utils.normalize(HobbyPath.utils.rotateAngle(ds[i], -beta[i]));
|
|
209
|
-
endControlPoints[i] = new Vertex( this.vertices[j].x - b * v.x, this.vertices[j].y - b * v.y );
|
|
210
|
-
}
|
|
211
|
-
return { startControlPoints : startControlPoints,
|
|
212
|
-
endControlPoints: endControlPoints
|
|
213
|
-
};
|
|
129
|
+
for (let i = 0; i < n; i++) {
|
|
130
|
+
// the "next" point in a modular way
|
|
131
|
+
let j: number = succ(i);
|
|
132
|
+
ds[i] = this.vertices[i].difference(this.vertices[j]);
|
|
133
|
+
D[i] = Math.sqrt(ds[i].x * ds[i].x + ds[i].y * ds[i].y);
|
|
134
|
+
}
|
|
135
|
+
let gamma: Array<number> = new Array(n + (circular ? 0 : 1));
|
|
136
|
+
for (let i = circular ? 0 : 1; i < n; i++) {
|
|
137
|
+
// the "previous" point in a modular way
|
|
138
|
+
let k: number = pred(i);
|
|
139
|
+
let sin = ds[k].y / D[k];
|
|
140
|
+
let cos = ds[k].x / D[k];
|
|
141
|
+
let vec = HobbyPath.utils.rotate(ds[i], -sin, cos);
|
|
142
|
+
gamma[i] = Math.atan2(vec.y, vec.x);
|
|
143
|
+
}
|
|
144
|
+
if (!circular) gamma[n] = 0;
|
|
145
|
+
let a: Array<number> = new Array(n + (circular ? 0 : 1));
|
|
146
|
+
let b: Array<number> = new Array(n + (circular ? 0 : 1));
|
|
147
|
+
let c: Array<number> = new Array(n + (circular ? 0 : 1));
|
|
148
|
+
let d: Array<number> = new Array(n + (circular ? 0 : 1));
|
|
149
|
+
for (let i = circular ? 0 : 1; i < n; i++) {
|
|
150
|
+
// j is the "next" point, k the "previous" one
|
|
151
|
+
let j: number = succ(i);
|
|
152
|
+
let k: number = pred(i);
|
|
153
|
+
// see video for the equations
|
|
154
|
+
a[i] = 1 / D[k];
|
|
155
|
+
b[i] = (2 * D[k] + 2 * D[i]) / (D[k] * D[i]);
|
|
156
|
+
c[i] = 1 / D[i];
|
|
157
|
+
d[i] = -(2 * gamma[i] * D[i] + gamma[j] * D[k]) / (D[k] * D[i]);
|
|
158
|
+
}
|
|
159
|
+
// make matrix tridiagonal in preparation for the "sherman" function
|
|
160
|
+
var alpha: Array<number>;
|
|
161
|
+
var beta: Array<number>;
|
|
162
|
+
if (circular) {
|
|
163
|
+
let s: number = a[0] * omega; // Use omega here?
|
|
164
|
+
a[0] = 0;
|
|
165
|
+
let t: number = c[n - 1] * omega; // Use omega here?
|
|
166
|
+
c[n - 1] = 0;
|
|
167
|
+
alpha = HobbyPath.utils.sherman(a, b, c, d, s, t);
|
|
168
|
+
beta = new Array<number>(n);
|
|
169
|
+
for (let i = 0; i < n - (circular ? 0 : 1); i++) {
|
|
170
|
+
// "next" point
|
|
171
|
+
let j: number = succ(i);
|
|
172
|
+
beta[i] = -gamma[j] - alpha[j];
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// see the Jackowski article for the following values; the result
|
|
176
|
+
// will be that the curvature at the first point is identical to the
|
|
177
|
+
// curvature at the second point (and likewise for the last and
|
|
178
|
+
// second-to-last)
|
|
179
|
+
b[0] = 2 + omega;
|
|
180
|
+
c[0] = 2 * omega + 1;
|
|
181
|
+
d[0] = -c[0] * gamma[1];
|
|
182
|
+
a[n] = 2 * omega + 1;
|
|
183
|
+
b[n] = 2 + omega;
|
|
184
|
+
d[n] = 0;
|
|
185
|
+
// solve system for the angles called "alpha" in the video
|
|
186
|
+
alpha = HobbyPath.utils.thomas(a, b, c, d);
|
|
187
|
+
// compute "beta" angles from "alpha" angles
|
|
188
|
+
beta = new Array<number>(n);
|
|
189
|
+
for (let i = 0; i < n - 1; i++) beta[i] = -gamma[i + 1] - alpha[i + 1];
|
|
190
|
+
// again, see Jackowski article
|
|
191
|
+
beta[n - 1] = -alpha[n];
|
|
214
192
|
}
|
|
193
|
+
let startControlPoints = new Array<Vertex>(n);
|
|
194
|
+
let endControlPoints = new Array<Vertex>(n);
|
|
195
|
+
for (let i = 0; i < n; i++) {
|
|
196
|
+
let j: number = succ(i);
|
|
197
|
+
let a: number = (HobbyPath.utils.rho(alpha[i], beta[i]) * D[i]) / 3;
|
|
198
|
+
let b: number = (HobbyPath.utils.rho(beta[i], alpha[i]) * D[i]) / 3;
|
|
199
|
+
let v: Vertex = HobbyPath.utils.normalize(HobbyPath.utils.rotateAngle(ds[i], alpha[i]));
|
|
200
|
+
startControlPoints[i] = new Vertex(this.vertices[i].x + a * v.x, this.vertices[i].y + a * v.y);
|
|
201
|
+
v = HobbyPath.utils.normalize(HobbyPath.utils.rotateAngle(ds[i], -beta[i]));
|
|
202
|
+
endControlPoints[i] = new Vertex(this.vertices[j].x - b * v.x, this.vertices[j].y - b * v.y);
|
|
203
|
+
}
|
|
204
|
+
return { startControlPoints: startControlPoints, endControlPoints: endControlPoints };
|
|
205
|
+
}
|
|
215
206
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
v[0] = t;
|
|
300
|
-
v[n-1] = s;
|
|
301
|
-
b[0] -= t;
|
|
302
|
-
b[n-1] -= s;
|
|
303
|
-
// this would be more efficient if computed in parallel, but hey...
|
|
304
|
-
const Td : Array<number> = HobbyPath.utils.thomas(a, b, c, d);
|
|
305
|
-
const Tu : Array<number> = HobbyPath.utils.thomas(a, b, c, u);
|
|
306
|
-
const factor : number = (t*Td[0] + s*Td[n-1]) / (1 + t*Tu[0] + s*Tu[n-1]);
|
|
307
|
-
const x = new Array(n);
|
|
308
|
-
for (let i = 0; i < n; i++)
|
|
309
|
-
x[i] = Td[i] - factor * Tu[i];
|
|
310
|
-
return x;
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
}; // END class
|
|
207
|
+
static utils = {
|
|
208
|
+
// rotates a vector [x, y] about an angle; the angle is implicitly
|
|
209
|
+
// determined by its sine and cosine
|
|
210
|
+
rotate: (vert: Vertex, sin: number, cos: number): Vertex => {
|
|
211
|
+
return new Vertex(vert.x * cos - vert.y * sin, vert.x * sin + vert.y * cos);
|
|
212
|
+
},
|
|
213
|
+
// rotates a vector [x, y] about the angle alpha
|
|
214
|
+
rotateAngle: (vert: Vertex, alpha: number): Vertex => {
|
|
215
|
+
return HobbyPath.utils.rotate(vert, Math.sin(alpha), Math.cos(alpha));
|
|
216
|
+
},
|
|
217
|
+
// returns a normalized version of the vector
|
|
218
|
+
normalize: (vec: Vertex): Vertex => {
|
|
219
|
+
let n = Math.hypot(vec.x, vec.y);
|
|
220
|
+
if (n == 0) return new Vertex(0, 0);
|
|
221
|
+
else return new Vertex(vec.x / n, vec.y / n); // TODO: do in-place
|
|
222
|
+
},
|
|
223
|
+
// the "velocity function" (also called rho in the video); a and b are
|
|
224
|
+
// the angles alpha and beta, the return value is the distance between
|
|
225
|
+
// a control point and its neighboring point; to compute sigma(a,b)
|
|
226
|
+
// we'll simply use rho(b,a)
|
|
227
|
+
rho: (a: number, b: number): number => {
|
|
228
|
+
// see video for formula
|
|
229
|
+
let sa = Math.sin(a);
|
|
230
|
+
let sb = Math.sin(b);
|
|
231
|
+
let ca = Math.cos(a);
|
|
232
|
+
let cb = Math.cos(b);
|
|
233
|
+
let s5 = Math.sqrt(5);
|
|
234
|
+
let num = 4 + Math.sqrt(8) * (sa - sb / 16) * (sb - sa / 16) * (ca - cb);
|
|
235
|
+
let den = 2 + (s5 - 1) * ca + (3 - s5) * cb;
|
|
236
|
+
return num / den;
|
|
237
|
+
},
|
|
238
|
+
// Implements the Thomas algorithm for a tridiagonal system with i-th
|
|
239
|
+
// row a[i]x[i-1] + b[i]x[i] + c[i]x[i+1] = d[i] starting with row
|
|
240
|
+
// i=0, ending with row i=n-1 and with a[0] = c[n-1] = 0. Returns the
|
|
241
|
+
// values x[i] as an array.
|
|
242
|
+
thomas: (a: Array<number>, b: Array<number>, c: Array<number>, d: Array<number>): Array<number> => {
|
|
243
|
+
let n: number = a.length;
|
|
244
|
+
let cc: Array<number> = new Array<number>(n);
|
|
245
|
+
let dd: Array<number> = new Array<number>(n);
|
|
246
|
+
// forward sweep
|
|
247
|
+
cc[0] = c[0] / b[0];
|
|
248
|
+
dd[0] = d[0] / b[0];
|
|
249
|
+
for (let i = 1; i < n; i++) {
|
|
250
|
+
let den: number = b[i] - cc[i - 1] * a[i];
|
|
251
|
+
cc[i] = c[i] / den;
|
|
252
|
+
dd[i] = (d[i] - dd[i - 1] * a[i]) / den;
|
|
253
|
+
}
|
|
254
|
+
let x: Array<number> = new Array<number>(n);
|
|
255
|
+
// back substitution
|
|
256
|
+
x[n - 1] = dd[n - 1];
|
|
257
|
+
for (let i = n - 2; i >= 0; i--) x[i] = dd[i] - cc[i] * x[i + 1];
|
|
258
|
+
return x;
|
|
259
|
+
},
|
|
260
|
+
// Solves an "almost" tridiagonal linear system with i-th row
|
|
261
|
+
// a[i]x[i-1] + b[i]x[i] + c[i]x[i+1] = d[i] starting with row i=0,
|
|
262
|
+
// ending with row i=n-1 and with a[0] = c[n-1] = 0. Returns the
|
|
263
|
+
// values x[i] as an array. The system is not really tridiagonal
|
|
264
|
+
// because the 0-th row is b[0]x[0] + c[0]x[1] + sx[n-1] = d[0] and
|
|
265
|
+
// row n-1 is tx[0] + a[n-1]x[n-2] + b[n-1]x[n-1] = d[n-1]. The
|
|
266
|
+
// Sherman-Morrison-Woodbury formula is used so that the function
|
|
267
|
+
// "thomas" can be called to solve the system.
|
|
268
|
+
sherman: (a: Array<number>, b: Array<number>, c: Array<number>, d: Array<number>, s: number, t: number): Array<number> => {
|
|
269
|
+
const n: number = a.length;
|
|
270
|
+
const u: Array<number> = new Array<number>(n);
|
|
271
|
+
u.fill(0, 1, n - 1);
|
|
272
|
+
u[0] = 1;
|
|
273
|
+
u[n - 1] = 1;
|
|
274
|
+
let v: Array<number> = new Array<number>(n);
|
|
275
|
+
v.fill(0, 1, n - 1);
|
|
276
|
+
v[0] = t;
|
|
277
|
+
v[n - 1] = s;
|
|
278
|
+
b[0] -= t;
|
|
279
|
+
b[n - 1] -= s;
|
|
280
|
+
// this would be more efficient if computed in parallel, but hey...
|
|
281
|
+
const Td: Array<number> = HobbyPath.utils.thomas(a, b, c, d);
|
|
282
|
+
const Tu: Array<number> = HobbyPath.utils.thomas(a, b, c, u);
|
|
283
|
+
const factor: number = (t * Td[0] + s * Td[n - 1]) / (1 + t * Tu[0] + s * Tu[n - 1]);
|
|
284
|
+
const x = new Array(n);
|
|
285
|
+
for (let i = 0; i < n; i++) x[i] = Td[i] - factor * Tu[i];
|
|
286
|
+
return x;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
} // END class
|