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.
Files changed (78) hide show
  1. package/dist/index.esm.js +135 -36
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/index.esm.min.js +1 -1
  4. package/dist/index.esm.min.js.map +1 -1
  5. package/dist/plotboilerplate.browser.min.js +1 -1
  6. package/dist/plotboilerplate.browser.min.js.map +1 -1
  7. package/dist/plotboilerplate.js +96 -17
  8. package/dist/plotboilerplate.min.js +1 -1
  9. package/package.json +2 -1
  10. package/src/cjs/PlotBoilerplate.d.ts +11 -1
  11. package/src/cjs/PlotBoilerplate.js +23 -7
  12. package/src/cjs/PlotBoilerplate.js.map +1 -1
  13. package/src/cjs/VertexListeners.d.ts +2 -1
  14. package/src/cjs/VertexListeners.js +5 -1
  15. package/src/cjs/VertexListeners.js.map +1 -1
  16. package/src/cjs/draw.d.ts +18 -2
  17. package/src/cjs/draw.js +21 -2
  18. package/src/cjs/draw.js.map +1 -1
  19. package/src/cjs/drawgl.d.ts +18 -3
  20. package/src/cjs/drawgl.js +19 -2
  21. package/src/cjs/drawgl.js.map +1 -1
  22. package/src/cjs/drawutilssvg.d.ts +19 -1
  23. package/src/cjs/drawutilssvg.js +28 -5
  24. package/src/cjs/drawutilssvg.js.map +1 -1
  25. package/src/cjs/interfaces/DrawLib.d.ts +18 -1
  26. package/src/cjs/interfaces/DrawLib.js +2 -0
  27. package/src/cjs/interfaces/DrawLib.js.map +1 -1
  28. package/src/cjs/utils/Params.d.ts +3 -2
  29. package/src/cjs/utils/Params.js +11 -3
  30. package/src/cjs/utils/Params.js.map +1 -1
  31. package/src/cjs/utils/WebColors.d.ts +2 -1
  32. package/src/cjs/utils/WebColors.js +15 -2
  33. package/src/cjs/utils/WebColors.js.map +1 -1
  34. package/src/cjs/utils/algorithms/HobbyPath.js +13 -16
  35. package/src/cjs/utils/algorithms/HobbyPath.js.map +1 -1
  36. package/src/cjs/utils/helpers/BezierResizeHelper.d.ts +144 -0
  37. package/src/cjs/utils/helpers/BezierResizeHelper.js +319 -0
  38. package/src/cjs/utils/helpers/BezierResizeHelper.js.map +1 -0
  39. package/src/esm/PlotBoilerplate.d.ts +11 -1
  40. package/src/esm/PlotBoilerplate.js +23 -7
  41. package/src/esm/PlotBoilerplate.js.map +1 -1
  42. package/src/esm/VertexListeners.d.ts +2 -1
  43. package/src/esm/VertexListeners.js +5 -1
  44. package/src/esm/VertexListeners.js.map +1 -1
  45. package/src/esm/draw.d.ts +18 -2
  46. package/src/esm/draw.js +21 -2
  47. package/src/esm/draw.js.map +1 -1
  48. package/src/esm/drawgl.d.ts +18 -3
  49. package/src/esm/drawgl.js +19 -2
  50. package/src/esm/drawgl.js.map +1 -1
  51. package/src/esm/drawutilssvg.d.ts +19 -1
  52. package/src/esm/drawutilssvg.js +28 -5
  53. package/src/esm/drawutilssvg.js.map +1 -1
  54. package/src/esm/interfaces/DrawLib.d.ts +18 -1
  55. package/src/esm/interfaces/DrawLib.js +2 -0
  56. package/src/esm/interfaces/DrawLib.js.map +1 -1
  57. package/src/esm/utils/Params.d.ts +3 -2
  58. package/src/esm/utils/Params.js +11 -3
  59. package/src/esm/utils/Params.js.map +1 -1
  60. package/src/esm/utils/WebColors.d.ts +2 -1
  61. package/src/esm/utils/WebColors.js +15 -2
  62. package/src/esm/utils/WebColors.js.map +1 -1
  63. package/src/esm/utils/algorithms/HobbyPath.js +13 -16
  64. package/src/esm/utils/algorithms/HobbyPath.js.map +1 -1
  65. package/src/esm/utils/helpers/BezierResizeHelper.d.ts +144 -0
  66. package/src/esm/utils/helpers/BezierResizeHelper.js +314 -0
  67. package/src/esm/utils/helpers/BezierResizeHelper.js.map +1 -0
  68. package/src/ts/PlotBoilerplate.ts +37 -11
  69. package/src/ts/VertexListeners.ts +5 -1
  70. package/src/ts/draw.ts +32 -2
  71. package/src/ts/drawgl.ts +38 -3
  72. package/src/ts/drawutilssvg.ts +33 -6
  73. package/src/ts/interfaces/DrawLib.ts +19 -1
  74. package/src/ts/utils/Params.ts +11 -3
  75. package/src/ts/utils/WebColors.ts +15 -2
  76. package/src/ts/utils/algorithms/HobbyPath.ts +248 -272
  77. package/src/ts/utils/helpers/BezierResizeHelper.ts +390 -0
  78. 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
