-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCango3D-13v00.js
4251 lines (4209 loc) · 180 KB
/
Cango3D-13v00.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*============================================================================
Filename: Cango3D-13v00.js
Rev: 13
By: A.R.Collins
A basic 3D graphics interface for the canvas
element using Right Handed coordinate system.
Kindly give credit to Dr A R Collins <http://www.arc.id.au/>
Report bugs to tony at arc.id.au
Date |Description |By
----------------------------------------------------------------------------
05May13 First beta of Ver 3 after major re-write ARC
17May13 Released as 3v19 ARC
09Sep13 Released as 4v00 ARC
04Jun14 Released as 5v00 ARC
28Jun14 Released as 6v00 ARC
16Aug14 Released as 7v00 ARC
25May17 Released as 8v00 ARC
30Dec19 Released as 9v00 ARC
30May20 Released as 10v00 ARC
09Jun20 bugfix: avoid divide by 0 in 2D projections and gradient calcs
bugfix: undeclared var in Cgo3Dsegs.appendPath ARC
10Aug20 bugfix: calcSphereShading not called if no transforms applied ARC
11Aug20 Removed unused ofsTfm matrix ARC
15Nov20 Convert transforms to use DOMMatrix ARC
21Jan21 Released as 11v00 ARC
29Apr22 bugfix: calcNormal line 2548 ARC
04May22 Added Quaternion class ARC
05May22 Removed unused calcIncAngle ARC
10May22 Added pitch, yaw, roll dragging ARC
25May22 Refactor esp. to simplify labelling a Panel3D
and unify Extension Objects as Group3D sub-classes ARC
15Jun22 Refactor dragNdrop behaviour, add transformRestore ARC
17Jun22 Changed the order of enableDrag arguments ARC
18Jun22 Make wireframe a property render object not a render argument ARC
21Jun22 Added wireframe method ARC
07Jul22 Refactor, apply transforms to refer to world frame of reference ARC
20Jul22 Make shapeDefs private (apps should use PathSVGUtils module) ARC
21Jul22 Released as 12v00 ARC
16Dec22 Add support for PathSVG to svgToCgo3D ARC
29Dec22 bugfix: Sphere3D 'sun' check used wrong centroid
shadow blur didn't scale with disc radius
add extra color stop to disc shading ARC
08Mar23 bugfix: bad test for PathSVG if PathSVGUtils not loaded ARC
09Mar23 Add roll pitch yaw as well as the old rotateX, Y, Z ARC
14Mar23 Switch roll, pitch, yaw coords to Z down, Y right, X ahead ARC
02Apr23 Get rid of DOMMatrix ARC
03Apr23 clearCanvas clears to cgo.backgroundColor (any argument ignored) ARC
05Apr23 Released as 13v00 ARC
============================================================================*/
// exposed globals
var Cango3D, Group3D, Path3D, Shape3D,
Panel3D, Text3D, Sphere3D,
ObjectByRevolution3D, ObjectByExtrusion3D, SurfaceByExtrusion3D,
svgToCgo3D, // SVG path data conversion utility function
Cgo3Dsegs, Point,
RGBAColor;
var max_x_seen = -1000000000;
var max_y_seen = -1000000000;
var min_x_seen = 1000000000;
var min_y_seen = 1000000000;
(function()
{
"use strict";
function clone(obj)
{
if (obj === null || obj === undefined)
{
return obj;
}
const nObj = (Array.isArray(obj)) ? [] : new obj.constructor();
for (let i in obj)
{
if (obj[i] && typeof obj[i] === "object")
{
nObj[i] = clone(obj[i]);
}
else
{
nObj[i] = obj[i];
}
}
return nObj;
}
/* const matrixTranspose = function(mat)
{
const width = mat[0].length;
const height = mat.length;
const transposed = [];
for (let i = 0; i < width; i++)
{
transposed[i] = [];
for (let j = 0; j < height; j++)
{
transposed[i][j] = mat[j][i];
}
}
return transposed;
};
const matrixMultiply = function(m1, m2)
{
const height = m1.length; // height of result = M1 rows
const width = m2[0].length; // width of result = M2 columns
const m1cols = m1[0].length;
const result = [];
if (m1cols != m2.length)
{
console.warn("matrix size error");
return result;
}
for (let r = 0; r < height; r++)
{
result[r] = [];
for (let c = 0; c < width; c++)
{
let sum = 0;
for (let p = 0; p < m1cols; p++)
{
sum += m1[r][p] * m2[p][c];
}
result[r][c] = sum;
}
}
return result;
}; */
/*=====================================================================
* A 3D coordinate (right handed system)
*
* X +ve right
* Y +ve up
* Z +ve out screen
*=====================================================================*/
class Point {
constructor(x=0, y=0, z=0)
{
this.x = x;
this.y = y;
this.z = z;
// Translated, rotated, scaled
this.tx = this.x;
this.ty = this.y;
this.tz = this.z;
// tx, ty, tz, projected to 2D as seen from viewpoint
this.fx = 0;
this.fy = 0;
}
hardTransform(mat)
{
this.tx = mat[0][0]*this.x + mat[0][1]*this.y + mat[0][2]*this.z + mat[0][3];
this.ty = mat[1][0]*this.x + mat[1][1]*this.y + mat[1][2]*this.z + mat[1][3];
this.tz = mat[2][0]*this.x + mat[2][1]*this.y + mat[2][2]*this.z + mat[2][3];
this.x = this.tx;
this.y = this.ty;
this.z = this.tz;
if (max_x_seen < this.x ) { max_x_seen = this.x }
if (max_y_seen < this.y ) { max_y_seen = this.y }
if (min_x_seen > this.x ) { min_x_seen = this.x }
if (min_y_seen > this.y ) { min_y_seen = this.y }
}
softTransform(mat)
{
const v0 = mat[0][0]*this.tx + mat[0][1]*this.ty + mat[0][2]*this.tz + mat[0][3];
const v1 = mat[1][0]*this.tx + mat[1][1]*this.ty + mat[1][2]*this.tz + mat[1][3];
const v2 = mat[2][0]*this.tx + mat[2][1]*this.ty + mat[2][2]*this.tz + mat[2][3];
this.tx = v0;
this.ty = v1;
this.tz = v2;
if (max_x_seen < this.tx ) { max_x_seen = this.tx }
if (max_y_seen < this.ty ) { max_y_seen = this.ty }
if (min_x_seen > this.tx ) { min_x_seen = this.tx }
if (min_y_seen > this.ty ) { min_y_seen = this.ty }
// original x,y,z remain unchanged
}
softCoordsReset()
{
this.tx = this.x;
this.ty = this.y;
this.tz = this.z;
// original x,y,z remain unchanged
}
}
class DrawCmd3D {
constructor(cmdStr, controlPoints=[], endPoint)
{
this.drawFn = cmdStr; // String 'moveTo' etc, the canvas command to call
this.cPts = controlPoints; // Array [Point, Point ..] Bezier curve control points
this.ep = endPoint; // Final pen position (undefined for 'closePath' drawFn)
this.parms = []; // [x,y,x,y,x ..] 2D world coordinate version of cPts and ep
this.parmsPx = []; // [x,y,x,y,x ..] 2D pixel coordinate version of cPts and ep
}
}
if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) {
// @info
// Polyfill for SVG getPathData() and setPathData() methods. Based on:
// - SVGPathSeg polyfill by Philip Rogers (MIT License)
// https://github.com/progers/pathseg
// - SVGPathNormalizer by Tadahisa Motooka (MIT License)
// https://github.com/motooka/SVGPathNormalizer/tree/master/src
// - arcToCubicCurves() by Dmitry Baranovskiy (MIT License)
// https://github.com/DmitryBaranovskiy/raphael/blob/v2.1.1/raphael.core.js#L1837
// @author
// Jarosław Foksa
// @source
// https://github.com/jarek-foksa/path-data-polyfill.js/
// @license
// MIT License
!function(){var e={Z:"Z",M:"M",L:"L",C:"C",Q:"Q",A:"A",H:"H",V:"V",S:"S",T:"T",z:"Z",m:"m",l:"l",c:"c",q:"q",a:"a",h:"h",v:"v",s:"s",t:"t"},t=function(e){this._string=e,this._currentIndex=0,this._endIndex=this._string.length,this._prevCommand=null,this._skipOptionalSpaces()},s=-1!==window.navigator.userAgent.indexOf("MSIE ")
t.prototype={parseSegment:function(){var t=this._string[this._currentIndex],s=e[t]?e[t]:null
if(null===s){if(null===this._prevCommand)return null
if(s=("+"===t||"-"===t||"."===t||t>="0"&&"9">=t)&&"Z"!==this._prevCommand?"M"===this._prevCommand?"L":"m"===this._prevCommand?"l":this._prevCommand:null,null===s)return null}else this._currentIndex+=1
this._prevCommand=s
var r=null,a=s.toUpperCase()
return"H"===a||"V"===a?r=[this._parseNumber()]:"M"===a||"L"===a||"T"===a?r=[this._parseNumber(),this._parseNumber()]:"S"===a||"Q"===a?r=[this._parseNumber(),this._parseNumber(),this._parseNumber(),this._parseNumber()]:"C"===a?r=[this._parseNumber(),this._parseNumber(),this._parseNumber(),this._parseNumber(),this._parseNumber(),this._parseNumber()]:"A"===a?r=[this._parseNumber(),this._parseNumber(),this._parseNumber(),this._parseArcFlag(),this._parseArcFlag(),this._parseNumber(),this._parseNumber()]:"Z"===a&&(this._skipOptionalSpaces(),r=[]),null===r||r.indexOf(null)>=0?null:{type:s,values:r}},hasMoreData:function(){return this._currentIndex<this._endIndex},peekSegmentType:function(){var t=this._string[this._currentIndex]
return e[t]?e[t]:null},initialCommandIsMoveTo:function(){if(!this.hasMoreData())return!0
var e=this.peekSegmentType()
return"M"===e||"m"===e},_isCurrentSpace:function(){var e=this._string[this._currentIndex]
return" ">=e&&(" "===e||"\n"===e||" "===e||"\r"===e||"\f"===e)},_skipOptionalSpaces:function(){for(;this._currentIndex<this._endIndex&&this._isCurrentSpace();)this._currentIndex+=1
return this._currentIndex<this._endIndex},_skipOptionalSpacesOrDelimiter:function(){return this._currentIndex<this._endIndex&&!this._isCurrentSpace()&&","!==this._string[this._currentIndex]?!1:(this._skipOptionalSpaces()&&this._currentIndex<this._endIndex&&","===this._string[this._currentIndex]&&(this._currentIndex+=1,this._skipOptionalSpaces()),this._currentIndex<this._endIndex)},_parseNumber:function(){var e=0,t=0,s=1,r=0,a=1,n=1,u=this._currentIndex
if(this._skipOptionalSpaces(),this._currentIndex<this._endIndex&&"+"===this._string[this._currentIndex]?this._currentIndex+=1:this._currentIndex<this._endIndex&&"-"===this._string[this._currentIndex]&&(this._currentIndex+=1,a=-1),this._currentIndex===this._endIndex||(this._string[this._currentIndex]<"0"||this._string[this._currentIndex]>"9")&&"."!==this._string[this._currentIndex])return null
for(var i=this._currentIndex;this._currentIndex<this._endIndex&&this._string[this._currentIndex]>="0"&&this._string[this._currentIndex]<="9";)this._currentIndex+=1
if(this._currentIndex!==i)for(var l=this._currentIndex-1,h=1;l>=i;)t+=h*(this._string[l]-"0"),l-=1,h*=10
if(this._currentIndex<this._endIndex&&"."===this._string[this._currentIndex]){if(this._currentIndex+=1,this._currentIndex>=this._endIndex||this._string[this._currentIndex]<"0"||this._string[this._currentIndex]>"9")return null
for(;this._currentIndex<this._endIndex&&this._string[this._currentIndex]>="0"&&this._string[this._currentIndex]<="9";)s*=10,r+=(this._string.charAt(this._currentIndex)-"0")/s,this._currentIndex+=1}if(this._currentIndex!==u&&this._currentIndex+1<this._endIndex&&("e"===this._string[this._currentIndex]||"E"===this._string[this._currentIndex])&&"x"!==this._string[this._currentIndex+1]&&"m"!==this._string[this._currentIndex+1]){if(this._currentIndex+=1,"+"===this._string[this._currentIndex]?this._currentIndex+=1:"-"===this._string[this._currentIndex]&&(this._currentIndex+=1,n=-1),this._currentIndex>=this._endIndex||this._string[this._currentIndex]<"0"||this._string[this._currentIndex]>"9")return null
for(;this._currentIndex<this._endIndex&&this._string[this._currentIndex]>="0"&&this._string[this._currentIndex]<="9";)e*=10,e+=this._string[this._currentIndex]-"0",this._currentIndex+=1}var v=t+r
return v*=a,e&&(v*=Math.pow(10,n*e)),u===this._currentIndex?null:(this._skipOptionalSpacesOrDelimiter(),v)},_parseArcFlag:function(){if(this._currentIndex>=this._endIndex)return null
var e=null,t=this._string[this._currentIndex]
if(this._currentIndex+=1,"0"===t)e=0
else{if("1"!==t)return null
e=1}return this._skipOptionalSpacesOrDelimiter(),e}}
var r=function(e){if(!e||0===e.length)return[]
var s=new t(e),r=[]
if(s.initialCommandIsMoveTo())for(;s.hasMoreData();){var a=s.parseSegment()
if(null===a)break
r.push(a)}return r},a=SVGPathElement.prototype.setAttribute,n=SVGPathElement.prototype.removeAttribute,u=window.Symbol?Symbol():"__cachedPathData",i=window.Symbol?Symbol():"__cachedNormalizedPathData",l=function(e,t,s,r,a,n,u,i,h,v){var p,_,c,o,d=function(e){return Math.PI*e/180},y=function(e,t,s){var r=e*Math.cos(s)-t*Math.sin(s),a=e*Math.sin(s)+t*Math.cos(s)
return{x:r,y:a}},x=d(u),f=[]
if(v)p=v[0],_=v[1],c=v[2],o=v[3]
else{var I=y(e,t,-x)
e=I.x,t=I.y
var m=y(s,r,-x)
s=m.x,r=m.y
var g=(e-s)/2,b=(t-r)/2,M=g*g/(a*a)+b*b/(n*n)
M>1&&(M=Math.sqrt(M),a=M*a,n=M*n)
var S
S=i===h?-1:1
var V=a*a,A=n*n,C=V*A-V*b*b-A*g*g,P=V*b*b+A*g*g,N=S*Math.sqrt(Math.abs(C/P))
c=N*a*b/n+(e+s)/2,o=N*-n*g/a+(t+r)/2,p=Math.asin(parseFloat(((t-o)/n).toFixed(9))),_=Math.asin(parseFloat(((r-o)/n).toFixed(9))),c>e&&(p=Math.PI-p),c>s&&(_=Math.PI-_),0>p&&(p=2*Math.PI+p),0>_&&(_=2*Math.PI+_),h&&p>_&&(p-=2*Math.PI),!h&&_>p&&(_-=2*Math.PI)}var E=_-p
if(Math.abs(E)>120*Math.PI/180){var D=_,O=s,L=r
_=h&&_>p?p+120*Math.PI/180*1:p+120*Math.PI/180*-1,s=c+a*Math.cos(_),r=o+n*Math.sin(_),f=l(s,r,O,L,a,n,u,0,h,[_,D,c,o])}E=_-p
var k=Math.cos(p),G=Math.sin(p),T=Math.cos(_),Z=Math.sin(_),w=Math.tan(E/4),H=4/3*a*w,Q=4/3*n*w,z=[e,t],F=[e+H*G,t-Q*k],q=[s+H*Z,r-Q*T],j=[s,r]
if(F[0]=2*z[0]-F[0],F[1]=2*z[1]-F[1],v)return[F,q,j].concat(f)
f=[F,q,j].concat(f)
for(var R=[],U=0;U<f.length;U+=3){var a=y(f[U][0],f[U][1],x),n=y(f[U+1][0],f[U+1][1],x),B=y(f[U+2][0],f[U+2][1],x)
R.push([a.x,a.y,n.x,n.y,B.x,B.y])}return R},h=function(e){return e.map(function(e){return{type:e.type,values:Array.prototype.slice.call(e.values)}})},v=function(e){var t=[],s=null,r=null,a=null,n=null
return e.forEach(function(e){var u=e.type
if("M"===u){var i=e.values[0],l=e.values[1]
t.push({type:"M",values:[i,l]}),a=i,n=l,s=i,r=l}else if("m"===u){var i=s+e.values[0],l=r+e.values[1]
t.push({type:"M",values:[i,l]}),a=i,n=l,s=i,r=l}else if("L"===u){var i=e.values[0],l=e.values[1]
t.push({type:"L",values:[i,l]}),s=i,r=l}else if("l"===u){var i=s+e.values[0],l=r+e.values[1]
t.push({type:"L",values:[i,l]}),s=i,r=l}else if("C"===u){var h=e.values[0],v=e.values[1],p=e.values[2],_=e.values[3],i=e.values[4],l=e.values[5]
t.push({type:"C",values:[h,v,p,_,i,l]}),s=i,r=l}else if("c"===u){var h=s+e.values[0],v=r+e.values[1],p=s+e.values[2],_=r+e.values[3],i=s+e.values[4],l=r+e.values[5]
t.push({type:"C",values:[h,v,p,_,i,l]}),s=i,r=l}else if("Q"===u){var h=e.values[0],v=e.values[1],i=e.values[2],l=e.values[3]
t.push({type:"Q",values:[h,v,i,l]}),s=i,r=l}else if("q"===u){var h=s+e.values[0],v=r+e.values[1],i=s+e.values[2],l=r+e.values[3]
t.push({type:"Q",values:[h,v,i,l]}),s=i,r=l}else if("A"===u){var i=e.values[5],l=e.values[6]
t.push({type:"A",values:[e.values[0],e.values[1],e.values[2],e.values[3],e.values[4],i,l]}),s=i,r=l}else if("a"===u){var i=s+e.values[5],l=r+e.values[6]
t.push({type:"A",values:[e.values[0],e.values[1],e.values[2],e.values[3],e.values[4],i,l]}),s=i,r=l}else if("H"===u){var i=e.values[0]
t.push({type:"H",values:[i]}),s=i}else if("h"===u){var i=s+e.values[0]
t.push({type:"H",values:[i]}),s=i}else if("V"===u){var l=e.values[0]
t.push({type:"V",values:[l]}),r=l}else if("v"===u){var l=r+e.values[0]
t.push({type:"V",values:[l]}),r=l}else if("S"===u){var p=e.values[0],_=e.values[1],i=e.values[2],l=e.values[3]
t.push({type:"S",values:[p,_,i,l]}),s=i,r=l}else if("s"===u){var p=s+e.values[0],_=r+e.values[1],i=s+e.values[2],l=r+e.values[3]
t.push({type:"S",values:[p,_,i,l]}),s=i,r=l}else if("T"===u){var i=e.values[0],l=e.values[1]
t.push({type:"T",values:[i,l]}),s=i,r=l}else if("t"===u){var i=s+e.values[0],l=r+e.values[1]
t.push({type:"T",values:[i,l]}),s=i,r=l}else("Z"===u||"z"===u)&&(t.push({type:"Z",values:[]}),s=a,r=n)}),t},p=function(e){var t=[],s=null,r=null,a=null,n=null,u=null,i=null,h=null
return e.forEach(function(e){if("M"===e.type){var v=e.values[0],p=e.values[1]
t.push({type:"M",values:[v,p]}),i=v,h=p,n=v,u=p}else if("C"===e.type){var _=e.values[0],c=e.values[1],o=e.values[2],d=e.values[3],v=e.values[4],p=e.values[5]
t.push({type:"C",values:[_,c,o,d,v,p]}),r=o,a=d,n=v,u=p}else if("L"===e.type){var v=e.values[0],p=e.values[1]
t.push({type:"L",values:[v,p]}),n=v,u=p}else if("H"===e.type){var v=e.values[0]
t.push({type:"L",values:[v,u]}),n=v}else if("V"===e.type){var p=e.values[0]
t.push({type:"L",values:[n,p]}),u=p}else if("S"===e.type){var y,x,o=e.values[0],d=e.values[1],v=e.values[2],p=e.values[3]
"C"===s||"S"===s?(y=n+(n-r),x=u+(u-a)):(y=n,x=u),t.push({type:"C",values:[y,x,o,d,v,p]}),r=o,a=d,n=v,u=p}else if("T"===e.type){var _,c,v=e.values[0],p=e.values[1]
"Q"===s||"T"===s?(_=n+(n-r),c=u+(u-a)):(_=n,c=u)
var y=n+2*(_-n)/3,x=u+2*(c-u)/3,f=v+2*(_-v)/3,I=p+2*(c-p)/3
t.push({type:"C",values:[y,x,f,I,v,p]}),r=_,a=c,n=v,u=p}else if("Q"===e.type){var _=e.values[0],c=e.values[1],v=e.values[2],p=e.values[3],y=n+2*(_-n)/3,x=u+2*(c-u)/3,f=v+2*(_-v)/3,I=p+2*(c-p)/3
t.push({type:"C",values:[y,x,f,I,v,p]}),r=_,a=c,n=v,u=p}else if("A"===e.type){var m=Math.abs(e.values[0]),g=Math.abs(e.values[1]),b=e.values[2],M=e.values[3],S=e.values[4],v=e.values[5],p=e.values[6]
if(0===m||0===g)t.push({type:"C",values:[n,u,v,p,v,p]}),n=v,u=p
else if(n!==v||u!==p){var V=l(n,u,v,p,m,g,b,M,S)
V.forEach(function(e){t.push({type:"C",values:e})}),n=v,u=p}}else"Z"===e.type&&(t.push(e),n=i,u=h)
s=e.type}),t}
SVGPathElement.prototype.setAttribute=function(e,t){"d"===e&&(this[u]=null,this[i]=null),a.call(this,e,t)},SVGPathElement.prototype.removeAttribute=function(e,t){"d"===e&&(this[u]=null,this[i]=null),n.call(this,e)},SVGPathElement.prototype.getPathData=function(e){if(e&&e.normalize){if(this[i])return h(this[i])
var t
this[u]?t=h(this[u]):(t=r(this.getAttribute("d")||""),this[u]=h(t))
var s=p(v(t))
return this[i]=h(s),s}if(this[u])return h(this[u])
var t=r(this.getAttribute("d")||"")
return this[u]=h(t),t},SVGPathElement.prototype.setPathData=function(e){if(0===e.length)s?this.setAttribute("d",""):this.removeAttribute("d")
else{for(var t="",r=0,a=e.length;a>r;r+=1){var n=e[r]
r>0&&(t+=" "),t+=n.type,n.values&&n.values.length>0&&(t+=" "+n.values.join(" "))}this.setAttribute("d",t)}},SVGRectElement.prototype.getPathData=function(e){var t=this.x.baseVal.value,s=this.y.baseVal.value,r=this.width.baseVal.value,a=this.height.baseVal.value,n=this.hasAttribute("rx")?this.rx.baseVal.value:this.ry.baseVal.value,u=this.hasAttribute("ry")?this.ry.baseVal.value:this.rx.baseVal.value
n>r/2&&(n=r/2),u>a/2&&(u=a/2)
var i=[{type:"M",values:[t+n,s]},{type:"H",values:[t+r-n]},{type:"A",values:[n,u,0,0,1,t+r,s+u]},{type:"V",values:[s+a-u]},{type:"A",values:[n,u,0,0,1,t+r-n,s+a]},{type:"H",values:[t+n]},{type:"A",values:[n,u,0,0,1,t,s+a-u]},{type:"V",values:[s+u]},{type:"A",values:[n,u,0,0,1,t+n,s]},{type:"Z",values:[]}]
return i=i.filter(function(e){return"A"!==e.type||0!==e.values[0]&&0!==e.values[1]?!0:!1}),e&&e.normalize===!0&&(i=p(i)),i},SVGCircleElement.prototype.getPathData=function(e){var t=this.cx.baseVal.value,s=this.cy.baseVal.value,r=this.r.baseVal.value,a=[{type:"M",values:[t+r,s]},{type:"A",values:[r,r,0,0,1,t,s+r]},{type:"A",values:[r,r,0,0,1,t-r,s]},{type:"A",values:[r,r,0,0,1,t,s-r]},{type:"A",values:[r,r,0,0,1,t+r,s]},{type:"Z",values:[]}]
return e&&e.normalize===!0&&(a=p(a)),a},SVGEllipseElement.prototype.getPathData=function(e){var t=this.cx.baseVal.value,s=this.cy.baseVal.value,r=this.rx.baseVal.value,a=this.ry.baseVal.value,n=[{type:"M",values:[t+r,s]},{type:"A",values:[r,a,0,0,1,t,s+a]},{type:"A",values:[r,a,0,0,1,t-r,s]},{type:"A",values:[r,a,0,0,1,t,s-a]},{type:"A",values:[r,a,0,0,1,t+r,s]},{type:"Z",values:[]}]
return e&&e.normalize===!0&&(n=p(n)),n},SVGLineElement.prototype.getPathData=function(){return[{type:"M",values:[this.x1.baseVal.value,this.y1.baseVal.value]},{type:"L",values:[this.x2.baseVal.value,this.y2.baseVal.value]}]},SVGPolylineElement.prototype.getPathData=function(){for(var e=[],t=0;t<this.points.numberOfItems;t+=1){var s=this.points.getItem(t)
e.push({type:0===t?"M":"L",values:[s.x,s.y]})}return e},SVGPolygonElement.prototype.getPathData=function(){for(var e=[],t=0;t<this.points.numberOfItems;t+=1){var s=this.points.getItem(t)
e.push({type:0===t?"M":"L",values:[s.x,s.y]})}return e.push({type:"Z",values:[]}),e}}()
}
/*========================================================
* svgProtocol (a function for use with Array.reduce)
* -------------------------------------------------------
* Object holding definitions of each command with methods
* to convert to Cgo2D for both cartesian and SVG
* coordinate systems
*========================================================*/
const svgProtocol = {
"M": {
parmCount3D: 3,
extCmd: "L",
toAbs3D: function(acc, curr) {
const cmd = curr[0].toUpperCase(); // uppercase command means absolute coords
let x = curr[1],
y = curr[2],
z = curr[3];
// Check if 'curr' was a relative (lowercase) command
if (cmd !== curr[0]) {
x += acc.px;
y += acc.py;
z += acc.pz;
}
const currAbs = [cmd, x, y, z];
acc.px = x;
acc.py = y;
acc.pz = z;
return currAbs;
},
toDrawCmd: function(curr){
const x = curr[1],
y = curr[2],
z = curr[3],
cPts = [],
ep = new Point(x, y, z);
return new DrawCmd3D('moveTo', cPts, ep);
}
},
"L": {
parmCount3D: 3,
extCmd: "L",
toAbs3D: function(acc, curr) {
const cmd = curr[0].toUpperCase(); // uppercase command means absolute coords
let x = curr[1],
y = curr[2],
z = curr[3];
// Check if 'curr' was a relative (lowercase) command
if (cmd !== curr[0]) {
x += acc.px;
y += acc.py;
z += acc.pz;
}
const currAbs = [cmd, x, y, z];
acc.px = x;
acc.py = y;
acc.pz = z;
return currAbs;
},
toDrawCmd: function(curr){
const x = curr[1],
y = curr[2],
z = curr[3],
cPts = [],
ep = new Point(x, y, z);
return new DrawCmd3D('lineTo', cPts, ep);
}
},
"C": { // Cubic Bezier curve
parmCount3D: 9,
extCmd: "C",
toAbs3D: function(acc, curr) {
const cmd = curr[0].toUpperCase(); // uppercase command means absolute coords
let c1x = curr[1],
c1y = curr[2],
c1z = curr[3],
c2x = curr[4],
c2y = curr[5],
c2z = curr[6],
x = curr[7],
y = curr[8],
z = curr[9]
// Check if 'curr' was a relative (lowercase) command
if (cmd !== curr[0]) {
c1x += acc.px;
c1y += acc.py;
c1z += acc.pz;
c2x += acc.px;
c2y += acc.py;
c2z += acc.pz;
x += acc.px;
y += acc.py;
z += acc.pz;
}
const currAbs = [cmd, c1x, c1y, c1z, c2x, c2y, c2z, x, y, z];
acc.px = x;
acc.py = y;
acc.pz = z;
return currAbs;
},
toDrawCmd: function(curr){
const c1x = curr[1],
c1y = curr[2],
c1z = curr[3],
c2x = curr[4],
c2y = curr[5],
c2z = curr[6],
x = curr[7],
y = curr[8],
z = curr[9],
cp1 = new Point(c1x, c1y, c1z),
cp2 = new Point(c2x, c2y, c2z),
cPts = [cp1, cp2],
ep = new Point(x, y, z);
return new DrawCmd3D('bezierCurveTo', cPts, ep);
}
},
"Z": {
parmCount3D: 0,
toAbs3D: function(acc, curr) {
const cmd = curr[0].toUpperCase(),
currAbs = [cmd];
// leave pen position where it is in case of multi-segment path
return currAbs;
},
toDrawCmd: function(curr){
return new DrawCmd3D("closePath");
}
}
};
/*========================================================
* Cgo3DCmdCheck (a function for use with Array.reduce)
* -------------------------------------------------------
* Checks each array element, if its a string it must be
* one of the cmd letters in the Cgo3D protocol. If no bad
* cmds found then the array is returned without
* alteration, if not an empty array is returned.
*========================================================*/
function Cgo3DCmdCheck(acc, current, idx)
{
const cgo3Dcmds = ["M", "L", "C", "Z"];
// make a concession to SVG standard and allow all number array
if (idx === 0)
{
if (typeof current !== 'string')
{
acc.push("M");
// now we will fall through to normal checking
}
}
// if we see a command string, check it is in SVG protocol
if (typeof current === "string") { // check each string element
if (!cgo3Dcmds.includes(current.toUpperCase()))
{
console.warn("Cgo3D parse Error: unknown command string '"+current+"'");
acc.badCmdFound = true;
acc.length = 0; // any bad command will force empty array to be returned
}
}
if (!acc.badCmdFound)
{
acc.push(current);
}
// always return when using reduce...
return acc;
}
/*=================================================================
* unExtend3D (a function for use with Array.reduce)
* ----------------------------------------------------------------
* Undo the extension of commands given the svg protocol.
* see description of 'unExtend above.
* This version expects 3D coordinates eg.
*
* var a = ['M', 1, 2, 0, 'L', 3, 4, 0, 5, 6, 0, 7, 8, 0, 'Z']
* var b = a.reduce(unExtend3D, [])
*
* >> ['M', 1, 2, 0, 'L', 3, 4, 0, 'L', 5, 6, 0, 'L', 7, 8, 0, 'Z']
* This assumes no invalid commands are in the string -
* so array should be sanitized before running unExtend3D
*=================================================================*/
function unExtend3D(acc, current, idx, ary)
{
if (idx === 0)
{
acc.nextCmdPos = 0; // set expected position of next command string as first element
}
// Check if current is a command in the protocol (protocol only indexed by upperCase)
if (typeof current === 'string')
{
if (idx < acc.nextCmdPos)
{
// we need another number but found a string
console.warn("bad number of parameters for '"+acc.currCmd+"' at index "+idx);
acc.badParameter = true; // raise flag to bailout processing this
acc.push(0); // try to get out without crashing (acc data will be ditched any way)
return acc;
}
// its a command the protocol knows, remember it across iterations of elements
acc.currCmd = current.toUpperCase(); // save as a property of the acc Array object (not an Array element)
acc.uc = (current.toUpperCase() === current); // upperCase? true or false
// calculate where the next command should be
acc.nextCmdPos = idx + svgProtocol[acc.currCmd].parmCount3D + 1;
acc.push(current);
}
else if (idx < acc.nextCmdPos) // processing parameters
{
// keep shoving parameters
acc.push(current);
}
else
{
// we have got a full set of parameters but hit another number
// instead of a command string, it must be a command extension
// push a the extension command (same as current except for M which extend to L)
// into the accumulator
acc.currCmd = svgProtocol[acc.currCmd].extCmd; // NB: don't change the acc.uc boolean
const newCmd = (acc.uc)? acc.currCmd: acc.currCmd.toLowerCase();
acc.push(newCmd, current);
// calculate where the next command should be
acc.nextCmdPos = idx + svgProtocol[acc.currCmd].parmCount3D;
}
if (idx === ary.length-1) // done processing check if all was ok
{
if (acc.badParameter)
{
acc.length = 0;
}
}
// always return when using reduce...
return acc;
}
/*========================================================
* svgCmdSplitter (a function for use with Array.reduce)
* ------------------------------------------------------
* Split an array on a string type element, e.g.
*
* var a = ['a', 1, 2, 'b', 3, 4, 'c', 5, 6, 7, 8]
* var b = a.reduce(svgCmdSplitter, [])
*
* >> [['a', 1, 2],['b', 3, 4], ['c', 5, 6, 7, 8]]
*======================================================*/
function svgCmdSplitter(acc, curr)
{
// if we see a command string, start a new array element
if (typeof curr === "string") {
acc.push([]);
}
// add this element to the back of the acc's last array
acc[acc.length-1].push(curr);
// always return when using reduce...
return acc;
}
/*====================================================================
* toAbsoluteCoords3D (a function for use with Array.reduce)
* -------------------------------------------------------------------
* Version of the toAbsoluteCoords but expecting 3D coordinates
* eg. ['M', 1, 2, 0, 'l', 3, 4, 1, 'm', 5, 6, 7, 'l', 8, 3, 0, 'z']
* >> ['M', 1, 2, 0, 'L', 4, 6, 1, 'M', 5, 6, 7, 'L', 13, 9, 7, 'Z']
*====================================================================*/
function toAbsoluteCoords3D(acc, current, idx)
{
if (acc.px === undefined)
{
acc.px = 0;
acc.py = 0;
acc.pz = 0;
}
// get protocol object for this command, indexed by uppercase only
const currCmd = svgProtocol[current[0].toUpperCase()];
// call protocol toAbs3D function for this command
// it returns absolute coordinate version based on current
// pen position stored in acc.px, acc.py, acc.pz
const currAbs = currCmd.toAbs3D(acc, current, idx);
acc.push(currAbs);
// always return when using reduce...
return acc;
}
/*============================================================================
* svgToCgo3D (a function that takes 2D SVG data and returns Cgo3D format array
*===========================================================================*/
svgToCgo3D = function(data2D, xOfs=0, yOfs=0)
{
const newCmds3D = [];
let pathStr;
let cmds2D = [];
let svgPathElem;
if ((typeof data2D === "string") && data2D.length) // a string will be svg commands
{
pathStr = data2D;
}
else if (typeof PathSVG === "function" && data2D instanceof PathSVG)
{
pathStr = data2D.toString();
}
else if (Array.isArray(data2D) && data2D.length) // array and not empty
{
if (typeof(data2D[0]) === "number") // must be an Array of numbers
{
pathStr = "M "+data2D.join(" "); // insert 'M' command so its valid SVG
}
else
{
pathStr = data2D.join(" ");
}
}
else
{
console.warn("Type Error: svgToCgo3D invalid input data");
return [];
}
svgPathElem = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgPathElem.setAttribute("d", pathStr);
cmds2D = svgPathElem.getPathData({normalize: true}); // returns segments converted to lines and Bezier curves
if (cmds2D.length < 2) {
console.error("Invalid SVG data", pathStr);
return [];
}
cmds2D.forEach((seg)=>{
newCmds3D.push(seg.type);
for (let j=0; j<seg.values.length; j+=2) // step through the coord pairs
{
newCmds3D.push(seg.values[j]+xOfs, seg.values[j+1]+yOfs, 0);
}
});
return newCmds3D;
}
/*===============================================================================
* toDrawCmds3D (a function for use with Array.reduce)
* ------------------------------------------------------------------------------
* Convert a Cgo3D data array to an array DrawCmd3D objects e.g.
*
* [['M', 0.1, 0.2, 0], ['L', 1, 2, 0], ['C', 3, 4, 5, 6, 2, 9, 0, 1, 2], ['Z']]
*
* will become
* [{ drawFn: "moveTo",
* cPts: [],
* ep: eP // Point object
* },
* { drawFn: "lineTo",
* cPts: [],
* ep: eP // Point object
* },
* { drawFn: "cubicBezierTo",
* cPts: [cp1, cp2], // cp1, cp2 are Point objects
* ep: eP
* }]
*===============================================================================*/
function toDrawCmd3D(acc, current)
{
// call protocol toDrawCmd function for this command
// it returns a DrawCmd3D object made from the current cmd and parms
const currCmd = svgProtocol[current[0].toUpperCase()],
currDC = currCmd.toDrawCmd(current);
if (currDC !== null)
{
acc.push(currDC);
}
// always return when using reduce...
return acc;
}
/*======================================================================
* Cgo3Dsegs: Converts an array of Cgo3D data in format
* ['M',x,y,z, 'L',x,y,z, ... 'C',c1x,c1y,c1z,c2x,c2y,c2z,x,y,z, 'Z']
* defining a path to an array of DrawCmd3D objects adding methods to
* transform the path; translate, rotate etc.
*======================================================================*/
Cgo3Dsegs = class extends Array {
constructor (cgo3Dary)
{
let data;
if (!Array.isArray(cgo3Dary) || (cgo3Dary.length === 0))
{
data = [];
}
else if (Array.isArray(cgo3Dary) && (cgo3Dary[0] instanceof DrawCmd3D))
{
data = drawCmd3DToCgo3D(cgo3Dary);
}
else
{
data = cgo3Dary;
}
const newCmdsAry = data.reduce(Cgo3DCmdCheck, [])
.reduce(unExtend3D, [])
.reduce(svgCmdSplitter, [])
.reduce(toAbsoluteCoords3D, [])
.reduce(toDrawCmd3D, []);
super(...newCmdsAry);
}
dup(){
// convert each drawCmd3D to Cgo3D and generate new Cgo3D segs
const newCgo3D = [];
const cmdType = {moveTo:"M", lineTo:"L", bezierCurveTo:"C", closePath:"Z"};
this.forEach((seg)=>{
newCgo3D.push(cmdType[seg.drawFn]);
seg.cPts.forEach((pt)=>{
newCgo3D.push(pt.x, pt.y, pt.z);
});
if (seg.ep)
{
newCgo3D.push(seg.ep.x, seg.ep.y, seg.ep.z);
}
});
return new Cgo3Dsegs(newCgo3D);
}
translate(xOfs=0, yOfs=0, zOfs=0){
const transMat = translateMatrix(xOfs, yOfs, zOfs);
const newCgo3Dsegs = new Cgo3Dsegs();
this.forEach((seg)=>{
const newSeg = clone(seg);
newSeg.cPts.forEach((pt)=>{
pt.hardTransform(transMat);
});
if (newSeg.ep)
{
newSeg.ep.hardTransform(transMat);
}
newCgo3Dsegs.push(newSeg);
});
return newCgo3Dsegs;
}
rotate(vx=0, vy=0, vz=0, deg=0)
{
const rotMat = rotateMatrix(vx, vy, vz, deg);
const newCgo3Dsegs = new Cgo3Dsegs();
this.forEach((seg)=>{
const newSeg = clone(seg);
newSeg.cPts.forEach((pt)=>{
pt.hardTransform(rotMat);
});
if (newSeg.ep)
{
newSeg.ep.hardTransform(rotMat);
}
newCgo3Dsegs.push(newSeg);
});
return newCgo3Dsegs;
}
rotateX(deg=0)
{
return this.rotate(1, 0, 0, deg);
}
rotateY(deg=0)
{
return this.rotate(0, 1, 0, deg);
}
rotateZ(deg=0)
{
return this.rotate(0, 0, 1, deg);
}
scale(scl=1)
{
const sclMat = scaleMatrix(scl);
const newCgo3Dsegs = new Cgo3Dsegs();
this.forEach((seg)=>{
const newSeg = clone(seg);
newSeg.cPts.forEach((pt)=>{
pt.hardTransform(sclMat);
});
if (newSeg.ep)
{
newSeg.ep.hardTransform(sclMat);
}
newCgo3Dsegs.push(newSeg);
});
return newCgo3Dsegs;
}
scaleNonUniform(xScl, yScl, zScl)
{
const sclMat = scaleNonUniformMatrix(xScl, yScl, zScl);
const newCgo3Dsegs = new Cgo3Dsegs();
this.forEach((seg)=>{
const newSeg = clone(seg);
newSeg.cPts.forEach((pt)=>{
pt.hardTransform(sclMat);
});
if (newSeg.ep)
{
newSeg.ep.hardTransform(sclMat);
}
newCgo3Dsegs.push(newSeg);
});
return newCgo3Dsegs;
}
appendPath(extensionData)
{
let pathSegs;
const newSegs = new Cgo3Dsegs();
if (extensionData instanceof Cgo3Dsegs)
{
pathSegs = extensionData;
}
else
{
pathSegs= new Cgo3Dsegs(extensionData);
}
this.forEach((seg)=>{
const newSeg = clone(seg);
newSegs.push(newSeg);
});
pathSegs.forEach((seg)=>{
const newSeg = clone(seg);
newSegs.push(newSeg);
});
return newSegs;
}
joinPath(extensionData)
{
const newPathSegs = new Cgo3Dsegs();
let extSegs;
if (extensionData instanceof Cgo3Dsegs)
{
extSegs = extensionData;
}
else
{
extSegs= new Cgo3Dsegs(extensionData);
}
let len = this.length;
if (len == 0) // just add the extra including the "M" command
{
extSegs.forEach((seg)=>{
newPathSegs.push(clone(seg));
});
}
else // 'this' has length
{
if (this[len-1].drawFn == "closePath")
{
len += -1; // delete the 'closePath'
}
// start with the org segs
for (let i=0; i<len; i++)
{
newPathSegs.push(clone(this[i]));
}
// now tack on the extra commands skipping the initial "moveTo" segment
for (let j=1; j<extSegs.length; j++)
{
newPathSegs.push(clone(extSegs[j]));
}
}
return newPathSegs;
}
revWinding(){
// reverse the direction of drawing around a path, stops holes in shapes being filled
let zCmd = null,
k,
dCmd,
len = this.length;
const newPathSegs = new Cgo3Dsegs();
if (this[len-1].drawFn === "closePath")
{
zCmd = this[len-1];
len -= 1; // leave off 'closePath' cmd segment
}
// now step back along the path
k = len-1; // k points at the last segment in the path
dCmd = new DrawCmd3D("moveTo", [], clone(this[k].ep)); // make a 'moveTo' command from final coord pair
newPathSegs.push(dCmd); // make this the first command of the output
while (k>0)
{
const revcPts = clone(this[k].cPts); // use clone as original is altered by reverse
dCmd = new DrawCmd3D(this[k].drawFn, revcPts.reverse(), clone(this[k-1].ep));
newPathSegs.push(dCmd);
k -= 1;
}
// add the 'closePath' if it was a closed path
if (zCmd)
{
newPathSegs.push(zCmd);
}
return newPathSegs;
}
};
function drawCmd3DToCgo3D(dCmds)
{
const ary = [];
function rnd(val)
{
return val; //Math.round(val*1000)/1000;
}
function drawCmdtoCgo3D(drawCmd)
{
switch (drawCmd.drawFn)
{
case "moveTo":
ary.push("M");
ary.push(rnd(drawCmd.ep.x), rnd(drawCmd.ep.y), rnd(drawCmd.ep.z));
break;
case "lineTo":
ary.push("L");
ary.push(rnd(drawCmd.ep.x), rnd(drawCmd.ep.y), rnd(drawCmd.ep.z));
break;
case "bezierCurveTo":
ary.push("C");
ary.push(rnd(drawCmd.cPts[0].x), rnd(drawCmd.cPts[0].y), rnd(drawCmd.cPts[0].z));
ary.push(rnd(drawCmd.cPts[1].x), rnd(drawCmd.cPts[1].y), rnd(drawCmd.cPts[1].z));
ary.push(rnd(drawCmd.ep.x), rnd(drawCmd.ep.y), rnd(drawCmd.ep.z));
break;
case "closePath":
ary.push("Z");
break;
}
}
if (Array.isArray(dCmds))
{
dCmds.forEach(drawCmdtoCgo3D);
}
else
{
drawCmdtoCgo3D(dCmds);
}
return ary;
}
const shapeDefs = {
circle: function(dia=1, sectors=12)
{
const r = dia/2;
const incAng = 360/sectors;
function createCircularArc(r, incAngle)
{
// References:
// 1. A. Riskus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa"
// 2. Imre Juhasz, "Approximating the helix with rational cubic Bezier curves"
const alpha = incAngle*Math.PI/360.0, // half included angle
ax = r*Math.cos(alpha),
ay = r*Math.sin(alpha),
b0 = {x:ax, y:-ay},
b1 = {x:(4*r - ax)/3, y:-(r - ax)*(3*r - ax)/(3*ay)},
b2 = {x:(4*r - ax)/3, y:(r - ax)*(3*r - ax)/(3*ay)},
b3 = {x:ax, y:ay};
return ["M", b0.x,b0.y, "C", b1.x,b1.y, b2.x,b2.y, b3.x,b3.y];
}
function rot(ary, degs)
{
// arc = [x0, y0, x1, y1, ...]
// rotate array of x,y points by angle degs
const A = Math.PI*degs/180.0, // radians
sinA = Math.sin(A),
cosA = Math.cos(A);
const rotAry = [];
for (let i=0; i<ary.length; i+=2)
{
const x = ary[i],
y = ary[i+1];
rotAry[i] = x*cosA - y*sinA;
rotAry[i+1] = x*sinA + y*cosA;
}
return rotAry;
}
let circ = createCircularArc(r, incAng);
const arc = circ.slice(4);
for (let s=1; s<sectors; s++)
{
circ = circ.concat(rot(arc, s*incAng));
}
circ.push("Z");
return circ;
},
rectangle: function(w=1, height=0)
{
const h = (height)? height: w;
return ['m', 0.5*w,-0.5*h, 'l',0,h, -w,0, 0,-h, 'z'];
}
};
/**
* A class to parse color values
* @author Stoyan Stefanov <[email protected]>
* @link http://www.phpied.com/rgb-color-parser-in-javascript/
* @license Use it if you like it
*
* supplemented to handle rgba format (alpha 0 .. 1) by ARC 04Sep09
*/
RGBAColor = class {
constructor(color_string)
{
const simple_colors = {
aliceblue: 'f0f8ff',
antiquewhite: 'faebd7',
aqua: '00ffff',
aquamarine: '7fffd4',
azure: 'f0ffff',
beige: 'f5f5dc',
bisque: 'ffe4c4',
black: '000000',
blanchedalmond: 'ffebcd',
blue: '0000ff',
blueviolet: '8a2be2',
brown: 'a52a2a',
burlywood: 'deb887',
cadetblue: '5f9ea0',
chartreuse: '7fff00',
chocolate: 'd2691e',