-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathsvg_cooker.html
581 lines (486 loc) · 34.9 KB
/
svg_cooker.html
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
<!-- vim: sw=2 ts=2 expandtab smartindent ft=javascript
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SVG cooker</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🍗</text></svg>">
<style>
document, body { margin: 0px; padding: 0px; overflow: hidden; }
#hud {
font-family: monospace;
background-color: white;
position: absolute;
right: 0px;
top: 0px;
#hud_info {
display: flex;
flex-direction: column;
padding: 0.5rem;
gap: 0.7rem;
p {
margin: 0;
}
}
margin: 0.3rem;
box-shadow: 1px 2px 9px black;
}
#drawing_area {
display: inline-block;
background-color: white;
box-shadow: 1px 2px 4px rgba(125, 125, 125, 0.4);
margin: 0.5rem;
#drawing_select {
position: absolute;
right: 0px;
font-size: 1.1rem;
margin: 0.3rem;
box-shadow: 1px 1px 2px rgba(125, 125, 125, 0.4);
}
}
#code_out_box {
#code_out { margin: 0.5rem; }
background-color: #444444;
color: white;
height: 100vh;
width: 100vw;
overflow: scroll;
}
</style>
<script type="importmap">
{
"imports": {
"earcut": "https://unpkg.com/[email protected]/src/earcut.js"
}
}
</script>
</head>
<body>
<div id="code_out_box">
<pre id="code_out"></pre>
</div>
<div id="hud">
<div id="drawing_area">
<select id="drawing_select">
<option value="canvas_extracted">extracted</option>
<option value="canvas_tesselated">tesselated</option>
<option value="svg">svg</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" direction="ltr" width="304.07124999999996" height="274.8134783333222" viewBox="923.3984375 440.5927716666778 304.07124999999996 274.8134783333222" stroke-linecap="round" stroke-linejoin="round" data-color-mode="light" class="tl-container tl-theme__force-sRGB tl-theme__light" style="background-color: rgb(249, 250, 251);"><defs/><g transform="matrix(1, 0, 0, 1, 955.3984, 576.3945)" opacity="1"><g transform="scale(1)"><path d="M2.8609,-1.6692 T3.5046,-0.6202 4.6875,1.7453 5.984,4.4167 7.5668,7.132 9.6585,10.3911 12.029,14.0158 14.5757,18.3153 16.8576,22.3457 18.5519,25.2418 20.1413,27.8712 22.1799,31.2383 24.8084,35.4722 27.4019,39.3965 29.7282,43.0806 31.7789,46.6818 33.6917,50.1802 35.5697,53.6489 37.6028,57.1929 39.7037,60.6195 41.6732,63.7225 43.5007,66.6534 45.1438,69.5313 46.7632,72.5961 48.6099,75.9426 50.3743,78.8261 52.2856,81.4269 54.2259,84.0648 56,86.6569 57.7615,89.3282 59.4027,92.0788 61.1691,95.0462 63.231,98.2507 64.373,99.933 A3.6951,3.6951 0 0 1 58.387,104.267 T57.0918,102.249 54.9214,98.7439 53.0769,95.5525 51.2932,92.4894 49.3737,89.3516 47.4172,86.3831 45.6624,83.8873 43.7782,80.9535 41.7735,77.5058 39.9754,74.1261 38.5428,71.3295 37.0101,68.6596 35.1768,65.6361 33.1598,62.3068 30.9468,58.5503 28.9185,54.8143 27.1838,51.4066 25.4187,47.992 23.4641,44.4813 21.0255,40.6079 18.4565,36.4794 15.8619,32.1387 13.4991,28.4444 11.573,25.629 9.6371,22.7947 7.5766,19.6133 5.238,16.0891 2.5891,12.3058 0.2434,8.5368 -1.244,5.1979 -2.3292,2.6348 -2.8609,1.6692 A3.3123,3.3123 0 0 1 2.8609,-1.6692 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1014.8633, 476.0664)" opacity="1"><g transform="scale(1)"><path d="M2.627,1.8487 T1.9268,2.8286 -0.0823,5.6217 -2.7445,9.1688 -5.3281,12.4592 -7.5312,15.466 -9.4238,18.4808 -11.06,21.4306 -12.3986,24.3261 -13.768,27.7467 -15.1344,31.2593 -16.4877,34.8348 -17.8239,38.2438 -19.1276,41.0974 -20.5848,43.9243 -22.1114,46.7776 -23.8283,49.8168 -25.5905,52.8416 -27.3317,55.6947 -29.4116,58.8271 -31.9197,62.2826 -34.1773,65.1871 -35.9155,67.7792 -37.6368,70.7935 -39.4925,73.742 -41.4077,76.586 -43.2676,79.7832 -45.0694,82.8215 -47.0309,85.8318 -49.0401,89.2872 -50.797,92.4035 -52.4994,95.1177 -54.0366,98.1132 -54.6911,99.749 A3.7818,3.7818 0 0 1 -61.9825,97.7381 T-61.2742,95.8818 -59.7648,92.7485 -58.1461,90.2918 -56.5563,87.7794 -55.0816,85.1168 -53.2036,81.9023 -51.0473,78.6152 -49.2883,75.7788 -47.3921,72.6524 -45.1423,69.4062 -43.1294,66.3256 -41.3526,63.4341 -39.543,60.9538 -37.4461,58.2574 -35.1486,54.9444 -33.2162,51.9742 -31.5854,49.3239 -29.877,46.3717 -28.2383,43.4549 -26.7264,40.6617 -25.0445,37.4065 -23.5927,34.2796 -22.5269,31.6828 -21.3681,28.8175 -19.9856,25.2924 -18.4743,21.5627 -16.8662,18.2591 -15.0176,15.0706 -12.8077,11.6899 -10.3438,8.4117 -7.8228,5.2691 -5.2778,1.9007 -3.3131,-0.8587 -2.627,-1.8487 A3.2123,3.2123 0 0 1 2.627,1.8487 ZM-59.694,102.2734 T-59.72,102.2642 -59.7461,102.2549 A3.7832,3.7832 0 0 1 -57.0307,95.1924 T-57.0052,95.203 -56.9796,95.2136 A3.7818,3.7818 0 0 1 -59.694,102.2734 ZM-62.171,98.6529 T-62.1193,96.8263 -62.0675,94.9998 A3.7482,3.7482 0 0 1 -54.5725,95.1402 T-54.5892,96.9673 -54.6058,98.7945 A3.7832,3.7832 0 0 1 -62.171,98.6529 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1014.2695, 472.8555)" opacity="1"><g transform="scale(1)"><path d="M0.2217,-2.9426 T2.6313,-2.6044 7.007,-2.0893 10.5005,-1.8685 14.1978,-1.8867 18.3612,-2.0957 22.8258,-2.3143 26.7492,-2.3842 30.4453,-2.4448 34.1047,-2.517 38.199,-2.5771 42.3517,-2.6239 46.3201,-2.6892 51.2082,-2.8871 55.8613,-3.1133 60.4869,-3.3219 64.3041,-3.378 68.797,-3.216 73.9523,-3.1226 77.6662,-3.1602 81.2891,-3.1977 84.8009,-3.2462 87.6955,-3.3383 90.8571,-3.3895 94.1775,-3.4117 97.1652,-3.4461 100.1346,-3.4556 103.3254,-3.4796 107.0107,-3.5081 110.6145,-3.5425 113.7845,-3.5923 116.8188,-3.6471 119.838,-3.7986 121.4294,-3.9171 A3.7973,3.7973 0 0 1 122.2106,3.6371 T120.2287,3.7286 116.8188,3.7871 113.7845,3.7323 110.6145,3.6825 107.0107,3.648 103.3251,3.6194 100.1335,3.595 97.16,3.5841 94.153,3.5405 90.8344,3.4097 87.6897,3.2564 84.7887,3.1584 81.2467,3.0911 77.564,2.9903 73.8699,2.8534 68.7217,2.8495 63.3926,2.895 59.7069,2.873 56.0729,2.904 51.4412,3.038 46.4323,3.1718 41.0253,3.2635 35.5,3.3761 30.5242,3.4269 26.7973,3.435 22.9801,3.4583 18.5004,3.4732 14.115,3.3667 10.2581,3.1725 6.6672,3.0448 2.2114,2.9692 -0.2217,2.9426 A2.9509,2.9509 0 0 1 0.2217,-2.9426 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1041.5273, 679.7344)" opacity="1"><g transform="scale(1)"><path d="M0.1361,-3.2811 T1.2349,-3.2305 3.8119,-3.1695 7.3583,-3.2206 11.4739,-3.2918 15.4017,-3.3234 18.9625,-3.3623 22.233,-3.3933 25.5619,-3.4105 28.8175,-3.2877 31.7905,-3.0478 34.9102,-2.9002 38.0934,-2.8727 41.0971,-2.8617 44.1205,-2.8468 47.3506,-2.8771 50.6913,-2.9293 54.2563,-2.9669 58.0555,-2.9546 61.9394,-2.906 65.7352,-2.907 69.1684,-2.9711 72.2983,-2.9764 75.3656,-2.9282 78.7281,-2.9414 82.5806,-2.9845 86.0659,-3.015 90.3833,-3.0211 93.2799,-3.013 A3.683,3.683 0 0 1 93.2801,4.353 T91.1527,4.3689 87.524,4.3638 84.4327,4.3622 81.3341,4.3961 78.3738,4.4289 75.4961,4.4103 72.4378,4.2821 69.1684,4.1511 65.7352,4.087 61.9394,4.086 58.0554,4.1345 54.2557,4.1467 50.6871,4.1078 47.3319,4.0472 44.0572,3.9897 40.9134,3.9164 37.8629,3.7778 34.665,3.6092 30.1239,3.4813 25.5647,3.4323 22.2433,3.4194 18.9951,3.4029 15.4918,3.4131 11.571,3.4648 7.3282,3.4977 3.6143,3.4285 0.963,3.3217 -0.1361,3.2811 A3.2839,3.2839 0 0 1 0.1361,-3.2811 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1194.5859, 578.1133)" opacity="1"><g transform="scale(1)"><path d="M2.8252,1.7715 T2.2011,2.7427 0.8563,5.1236 -0.5509,8.213 -1.8945,11.3708 -3.3767,14.2847 -4.9914,17.1322 -6.6287,20.2056 -8.5439,23.9087 -11.0301,28.3082 -13.7988,32.7414 -16.3549,36.7508 -18.4388,40.2495 -20.1407,43.517 -21.6822,46.8146 -23.2313,50.1604 -25.1126,53.815 -27.317,57.6221 -29.419,61.1681 -31.1792,64.1269 -32.7094,66.6632 -34.2584,69.3705 -36.1857,72.448 -38.2991,75.3488 -40.2433,78.0523 -41.8886,80.8274 -43.5414,83.5328 -45.4935,86.3207 -47.4243,88.9314 -49.0846,91.2622 -50.3496,93.9101 -52.0236,97.9355 -53.18,100.4983 A3.7918,3.7918 0 0 1 -59.6,96.4617 T-59.0056,95.3815 -57.9647,92.9448 -56.9691,90.2936 -55.5044,87.5526 -53.4883,84.7933 -51.3834,82.0602 -49.418,79.2658 -47.5558,76.2243 -45.4136,72.897 -43.068,69.905 -40.9748,67.0774 -39.2417,64.3263 -37.664,61.8346 -36.0322,59.2991 -34.1295,56.0677 -31.983,52.3422 -30.1084,48.7876 -28.3739,45.1907 -26.5941,41.675 -24.8676,38.5332 -22.9614,35.3441 -20.5667,31.422 -17.978,27.148 -15.6351,22.883 -13.6589,18.8742 -11.8897,15.3515 -10.1817,12.2752 -8.4778,9.1627 -6.7157,5.4866 -4.9323,1.8725 -3.4278,-0.7865 -2.8252,-1.7715 A3.3346,3.3346 0 0 1 2.8252,1.7715 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1136.9297, 474.8789)" opacity="1"><g transform="scale(1)"><path d="M3.3477,-0.7949 T3.62,0.2712 4.8639,2.6939 6.8071,5.4359 8.7336,8.3137 10.7395,11.3144 12.5644,14.0174 14.1011,16.7497 15.4488,19.7131 16.564,22.7742 17.7116,26.1659 19.144,29.652 20.7213,32.7888 22.5571,35.7228 24.5845,38.4036 26.9662,41.1741 29.4578,44.1125 31.4603,46.6347 33.3263,49.0499 35.3022,51.696 37.3744,54.776 39.0419,57.8917 40.3927,61.0057 41.7315,63.9437 43.1012,66.753 44.8148,70.0126 46.8985,73.4331 48.9746,76.6232 50.8445,79.5393 52.7263,82.2495 54.7461,84.7372 56.6643,87.113 58.441,89.5933 60.0469,92.2043 61.4257,95.2256 62.0967,96.9229 A3.903,3.903 0 0 1 54.9833,100.1371 T54.1106,97.964 52.4564,94.5349 50.8615,92.1006 49.0307,89.6994 47.0767,87.26 45.2302,84.5183 43.4365,81.5901 41.5374,78.6118 39.597,75.6358 37.896,72.8352 36.4607,70.0487 35.09,67.1695 33.6151,63.8937 32.1965,60.5878 30.6337,57.7283 28.6545,54.9105 26.7187,52.3232 24.873,50.029 22.8853,47.6767 20.8913,45.4246 18.9019,43.168 16.6644,40.3007 14.7164,37.2942 13.3395,34.735 12.0463,32.1584 10.8858,29.3711 9.8687,26.2687 8.7913,23.0372 7.5276,20.0547 6.0484,17.3926 4.0644,14.3954 1.795,10.8916 -0.3125,7.6783 -2.0856,4.6609 -3.0957,1.9372 -3.3477,0.7949 A3.4408,3.4408 0 0 1 3.3477,-0.7949 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1050.9102, 532.582)" opacity="1"><g transform="scale(1)"><path d="M2.2324,2.5632 T1.3748,3.291 -0.5635,4.9445 -3.0574,6.8655 -5.6811,8.7179 -8.3421,10.653 -10.9798,12.8632 -12.9651,15.2529 -14.5903,17.977 -16.1137,20.8659 -17.5369,23.9991 -18.8225,27.4199 -19.8118,30.7411 -20.6877,34.1974 -21.3984,37.4759 -21.8842,40.4589 -22.3627,43.8029 -22.7818,47.5038 -23.0312,51.5299 -23.1877,55.422 -23.3806,59.2548 -23.6032,63.8776 -23.7608,68.7057 -23.8326,73.0227 -23.8562,77.317 -23.8387,81.6212 -23.7471,85.7079 -23.6071,89.7765 -23.4812,93.3477 -23.4506,97.0047 -23.479,101.001 -23.5005,104.958 -23.5294,108.748 -23.6289,112.019 -23.7468,115.6579 -23.8389,119.4832 -23.9358,123.1843 -23.9883,127.0073 -23.9955,130.5785 -23.9802,133.933 -23.9095,137.3069 -23.8322,140.5682 -23.783,144.2193 -23.7509,146.3207 A3.6391,3.6391 0 0 1 -31.0291,146.3193 T-31.008,144.665 -30.9177,141.5282 -30.8322,138.5446 -30.8241,135.5032 -30.8237,132.2767 -30.8078,128.8039 -30.799,124.9704 -30.7497,121.0839 -30.6396,117.663 -30.5192,114.6465 -30.3439,111.683 -30.1729,108.5947 -30.1188,104.9285 -30.0912,100.9158 -30.0526,96.9806 -30.0337,93.4733 -30.086,89.995 -30.2069,85.9172 -30.2863,81.6946 -30.3075,77.2975 -30.292,72.9555 -30.2323,68.6066 -30.1915,63.7282 -30.2075,59.5673 -30.1486,56.6134 -30.0208,53.3897 -29.9428,49.0725 -29.7268,44.7334 -29.3286,41.1852 -28.8796,37.7513 -28.1827,33.8619 -27.2566,29.919 -26.3041,26.5393 -25.2499,23.4514 -24.1126,20.6591 -22.7025,17.647 -21.1305,14.7143 -19.4942,12.0446 -17.5457,9.3294 -15.341,7.0608 -13.0344,5.2256 -10.4409,3.4004 -7.7242,1.6489 -5.1857,-0.0924 -3.0711,-1.8136 -2.2324,-2.5632 A3.3991,3.3991 0 0 1 2.2324,2.5632 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1050.9141, 533.7266)" opacity="1"><g transform="scale(1)"><path d="M3.3387,-0.2869 T3.4129,1.2571 3.7714,4.8593 4.4409,8.3352 5.1922,11.3769 5.6317,14.7485 4.7489,17.8151 3.0986,20.3744 1.7856,23.0084 0.3699,26.1908 -1.1468,29.6069 -2.3567,32.7771 -3.1707,35.8662 -3.8086,39.1661 -4.2736,42.4301 -4.4342,45.6746 -4.4018,48.853 -4.3324,51.9583 -4.1597,55.0225 -3.5796,57.7654 -2.2648,60.5681 -0.4224,63.3506 1.6818,65.5753 4.3289,67.5523 7.1234,69.5779 9.6549,71.4627 12.1999,73.1725 15.0495,74.4147 18.1801,75.1769 21.4369,75.7548 24.5849,76.0722 27.469,76.0384 30.4999,75.9013 33.8818,75.7235 37.1213,75.3723 39.9677,74.7297 42.7381,73.6461 45.4971,72.2916 47.7609,70.4691 49.5097,68.0389 51.2811,64.9381 53.0438,61.574 54.2261,58.6536 54.8198,55.8608 55.0683,52.7046 55.1227,49.4794 55.2002,46.3455 55.1605,43.1844 54.738,40.0829 54.0004,36.7281 53.2422,33.6002 52.4049,30.8101 51.1749,28.0074 49.6874,25.2132 48.1288,23.0032 47.2891,22.2149 A3.9631,3.9631 0 0 1 52.5709,16.3051 T53.4527,17.0677 55.2381,19.1149 56.7892,21.696 58.1838,24.3492 59.4826,27.2971 60.4846,30.2239 61.2695,33.2862 61.9598,36.3794 62.5906,39.1663 62.9594,42.1911 63.037,45.2918 62.9688,48.3488 62.8472,51.584 62.6492,54.8199 62.2972,57.7765 61.6154,60.9233 60.3496,64.2515 58.8932,67.2837 57.2491,70.3018 55.4611,73.0978 53.742,75.5146 51.3738,77.7326 48.304,79.5495 45.2875,80.9644 42.2488,82.0943 38.9031,82.838 35.7697,83.2175 32.7451,83.4099 29.5767,83.5635 25.8938,83.7322 22.3226,83.6103 18.9922,83.1348 15.2388,82.4343 11.8234,81.4671 9.0472,80.2039 6.2916,78.501 3.4958,76.4677 1.0083,74.5591 -1.4248,72.8366 -4.0542,70.709 -6.317,68.2731 -8.0377,65.7849 -9.5863,63.2409 -10.8055,60.4664 -11.5142,57.4262 -11.7967,54.4641 -11.892,51.2471 -11.9299,47.9804 -11.929,44.9127 -11.7042,41.5396 -11.1965,37.8541 -10.5014,34.3174 -9.6698,31.251 -8.4201,27.8838 -6.9566,24.4159 -5.5205,21.375 -3.8371,18.158 -2.0187,15.3949 -1.3293,12.8102 -1.916,9.6939 -2.6241,5.7105 -3.1484,1.8209 -3.3387,0.2869 A3.351,3.351 0 0 1 3.3387,-0.2869 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1040.6133, 626.0195)" opacity="1"><g transform="scale(1)"><path d="M1.5462,-2.8829 T2.8362,-2.2218 5.7683,-0.8131 8.7271,0.5218 12.1828,1.6354 16.541,2.4759 20.4031,2.9676 23.928,3.2533 27.5154,3.3467 30.7804,3.2623 33.9694,3.0942 37.2772,2.9896 40.8546,2.9254 44.3635,2.7345 47.4032,2.4046 50.4965,1.666 53.6136,0.5159 56.4772,-0.9278 60.468,-2.7622 64.7849,-4.4292 67.8118,-5.7066 70.4908,-7.448 72.9945,-9.9572 75.43,-13.0339 77.582,-16.0012 79.629,-18.8493 81.7054,-21.7464 82.931,-24.9413 83.6356,-28.4818 84.0887,-31.8078 84.2622,-35.3498 84.3609,-38.983 84.4191,-42.6661 84.3542,-46.2915 83.9841,-49.7848 83.4134,-53.0636 82.8765,-56.0358 82.3148,-59.1596 81.6714,-62.1998 80.7489,-65.1124 79.4514,-68.1716 78.146,-71.0435 76.8537,-73.9061 75.1028,-77.0269 73.0902,-79.8697 70.379,-82.112 67.0762,-83.7416 63.5447,-85.1337 61.5615,-85.873 A3.8161,3.8161 0 0 1 64.1304,-93.0599 T65.5951,-92.5108 68.5195,-91.2878 71.6258,-89.9248 75.1882,-88.0567 77.9989,-85.7917 79.8451,-83.3697 81.7821,-80.4798 83.3695,-77.5533 84.5567,-74.882 85.6756,-72.2459 86.9386,-69.3606 88.099,-66.2716 88.7749,-63.3051 89.2682,-60.3298 89.7587,-57.2711 90.3716,-53.65 90.9202,-50.0077 91.196,-46.5309 91.297,-42.6127 91.2662,-38.8406 91.1943,-35.1085 91.029,-31.0916 90.5555,-27.2168 89.8759,-23.7509 88.7803,-20.1836 87.0672,-17.0739 85.1399,-14.6868 83.2273,-12.1313 80.9104,-8.8365 78.3382,-5.4206 75.9299,-2.7563 73.3647,-0.8257 70.6061,0.5826 67.2569,1.9929 63.4598,3.5625 59.9859,5.3461 56.9233,6.9809 53.8389,8.1497 50.4908,9.0179 46.8989,9.5188 43.0701,9.8094 39.0686,10.018 35.4702,10.0955 32.4279,10.1519 29.3946,10.2372 25.9383,10.1699 22.6211,9.9209 19.6207,9.5579 15.478,9.0517 10.4647,8.1953 6.3427,6.8288 2.8606,5.1745 -0.2602,3.6034 -1.5462,2.8829 A3.2713,3.2713 0 0 1 1.5462,-2.8829 ZM66.3802,-88.0269 T65.9584,-86.9877 65.1689,-84.2641 64.5364,-80.9605 64.2668,-77.2801 64.2621,-75.2187 A3.79,3.79 0 0 1 56.7179,-74.4813 T56.6948,-76.561 56.8928,-80.2884 57.354,-83.6022 57.9689,-86.9023 58.8277,-89.7211 59.3117,-90.9059 A3.8161,3.8161 0 0 1 66.3802,-88.0269 ZM56.7758,-75.6044 T56.9684,-76.5649 57.1609,-77.5254 A3.795,3.795 0 0 1 64.5991,-76.0146 T64.4016,-75.0551 64.2042,-74.0956 A3.79,3.79 0 0 1 56.7758,-75.6044 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1042.4492, 625.7773)" opacity="1"><g transform="scale(1)"><path d="M2.7377,1.2576 T2.4282,1.9946 1.8821,4.3955 1.5057,7.4809 1.239,10.4136 1.0128,13.4771 0.8289,16.7309 0.7224,20.2065 0.6768,24.0272 0.6523,27.997 0.6372,32.5942 0.6201,37.9202 0.6672,42.7098 0.8098,46.304 1.1712,49.9875 1.4397,52.214 A3.2608,3.2608 0 0 1 -5.0797,52.386 T-5.1306,50.068 -5.0765,46.2816 -4.8913,42.6562 -4.7115,37.7565 -4.4389,32.5142 -4.3401,28.1216 -4.564,24.1522 -4.7999,20.1935 -4.8959,16.5442 -4.8678,13.1634 -4.7629,9.9909 -4.6054,6.8837 -3.987,2.7612 -3.0985,-0.5354 -2.7377,-1.2576 A3.0127,3.0127 0 0 1 2.7377,1.2576 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1073.8008, 519.7578)" opacity="1"><g transform="scale(1)"><path d="M2.998,1.2115 T2.6447,2.1057 1.7791,5.0381 1.0388,9.2582 0.6261,13.5286 0.2111,17.5608 -0.1535,21.191 -0.3495,24.8576 -0.4391,28.7508 -0.5267,32.9692 -0.5894,37.1481 -0.6166,40.5986 -0.5631,43.7118 -0.2146,47.1719 0.4768,50.4393 2.3012,52.5768 3.7931,51.6416 3.9217,48.4654 4.0743,45.5449 4.3716,41.9444 4.6312,38.3228 4.711,35.0419 4.684,31.2558 4.5699,27.6274 4.1917,24.2166 3.5644,20.6232 2.9966,16.9173 2.6161,13.3375 2.3629,10.1465 1.8791,7.0825 0.7109,3.9727 -0.0829,2.421 A3.8101,3.8101 0 0 1 6.6429,-1.161 T7.6022,0.7554 8.9347,4.0197 9.5887,7.1038 9.9233,10.4399 10.141,13.6497 10.5633,16.9975 11.1688,20.5734 11.7082,23.7971 12.0116,27.1797 12.181,31.1963 12.2199,35.1666 12.1175,38.7088 11.9424,41.8961 11.7193,45.0517 11.5219,48.0048 11.3102,51.1088 10.5739,54.6463 9.2506,57.7126 6.791,59.8367 3.1926,60.6114 -0.5459,59.7781 -3.5159,57.7948 -5.4147,54.9985 -6.5906,51.6676 -7.1437,48.3069 -7.3614,45.2747 -7.3315,42.199 -7.1987,38.9848 -7.1374,35.0806 -7.0956,30.7063 -7.0245,26.5596 -6.8675,22.4743 -6.5291,18.6656 -6.0932,14.9266 -5.6818,10.6776 -5.163,6.0383 -4.2829,2.135 -3.3651,-0.3227 -2.998,-1.2115 A3.2335,3.2335 0 0 1 2.998,1.2115 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1015.9727, 678.9063)" opacity="1"><g transform="scale(1)"><path d="M-0.1029,-3.255 T1.0407,-3.3253 3.6019,-3.4866 5.0196,-3.5776 A3.3714,3.3714 0 0 1 5.3404,3.1576 T3.871,3.2041 1.2523,3.2528 0.1029,3.255 A3.2566,3.2566 0 0 1 -0.1029,-3.255 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g><g transform="matrix(1, 0, 0, 1, 1039.9883, 616.3008)" opacity="1"><g transform="scale(1)"><path d="M3.3123,0.1447 T3.2214,1.5 3.0952,4.6893 3.131,8.2308 3.3001,11.3452 3.6974,14.1742 3.9966,15.596 A3.453,3.453 0 0 1 -2.7766,16.944 T-3.1246,15.1139 -3.5066,11.6376 -3.5185,8.1863 -3.4319,4.4763 -3.3399,1.2132 -3.3123,-0.1447 A3.3155,3.3155 0 0 1 3.3123,0.1447 Z" stroke-linecap="round" fill="#f1ac4b"/></g></g></svg>
</div>
<div id="hud_info">
<p> vertices: <span id="stat_vertices"> 0 </span></p>
<p> triangles: <span id="stat_triangles"> 0 </span></p>
<p>
total size: <span id="stat_total_size"> 0 </span>
<br/> <span style="font-size:0.5rem;"> (assuming 2 floats + color per vertex and uint16_t indices)</span>
</p>
<hr style="width:100%"/>
<div style="display:flex;align-items:center;justify-content:space-between;">
<label for="drawing_name"> export name: </label>
<input id="drawing_name" value="svg"> </input>
</div>
<button id="copy_file">📋 copy .h</button>
</div>
</div>
<script type="module">"use strict";
import earcut from "earcut";
document.addEventListener('dragover', e => e.preventDefault());
document.addEventListener('drop', async (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
const div = document.createElement('div');
div.innerHTML = await file.text();
document.querySelector("svg").remove();
drawing_area.querySelector("canvas").remove();
drawing_name.value = file.name.split(/[^a-zA-Z0-9_]/)[0];
drawing_area.append(div.children[0]);
console.clear();
render();
});
function render() {
const [svg_x, svg_y, svg_w, svg_h] = document
.querySelector("svg")
.getAttribute('viewBox')
.split(' ')
.map(parseFloat);
let canvas_extracted;
let paths;
{
const canvas = canvas_extracted = document.createElement("canvas");
const ctx = canvas.getContext('2d');
canvas.width = svg_w*window.devicePixelRatio,
canvas.height = svg_h*window.devicePixelRatio
canvas.style.width = svg_w + 'px';
canvas.style.height = svg_h + 'px';
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
{
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.translate(-svg_x, -svg_y);
function extract_paths(svg, parent_transform, paths) {
let m = parent_transform;
if (svg.transform.baseVal[0]) {
const s = svg.transform.baseVal[0].matrix;
m = m.multiply(new DOMMatrix([s.a, s.b, s.c, s.d, s.e, s.f]));
}
if (svg.nodeName == 'path') {
ctx.lineCap = 'round';
ctx.beginPath();
const m_inverse = m.inverse();
let px, py; /* last position in SVG */
let cmd; /* last command */
const words = svg.getAttribute('d').split(/, | |,/).reverse();
let path_fill = [...svg.getAttribute('fill').slice(1).matchAll(/../g)]
.map(x => parseInt(x[0], 16));
let path;
while (words.length) {
let word = words.pop();
/* update command if present */
if (word[0].toLowerCase() != word[0].toUpperCase()) {
cmd = word[0];
if (word.length > 1) words.push(word.slice(1));
if (word.length == 1) continue;
} else {
words.push(word);
}
let valcount;
if (cmd == 'T') valcount = 2;
else if (cmd == 'M') valcount = 2;
else if (cmd == 'm') valcount = 2;
else if (cmd == 'A') valcount = 7;
else if (cmd == 'a') valcount = 7;
else if (cmd == 'Z') valcount = 0;
else throw new Error("unknown SVG command " + cmd);
if (valcount == 0) continue;
const values = Array.from({ length: valcount }, _ => parseFloat(words.pop()));
for (const v of values) if (isNaN(v)) throw new Error("value is NaN");
let p = m.transformPoint(new DOMPoint(values[0], values[1]));
if (cmd == 'M' || cmd == 'm') {
if (cmd == 'a') {
const q = m_inverse.transformPoint(new DOMPoint(px, py));
p = m.transformPoint(new DOMPoint(q.x + values[0], q.y + values[1]));
}
if (path) paths.push({ path, fill: path_fill });
path = [];
path.push({ x: p.x, y: p.y });
ctx.moveTo(p.x, p.y);
px = p.x, py = p.y;
}
if (cmd == 'T') {
path.push({ x: p.x, y: p.y });
ctx.lineTo(p.x, p.y);
px = p.x, py = p.y;
}
if (cmd == 'A' || cmd == 'a') {
const start_x = px;
const start_y = py;
let end = m.transformPoint(new DOMPoint(values.at(-2), values.at(-1)));
if (cmd == 'a') {
const q = m_inverse.transformPoint(new DOMPoint(px, py));
end = m.transformPoint(new DOMPoint(q.x + values.at(-2), q.y + values.at(-1)));
}
const center_x = lerp(start_x, end.x, 0.5);
const center_y = lerp(start_y, end.y, 0.5);
const start_angle = Math.atan2(start_y - center_y, start_x - center_x);
const end_angle = Math.atan2( end.y - center_y, end.x - center_x);
// ctx.arc(center_x, center_y, values[0], start_angle, end_angle);
let dist = rads_distance(start_angle, end_angle);
if (dist < 0) dist = Math.PI*2 + dist;
const verts_needed = Math.floor(dist / 0.6);
for (let i = 1; i <= verts_needed; i++) {
const t = lerp(start_angle, start_angle + dist, i / verts_needed);
const c_x = center_x + values[0]*Math.cos(t);
const c_y = center_y + values[0]*Math.sin(t);
ctx.lineTo(c_x, c_y);
path.push({ x: c_x, y: c_y });
}
px = end.x, py = end.y;
}
}
ctx.fillStyle = svg.getAttribute('fill');
ctx.fill();
paths.push({ path, fill: path_fill });
}
for (const child of svg.children) extract_paths(child, m, paths);
return paths;
}
paths = extract_paths(document.querySelector("svg"), new DOMMatrix(), []);
}
ctx.restore();
}
let meshes;
let bbox_size_x, bbox_size_y;
{
const start = performance.now();
meshes = paths.map(p => ({ fill: p.fill, vertices: p.path, indices: earcut(p.path.flatMap(x => [x.x, x.y])) }));
/* my simple earclipping routine produces ~decent results but takes 180x longer than earcut */
// meshes = paths.map(p.path => ({ fill: p.fill, vertices: p.path, indices: earclip(p.path.slice()) }));
const duration = performance.now() - start;
const vert_count = meshes.reduce((a, x) => a + x.vertices.length, 0);
const tri_count = meshes.reduce((a, x) => a + x.indices.length / 3, 0)
stat_vertices.textContent = vert_count;
stat_triangles.textContent = tri_count;
const n_bytes = vert_count * (4 * 3) + tri_count * (3 * 2);
let size_readout = `${n_bytes} bytes`;
if (n_bytes > (1 << 10)) size_readout = `${n_bytes >> 10} KiB`;
if (n_bytes > (1 << 20)) size_readout = `${n_bytes >> 20} MiB`;
stat_total_size.textContent = size_readout;
console.log(`earclipped ${vert_count} vertices in ${duration.toFixed(2)}ms`);
/* investigate low hanging fruit for optimization */
{
let unused_indices = 0;
let empty_tris = 0;
for (const mesh of meshes) {
const all_indices = new Set(Array.from({ length: mesh.vertices.length }, (_, i) => i));
const used_indices = new Set(mesh.indices);
for (let i = 0; i < mesh.indices.length; i += 3) {
const a = mesh.vertices[mesh.indices[i + 0]];
const b = mesh.vertices[mesh.indices[i + 1]];
const c = mesh.vertices[mesh.indices[i + 2]];
const d1x = a.x - b.x;
const d1y = a.y - b.y;
const d2x = b.x - c.x;
const d2y = b.y - c.y;
const determinant = (d1x*d2y - d1y*d2x);
empty_tris += Math.abs(determinant) < 0.1;
}
for (const i of all_indices) {
unused_indices += !used_indices.has(i);
}
}
console.log(`${unused_indices} unused vertices`);
console.log(`${empty_tris} empty/very small triangles (${(empty_tris / vert_count * 100).toFixed(2)}%)`);
/* TODO: make a map of <Vertex, sum(triangles it is a part of)>,
* remove vertices < 0.1 */
}
/* make bounding box and normalize vertices */
{
let og_min_x = Infinity;
let og_min_y = Infinity;
let og_max_x = -Infinity;
let og_max_y = -Infinity;
for (const mesh of meshes) {
for (const v of mesh.vertices) {
og_min_x = Math.min(v.x, og_min_x);
og_min_y = Math.min(v.y, og_min_y);
og_max_x = Math.max(v.x, og_max_x);
og_max_y = Math.max(v.y, og_max_y);
}
}
let size_x = og_max_x - og_min_x;
let size_y = og_max_y - og_min_y;
const major_axis = Math.max(size_x, size_y);
let max_x = og_min_x + major_axis + (size_x - major_axis) * 0.5;
let max_y = og_min_y + major_axis + (size_y - major_axis) * 0.5;
let min_x = og_min_x + (size_x - major_axis) * 0.5;
let min_y = og_min_y + (size_y - major_axis) * 0.5;
size_x = size_y = major_axis;
bbox_size_x = (og_max_x - og_min_x) / major_axis;
bbox_size_y = (og_max_y - og_min_y) / major_axis;
for (const mesh of meshes) {
for (const v of mesh.vertices) {
v.x = inv_lerp(min_x, max_x, v.x);
v.y = inv_lerp(min_y, max_y, v.y);
}
}
}
}
let canvas_tesselated;
{
const canvas = canvas_tesselated = document.createElement("canvas");
const ctx = canvas.getContext('2d');
const major_axis = Math.max(svg_w, svg_h);
canvas.width = major_axis*window.devicePixelRatio,
canvas.height = major_axis*window.devicePixelRatio
canvas.style.width = major_axis + 'px';
canvas.style.height = major_axis + 'px';
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
{
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
const pad = 30;
ctx.translate(pad, pad);
ctx.scale(major_axis - pad*2, major_axis - pad*2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1.0 / major_axis;
ctx.strokeRect(
(1 - bbox_size_x) * 0.5,
(1 - bbox_size_y) * 0.5,
bbox_size_x, bbox_size_y
);
{
const px = 1 / major_axis;
ctx.fillStyle = 'black';
ctx.font = (16.0 / major_axis) + 'px monospace';
ctx.textBaseline = 'bottom';
ctx.fillText('0,0', -ctx.measureText('0,0').width, 0);
ctx.fillRect(0 - px, 0 - px, 16*px, 2*px);
ctx.fillRect(0 - px, 0 - px, 2*px, 16*px);
ctx.fillStyle = 'black';
ctx.font = (16.0 / major_axis) + 'px monospace';
ctx.textBaseline = 'top';
ctx.fillText('1,1', 1, 1 + 4 / major_axis);
ctx.fillRect(1 + px, 1 - px, -16*px, 2*px);
ctx.fillRect(1 - px, 1 + px, 2*px, -16*px);
}
const radius = 1.0 / major_axis;
const lineWidth = 0.2 / major_axis;
for (const m of meshes) {
for (const v of m.vertices) {
ctx.beginPath();
ctx.arc(v.x, v.y, radius, 0, Math.PI*2);
ctx.fillStyle = 'black';
ctx.fill();
}
for (let i = 0; i < m.indices.length; i += 3) {
ctx.beginPath();
const a = m.vertices[m.indices[i + 0]];
const b = m.vertices[m.indices[i + 1]];
const c = m.vertices[m.indices[i + 2]];
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.lineTo(c.x, c.y);
ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
ctx.lineWidth = lineWidth;
ctx.fill();
ctx.stroke();
}
}
}
ctx.restore();
}
drawing_select.value = 'canvas_extracted'
drawing_area.querySelector("svg").style.display = 'none';
drawing_area.append(canvas_extracted);
drawing_select.oninput = ev => {
/* remove any canvas that may be present */
if (drawing_area.querySelector("canvas"))
drawing_area.querySelector("canvas").remove();
let show_svg = false;
let show_canvas = null;
if (drawing_select.value == "svg") show_svg = true;
if (drawing_select.value == "canvas_extracted") show_canvas = canvas_extracted;
if (drawing_select.value == "canvas_tesselated") show_canvas = canvas_tesselated;
drawing_area.querySelector("svg").style.display = show_svg ? 'block' : 'none';
if (show_canvas) drawing_area.append(show_canvas);
};
const render_code = () => {
let out = '';
out += `float model_${drawing_name.value}_size_x = ${bbox_size_x.toFixed(6)}f;\n`;
out += `float model_${drawing_name.value}_size_y = ${bbox_size_y.toFixed(6)}f;\n\n`;
out += `gl_geo_Vtx model_vtx_${drawing_name.value}[] = {\n`;
for (const m of meshes) {
for (const v of m.vertices) {
const x = v.x.toFixed(8).padStart(9);
const y = v.y.toFixed(8).padStart(9);
const srgb2linear = srgb => Math.round(Math.pow(srgb/255, 2.2) * 255);
const rgb = m.fill.map(x => (srgb2linear(x) + '').padStart(3)).join(', ');
out += ` { { ${x}, ${y} }, { ${rgb}, 255 } },\n`;
}
}
out += `};\n`;
out += `gl_Tri model_tri_${drawing_name.value}[] = {\n`;
let offset = 0;
for (const m of meshes) {
for (let i = 0; i < m.indices.length; i += 3) {
const a = offset + m.indices[i + 0];
const b = offset + m.indices[i + 1];
const c = offset + m.indices[i + 2];
out += ` { ${a}, ${b}, ${c} },\n`;
}
offset += m.vertices.length;
}
out += `};\n`;
code_out.textContent = out;
}
render_code();
drawing_name.oninput = () => render_code();
copy_file.onclick = () => {
copy_file.textContent = '✅';
setTimeout(() => copy_file.textContent = '📋 copy .h', 500);
navigator.clipboard.writeText(code_out.textContent);
}
}
render();
function earclip(vertices) {
const original_index = new Map(vertices.map((x, i) => [x, i]));
/* reverse shape if necessary */
{
let sum = 0;
/* signed area! */
for (let i = 0, j = vertices.length - 1; i < vertices.length; i++) {
sum += (vertices[j].x - vertices[i].x) * (vertices[i].y + vertices[j].y);
j = i;
}
if (sum < 0)
vertices.reverse();
}
function is_ear(a, b, c) {
/* it's an ear if the determinant is negative (convex) and there are no points inside of it */
/* convex check */
{
const d1x = vertices[a].x - vertices[b].x;
const d1y = vertices[a].y - vertices[b].y;
const d2x = vertices[b].x - vertices[c].x;
const d2y = vertices[b].y - vertices[c].y;
if ((d1x*d2y - d1y*d2x) < 0) {
return false;
}
}
/* make sure the triangle is empty inside */
let hit = false;
/* make sure no points from other triangles are inside this triangle */
for (let j = 0; j < vertices.length; j++) {
if (j == a || j == b || j == c) continue;
const p = vertices[j], v1 = vertices[a], v2 = vertices[b], v3 = vertices[c];
const alpha = ((v2.y - v3.y) * ( p.x - v3.x) + (v3.x - v2.x) * ( p.y - v3.y)) /
((v2.y - v3.y) * (v1.x - v3.x) + (v3.x - v2.x) * (v1.y - v3.y));
const beta = ((v3.y - v1.y) * ( p.x - v3.x) + (v1.x - v3.x) * ( p.y - v3.y)) /
((v2.y - v3.y) * (v1.x - v3.x) + (v3.x - v2.x) * (v1.y - v3.y));
const gamma = 1.0 - alpha - beta;
const contained = (alpha > 0 && beta > 0 && gamma > 0)
if (contained) {
hit = true;
break;
}
}
return !hit;
}
let indices = [];
let escape_hatch = 0;
while (vertices.length >= 3) {
for (let i = 0; i < vertices.length; i++) {
const a = (i + 0) % vertices.length;
const b = (i + 1) % vertices.length;
const c = (i + 2) % vertices.length;
if (is_ear(a, b, c)) {
indices.push(original_index.get(vertices[a]),
original_index.get(vertices[b]),
original_index.get(vertices[c]));
vertices.splice(b, 1);
break;
}
}
if (escape_hatch++ > 1e6) {
console.warn("failed to tesselate in 1 million iterations");
break;
}
}
return indices;
}
function lerp(v0, v1, t) { return (1 - t) * v0 + t * v1; }
function inv_lerp(min, max, p) { return (p - min) / (max - min); }
function rads_distance(a, b) {
const fmodf = (l, r) => l % r;
let difference = fmodf(b - a, Math.PI*2.0),
distance = fmodf(2.0 * difference, Math.PI*2.0) - difference;
return distance;
}
</script>
</body>
</html>