- startControlPoints : Array<Vertex>;
24
- endControlPoints : Array<Vertex>;
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
- * @member {Array<Vertex>} vertices
39
- * @memberof HobbyPath
40
- * @type {Array<Vertex>}
41
- * @instance
42
- **/
43
- private vertices : Array<Vertex>;
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
- * Add a new point to the end of the vertex sequence.
57
- *
58
- * @name addPoint
59
- * @memberof HobbyPath
60
- * @instance
61
- * @param {Vertex} p - The vertex (point) to add.
62
- **/
63
- addPoint(p: Vertex) : void {
64
- this.vertices.push( p );
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
- * Computes the control point coordinates for a Hobby curve through
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
- if( typeof omega === 'undefined' )
125
- omega = 0;
126
-
127
- let n : number = this.vertices.length - (circular ? 0 : 1);
128
- let D : Array<number> = new Array<number>(n);
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
- let ds : Array<Vertex> = new Array<Vertex>(n);
131
- var succ = (i:number) : number => { return circular ? ((i+1)%n) : (i+1) };
132
- var pred = (i:number) : number => { return circular ? ((i + n - 1) % n) : (i-1) };
133
-
134
- for (let i = 0; i < n; i++) {
135
- // the "next" point in a modular way
136
- let j : number = succ(i);
137
- ds[i] = this.vertices[i].difference( this.vertices[j] );
138
- D[i] = Math.sqrt(ds[i].x*ds[i].x+ds[i].y*ds[i].y);
139
- }
140
- let gamma : Array<number> = new Array(n + (circular?0:1));
141
- for (let i = (circular?0:1); i < n; i++) {
142
- // the "previous" point in a modular way
143
- let k : number = pred(i);
144
- let sin = ds[k].y / D[k];
145
- let cos = ds[k].x / D[k];
146
- let vec = HobbyPath.utils.rotate(ds[i], -sin, cos);
147
- gamma[i] = Math.atan2(vec.y, vec.x);
148
- }
149
- if( !circular )
150
- gamma[n] = 0;
151
- let a : Array<number> = new Array(n + (circular?0:1));
152
- let b : Array<number> = new Array(n + (circular?0:1));
153
- let c : Array<number> = new Array(n + (circular?0:1));
154
- let d : Array<number> = new Array(n + (circular?0:1));
155
- for (let i = (circular?0:1); i < n; i++) {
156
- // j is the "next" point, k the "previous" one
157
- let j : number = succ(i);
158
- let k : number = pred(i);
159
- // see video for the equations
160
- a[i] = 1 / D[k];
161
- b[i] = (2*D[k]+2*D[i])/(D[k]*D[i]);
162
- c[i] = 1 / D[i];
163
- d[i] = -(2*gamma[i]*D[i]+gamma[j]*D[k])/(D[k]*D[i]);
164
- }
165
- // make matrix tridiagonal in preparation for the "sherman" function
166
- var alpha : Array<number>;
167
- var beta : Array<number>;
168
- if( circular ) {
169
- let s : number = a[0]*omega; // Use omega here?
170
- a[0] = 0;
171
- let t : number = c[n-1]*omega; // Use omega here?
172
- c[n-1] = 0;
173
- alpha = HobbyPath.utils.sherman(a, b, c, d, s, t);
174
- beta = new Array<number>(n);
175
- for (let i = 0; i < n - (circular?0:1); i++) {
176
- // "next" point
177
- let j : number = succ(i);
178
- beta[i] = -gamma[j]-alpha[j];
179
- }
180
- } else {
181
- // see the Jackowski article for the following values; the result
182
- // will be that the curvature at the first point is identical to the
183
- // curvature at the second point (and likewise for the last and
184
- // second-to-last)
185
- b[0] = 2 + omega;
186
- c[0] = 2 * omega + 1;
187
- d[0] = -c[0] * gamma[1];
188
- a[n] = 2 * omega + 1;
189
- b[n] = 2 + omega;
190
- d[n] = 0;
191
- // solve system for the angles called "alpha" in the video
192
- alpha = HobbyPath.utils.thomas(a, b, c, d);
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
- static utils = {
217
- // rotates a vector [x, y] about an angle; the angle is implicitly
218
- // determined by its sine and cosine
219
- rotate : (vert: Vertex,
220
- sin: number,
221
- cos: number) : Vertex => {
222
- return new Vertex( vert.x*cos - vert.y*sin, vert.x*sin + vert.y*cos );
223
- },
224
- // rotates a vector [x, y] about the angle alpha
225
- rotateAngle: (vert: Vertex, alpha: number) : Vertex => {
226
- return HobbyPath.utils.rotate(vert, Math.sin(alpha), Math.cos(alpha));
227
- },
228
- // returns a normalized version of the vector
229
- normalize : (vec: Vertex) : Vertex => {
230
- let n = Math.hypot(vec.x, vec.y);
231
- if (n == 0)
232
- return new Vertex(0,0);
233
- else
234
- return new Vertex( vec.x/n, vec.y/n ); // TODO: do in-place
235
- },
236
- // the "velocity function" (also called rho in the video); a and b are
237
- // the angles alpha and beta, the return value is the distance between
238
- // a control point and its neighboring point; to compute sigma(a,b)
239
- // we'll simply use rho(b,a)
240
- rho : (a: number ,
241
- b: number) : number => {
242
- // see video for formula
243
- let sa = Math.sin(a);
244
- let sb = Math.sin(b);
245
- let ca = Math.cos(a);
246
- let cb = Math.cos(b);
247
- let s5 = Math.sqrt(5);
248
- let num = 4 + Math.sqrt(8) * (sa - sb/16) * (sb - sa/16) * (ca - cb);
249
- let den = 2 + (s5 - 1) * ca + (3 - s5) * cb;
250
- return num/den;
251
- },
252
- // Implements the Thomas algorithm for a tridiagonal system with i-th
253
- // row a[i]x[i-1] + b[i]x[i] + c[i]x[i+1] = d[i] starting with row
254
- // i=0, ending with row i=n-1 and with a[0] = c[n-1] = 0. Returns the
255
- // values x[i] as an array.
256
- thomas : (a: Array<number>,
257
- b: Array<number>,
258
- c: Array<number>,
259
- d: Array<number>) : Array<number> => {
260
- let n : number = a.length;
261
- let cc : Array<number> = new Array<number>(n);
262
- let dd : Array<number> = new Array<number>(n);
263
- // forward sweep
264
- cc[0] = c[0] / b[0];
265
- dd[0] = d[0] / b[0];
266
- for (let i = 1; i < n; i++) {
267
- let den : number = b[i] - cc[i-1]*a[i];
268
- cc[i] = c[i] / den;
269
- dd[i] = (d[i] - dd[i-1]*a[i]) / den;
270
- }
271
- let x : Array<number> = new Array<number>(n);
272
- // back substitution
273
- x[n-1] = dd[n-1];
274
- for (let i = n-2; i >= 0; i--)
275
- x[i] = dd[i] - cc[i]*x[i+1];
276
- return x;
277
- },
278
- // Solves an "almost" tridiagonal linear system with i-th row
279
- // a[i]x[i-1] + b[i]x[i] + c[i]x[i+1] = d[i] starting with row i=0,
280
- // ending with row i=n-1 and with a[0] = c[n-1] = 0. Returns the
281
- // values x[i] as an array. The system is not really tridiagonal
282
- // because the 0-th row is b[0]x[0] + c[0]x[1] + sx[n-1] = d[0] and
283
- // row n-1 is tx[0] + a[n-1]x[n-2] + b[n-1]x[n-1] = d[n-1]. The
284
- // Sherman-Morrison-Woodbury formula is used so that the function
285
- // "thomas" can be called to solve the system.
286
- sherman : ( a: Array<number>,
287
- b: Array<number>,
288
- c: Array<number>,
289
- d: Array<number>,
290
- s: number ,
291
- t: number) : Array<number> => {
292
- const n : number = a.length;
293
- const u : Array<number> = new Array<number>(n);
294
- u.fill(0, 1, n-1);
295
- u[0] = 1;
296
- u[n-1] = 1;
297
- let v : Array<number> = new Array<number>(n);
298
- v.fill(0, 1, n-1);
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