Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
AroriaNetwork
GitHub Repository: AroriaNetwork/3kho-backup
Path: blob/main/projects/missiles/lib/Path.js
1835 views
1
2
// Setup inheritance
3
Path.prototype = new Shape();
4
Path.prototype.constructor = Path;
5
Path.superclass = Shape.prototype;
6
7
8
9
// Class constants
10
11
Path.COMMAND = 0;
12
Path.NUMBER = 1;
13
Path.EOD = 2;
14
15
Path.PARAMS = {
16
A: [ "rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y" ],
17
a: [ "rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y" ],
18
C: [ "x1", "y1", "x2", "y2", "x", "y" ],
19
c: [ "x1", "y1", "x2", "y2", "x", "y" ],
20
H: [ "x" ],
21
h: [ "x" ],
22
L: [ "x", "y" ],
23
l: [ "x", "y" ],
24
M: [ "x", "y" ],
25
m: [ "x", "y" ],
26
Q: [ "x1", "y1", "x", "y" ],
27
q: [ "x1", "y1", "x", "y" ],
28
S: [ "x2", "y2", "x", "y" ],
29
s: [ "x2", "y2", "x", "y" ],
30
T: [ "x", "y" ],
31
t: [ "x", "y" ],
32
V: [ "y" ],
33
v: [ "y" ],
34
Z: [],
35
z: []
36
};
37
38
39
/*****
40
*
41
* constructor
42
*
43
*****/
44
function Path(svgNode) {
45
if ( arguments.length > 0 ) {
46
this.init(svgNode);
47
}
48
}
49
50
51
/*****
52
*
53
* init
54
*
55
*****/
56
Path.prototype.init = function(svgNode) {
57
if ( svgNode == null || svgNode.localName != "path" )
58
throw new Error("Path.init: Invalid localName: " + svgNode.localName);
59
60
// Call superclass method
61
Path.superclass.init.call(this, svgNode);
62
63
// Convert path data to segments
64
this.segments = null;
65
this.parseData( svgNode.getAttributeNS(null, "d") );
66
};
67
68
69
/*****
70
*
71
* realize
72
*
73
*****/
74
Path.prototype.realize = function() {
75
for ( var i = 0; i < this.segments.length; i++ ) {
76
this.segments[i].realize();
77
}
78
79
this.svgNode.addEventListener("mousedown", this, false);
80
};
81
82
83
/*****
84
*
85
* unrealize
86
*
87
*****/
88
Path.prototype.unrealize = function() {
89
for ( var i = 0; i < this.segments.length; i++ ) {
90
this.segments[i].unrealize();
91
}
92
93
this.svgNode.removeEventListener("mousedown", this, false);
94
};
95
96
97
98
/*****
99
*
100
* refresh
101
*
102
*****/
103
Path.prototype.refresh = function() {
104
var d = new Array();
105
106
for ( var i = 0; i < this.segments.length; i++ ) {
107
d.push( this.segments[i].toString() );
108
}
109
110
this.svgNode.setAttributeNS(null, "d", d.join(" "));
111
};
112
113
114
/*****
115
*
116
* registerHandles
117
*
118
*****/
119
Path.prototype.registerHandles = function() {
120
for ( var i = 0; i < this.segments.length; i++ ) {
121
this.segments[i].registerHandles();
122
}
123
};
124
125
126
/*****
127
*
128
* unregisterHandles
129
*
130
*****/
131
Path.prototype.unregisterHandles = function() {
132
for ( var i = 0; i < this.segments.length; i++ ) {
133
this.segments[i].unregisterHandles();
134
}
135
};
136
137
138
/*****
139
*
140
* selectHandles
141
*
142
*****/
143
Path.prototype.selectHandles = function(select) {
144
for ( var i = 0; i < this.segments.length; i++ ) {
145
this.segments[i].selectHandles(select);
146
}
147
};
148
149
150
/*****
151
*
152
* showHandles
153
*
154
*****/
155
Path.prototype.showHandles = function(state) {
156
for ( var i = 0; i < this.segments.length; i++ ) {
157
this.segments[i].showHandles(state);
158
}
159
};
160
161
162
/*****
163
*
164
* appendPathSegment
165
*
166
*****/
167
Path.prototype.appendPathSegment = function(segment) {
168
segment.previous = this.segments[this.segments.length-1];
169
170
this.segments.push(segment);
171
};
172
173
174
/*****
175
*
176
* parseData
177
*
178
*****/
179
/* XXX BM. This function has been heavilly hacked to get elliptical arcs working XXX */
180
181
Path.prototype.parseData = function(d) {
182
// convert path data to token array
183
var tokens = this.tokenize(d);
184
185
// point to first token in array
186
var index = 0;
187
188
// get the current token
189
var token = tokens[index];
190
191
// set mode to signify new path
192
var mode = "BOD";
193
194
// init segment array
195
// NOTE: should destroy previous segment handles here
196
this.segments = new Array();
197
198
// Process all tokens
199
while ( !token.typeis(Path.EOD) ) {
200
var param_length;
201
var params = new Array();
202
203
if ( mode == "BOD" ) {
204
// Start of new path. Must be a moveto command
205
if ( token.text == "M" || token.text == "m" ) {
206
// Advance past command token
207
index++;
208
209
// Get count of numbers that must follow this command
210
param_length = Path.PARAMS[token.text].length;
211
212
// Set new parsing mode
213
mode = token.text;
214
} else {
215
// Oops. New path didn't start with a moveto command
216
throw new Error("Path data must begin with a moveto command");
217
}
218
} else {
219
// Currently in a path definition
220
if ( token.typeis(Path.NUMBER) ) {
221
// Many commands allow you to keep repeating parameters
222
// without specifying the command again. This handles
223
// that case.
224
param_length = Path.PARAMS[mode].length;
225
} else {
226
// Advance past command token
227
index++;
228
229
// Get count of numbers that must follow this command
230
param_length = Path.PARAMS[token.text].length;
231
232
// Set new parsing mode
233
mode = token.text;
234
}
235
}
236
237
// Make sure we have enough tokens left to satisfy the number
238
// of parameters we need for the last command
239
if ( (index + param_length) < tokens.length ) {
240
// Get each parameter
241
for (var i = index; i < index + param_length; i++) {
242
var number = tokens[i];
243
244
// Make sure each parameter is a number.
245
if ( number.typeis(Path.NUMBER) )
246
params[params.length] = number.text;
247
else
248
throw new Error("Parameter type is not a number: " + mode + "," + number.text);
249
}
250
251
// NOTE: Should create add an appendPathSegment (careful, that
252
// effects RelativePathSegments
253
var segment;
254
var length = this.segments.length;
255
var previous = ( length == 0 ) ? null : this.segments[length-1];
256
switch (mode) {
257
case "A":
258
var startPoint = previous.getLastPoint();
259
var x1 = startPoint.x;
260
var y1 = startPoint.y;
261
var bezier_param_list = arcToCurve.apply(this,[x1,y1].concat(params));
262
for (var i=0; i<bezier_param_list.length/6; i++) {
263
var bezier_params = new Array();
264
for (var j=0; j<6; j++) {
265
bezier_params.push(bezier_param_list.shift());
266
}
267
this.segments.push(new AbsoluteCurveto3(bezier_params, this, previous));
268
previous = this.segments[length-1];
269
}
270
break;
271
272
case "C": this.segments.push(new AbsoluteCurveto3( params, this, previous )); break;
273
case "c": this.segments.push(new RelativeCurveto3( params, this, previous )); break;
274
case "H": this.segments.push(new AbsoluteHLineto( params, this, previous )); break;
275
case "L": this.segments.push(new AbsoluteLineto( params, this, previous )); break;
276
case "l": this.segments.push(new RelativeLineto( params, this, previous )); break;
277
case "M": this.segments.push(new AbsoluteMoveto( params, this, previous )); break;
278
case "m": this.segments.push(new RelativeMoveto( params, this, previous )); break;
279
case "Q": this.segments.push(new AbsoluteCurveto2( params, this, previous )); break;
280
case "q": this.segments.push(new RelativeCurveto2( params, this, previous )); break;
281
case "S": this.segments.push(new AbsoluteSmoothCurveto3( params, this, previous )); break;
282
case "s": this.segments.push(new RelativeSmoothCurveto3( params, this, previous )); break;
283
case "T": this.segments.push(new AbsoluteSmoothCurveto2( params, this, previous )); break;
284
case "t": this.segments.push(new RelativeSmoothCurveto2( params, this, previous )); break;
285
case "Z": this.segments.push(new RelativeClosePath( params, this, previous )); break;
286
case "z": this.segments.push(new RelativeClosePath( params, this, previous )); break;
287
default:
288
throw new Error("Unsupported segment type: " + mode);
289
};
290
291
// advance to the next unused token
292
index += param_length;
293
294
// get current token
295
token = tokens[index];
296
297
// Lineto's follow moveto when no command follows moveto params
298
if ( mode == "M" ) mode = "L";
299
if ( mode == "m" ) mode = "l";
300
} else {
301
throw new Error("Path data ended before all parameters were found");
302
}
303
}
304
}
305
306
307
/*****
308
*
309
* tokenize
310
*
311
* Need to add support for scientific notation
312
*
313
*****/
314
Path.prototype.tokenize = function(d) {
315
var tokens = new Array();
316
317
while ( d != "" ) {
318
if ( d.match(/^([ \t\r\n,]+)/) )
319
{
320
d = d.substr(RegExp.$1.length);
321
}
322
else if ( d.match(/^([aAcChHlLmMqQsStTvVzZ])/) )
323
{
324
tokens[tokens.length] = new Token(Path.COMMAND, RegExp.$1);
325
d = d.substr(RegExp.$1.length);
326
}
327
else if ( d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/) )
328
{
329
tokens[tokens.length] = new Token(Path.NUMBER, parseFloat(RegExp.$1));
330
d = d.substr(RegExp.$1.length);
331
}
332
else
333
{
334
throw new Error("Unrecognized segment command: " + d);
335
//d = "";
336
}
337
}
338
339
tokens[tokens.length] = new Token(Path.EOD, null);
340
341
return tokens;
342
}
343
344
345
/*****
346
*
347
* intersection methods
348
*
349
*****/
350
351
/*****
352
*
353
* intersectShape
354
*
355
*****/
356
Path.prototype.intersectShape = function(shape) {
357
var result = new Intersection("No Intersection");
358
359
for ( var i = 0; i < this.segments.length; i++ ) {
360
var inter = Intersection.intersectShapes(this.segments[i],shape);
361
362
result.appendPoints(inter.points);
363
}
364
365
if ( result.points.length > 0 ) result.status = "Intersection";
366
367
return result;
368
};
369
370
371
/*****
372
*
373
* get/set methods
374
*
375
*****/
376
377
/*****
378
*
379
* getIntersectionParams
380
*
381
*****/
382
Path.prototype.getIntersectionParams = function() {
383
return new IntersectionParams(
384
"Path",
385
[]
386
);
387
};
388
389
390
391
392
function arcToCurve (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
393
// for more information of where this math came from visit:
394
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
395
var _120 = Math.PI * 120 / 180,
396
rad = Math.PI / 180 * (+angle || 0),
397
res = [],
398
xy,
399
rotate = function (x, y, rad) {
400
var X = x * Math.cos(rad) - y * Math.sin(rad),
401
Y = x * Math.sin(rad) + y * Math.cos(rad);
402
return {x: X, y: Y};
403
},
404
f1, f2,
405
cx, cy;
406
if (!recursive) {
407
xy = rotate(x1, y1, -rad);
408
x1 = xy.x;
409
y1 = xy.y;
410
xy = rotate(x2, y2, -rad);
411
x2 = xy.x;
412
y2 = xy.y;
413
var cos = Math.cos(Math.PI / 180 * angle),
414
sin = Math.sin(Math.PI / 180 * angle),
415
x = (x1 - x2) / 2,
416
y = (y1 - y2) / 2;
417
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
418
if (h > 1) {
419
h = Math.sqrt(h);
420
rx = h * rx;
421
ry = h * ry;
422
}
423
var rx2 = rx * rx,
424
ry2 = ry * ry,
425
k = (large_arc_flag == sweep_flag ? -1 : 1) *
426
Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
427
428
cx = k * rx * y / ry + (x1 + x2) / 2;
429
cy = k * -ry * x / rx + (y1 + y2) / 2;
430
431
f1 = Math.asin(((y1 - cy) / ry).toFixed(9));
432
f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
433
f1 = x1 < cx ? Math.PI - f1 : f1;
434
f2 = x2 < cx ? Math.PI - f2 : f2;
435
// f1 < 0 && (f1 = Math.PI * 2 + f1);
436
f1 = f1 < 0 ? Math.PI * 2 + f1 : f1;
437
f2 = f2 < 0 ? Math.PI * 2 + f2 : f2;
438
if (sweep_flag && f1 > f2) {
439
f1 = f1 - Math.PI * 2;
440
}
441
if (!sweep_flag && f2 > f1) {
442
f2 = f2 - Math.PI * 2;
443
}
444
} else {
445
f1 = recursive[0];
446
f2 = recursive[1];
447
cx = recursive[2];
448
cy = recursive[3];
449
}
450
var df = f2 - f1;
451
if (Math.abs(df) > _120) {
452
var f2old = f2,
453
x2old = x2,
454
y2old = y2;
455
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
456
x2 = cx + rx * Math.cos(f2);
457
y2 = cy + ry * Math.sin(f2);
458
res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
459
}
460
df = f2 - f1;
461
var c1 = Math.cos(f1),
462
s1 = Math.sin(f1),
463
c2 = Math.cos(f2),
464
s2 = Math.sin(f2),
465
t = Math.tan(df / 4),
466
hx = 4 / 3 * rx * t,
467
hy = 4 / 3 * ry * t,
468
m1 = [x1, y1],
469
m2 = [x1 + hx * s1, y1 - hy * c1],
470
m3 = [x2 + hx * s2, y2 - hy * c2],
471
m4 = [x2, y2];
472
m2[0] = 2 * m1[0] - m2[0];
473
m2[1] = 2 * m1[1] - m2[1];
474
if (recursive) {
475
return [m2, m3, m4].concat(res);
476
} else {
477
res = [m2, m3, m4].concat(res).join().split(",");
478
var newres = [];
479
for (var i = 0, ii = res.length; i < ii; i++) {
480
newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
481
}
482
return newres;
483
}
484
}
485
486