shape-morph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1758 @@
1
+ 'use strict';
2
+
3
+ // src/core/utils.ts
4
+ var distanceEpsilon = 1e-4;
5
+ var angleEpsilon = 1e-6;
6
+ var relaxedDistanceEpsilon = 5e-3;
7
+ var floatPi = Math.PI;
8
+ var twoPi = 2 * Math.PI;
9
+ function pt(x, y) {
10
+ return { x, y };
11
+ }
12
+ function ptDistance(p) {
13
+ return Math.sqrt(p.x * p.x + p.y * p.y);
14
+ }
15
+ function ptDirection(p) {
16
+ const d = ptDistance(p);
17
+ return { x: p.x / d, y: p.y / d };
18
+ }
19
+ function ptDot(a, b) {
20
+ return a.x * b.x + a.y * b.y;
21
+ }
22
+ function ptClockwise(a, b) {
23
+ return a.x * b.y - a.y * b.x > 0;
24
+ }
25
+ function ptRotate90(p) {
26
+ return { x: -p.y, y: p.x };
27
+ }
28
+ function ptSub(a, b) {
29
+ return { x: a.x - b.x, y: a.y - b.y };
30
+ }
31
+ function ptAdd(a, b) {
32
+ return { x: a.x + b.x, y: a.y + b.y };
33
+ }
34
+ function ptMul(p, s) {
35
+ return { x: p.x * s, y: p.y * s };
36
+ }
37
+ function ptDiv(p, s) {
38
+ return { x: p.x / s, y: p.y / s };
39
+ }
40
+ function distance(x, y) {
41
+ return Math.sqrt(x * x + y * y);
42
+ }
43
+ function distanceSquared(x, y) {
44
+ return x * x + y * y;
45
+ }
46
+ function directionVector(x, y) {
47
+ const d = distance(x, y);
48
+ return { x: x / d, y: y / d };
49
+ }
50
+ function directionVectorAngle(angleRadians) {
51
+ return { x: Math.cos(angleRadians), y: Math.sin(angleRadians) };
52
+ }
53
+ function radialToCartesian(radius, angleRadians, center = { x: 0, y: 0 }) {
54
+ const d = directionVectorAngle(angleRadians);
55
+ return { x: d.x * radius + center.x, y: d.y * radius + center.y };
56
+ }
57
+ function interpolate(start, stop, fraction) {
58
+ return (1 - fraction) * start + fraction * stop;
59
+ }
60
+ function interpolatePoint(start, stop, fraction) {
61
+ return {
62
+ x: interpolate(start.x, stop.x, fraction),
63
+ y: interpolate(start.y, stop.y, fraction)
64
+ };
65
+ }
66
+ function positiveModulo(num, mod) {
67
+ return (num % mod + mod) % mod;
68
+ }
69
+ function convex(previous, current, next) {
70
+ return ptClockwise(ptSub(current, previous), ptSub(next, current));
71
+ }
72
+ function square(x) {
73
+ return x * x;
74
+ }
75
+
76
+ // src/core/cubic.ts
77
+ function axisExtremaTs(a, b, c) {
78
+ const zeroIsh = Math.abs(a) < distanceEpsilon;
79
+ if (zeroIsh) {
80
+ if (b !== 0) {
81
+ const t = 2 * c / (-2 * b);
82
+ if (t >= 0 && t <= 1) {
83
+ return [t];
84
+ }
85
+ }
86
+ return [];
87
+ }
88
+ const discriminant = b * b - 4 * a * c;
89
+ if (discriminant < 0) {
90
+ return [];
91
+ }
92
+ const sqrtD = Math.sqrt(discriminant);
93
+ const results = [];
94
+ for (const t of [(-b + sqrtD) / (2 * a), (-b - sqrtD) / (2 * a)]) {
95
+ if (t >= 0 && t <= 1) {
96
+ results.push(t);
97
+ }
98
+ }
99
+ return results;
100
+ }
101
+ var Cubic = class _Cubic {
102
+ points;
103
+ constructor(a, b, c, d, e, f, g, h) {
104
+ if (a instanceof Float64Array) {
105
+ this.points = a;
106
+ } else {
107
+ this.points = new Float64Array([
108
+ a,
109
+ b,
110
+ c,
111
+ d,
112
+ e,
113
+ f,
114
+ g,
115
+ h
116
+ ]);
117
+ }
118
+ }
119
+ get anchor0X() {
120
+ return this.points[0];
121
+ }
122
+ get anchor0Y() {
123
+ return this.points[1];
124
+ }
125
+ get control0X() {
126
+ return this.points[2];
127
+ }
128
+ get control0Y() {
129
+ return this.points[3];
130
+ }
131
+ get control1X() {
132
+ return this.points[4];
133
+ }
134
+ get control1Y() {
135
+ return this.points[5];
136
+ }
137
+ get anchor1X() {
138
+ return this.points[6];
139
+ }
140
+ get anchor1Y() {
141
+ return this.points[7];
142
+ }
143
+ pointOnCurve(t) {
144
+ const u = 1 - t;
145
+ return pt(
146
+ this.anchor0X * (u * u * u) + this.control0X * (3 * t * u * u) + this.control1X * (3 * t * t * u) + this.anchor1X * (t * t * t),
147
+ this.anchor0Y * (u * u * u) + this.control0Y * (3 * t * u * u) + this.control1Y * (3 * t * t * u) + this.anchor1Y * (t * t * t)
148
+ );
149
+ }
150
+ zeroLength() {
151
+ return Math.abs(this.anchor0X - this.anchor1X) < distanceEpsilon && Math.abs(this.anchor0Y - this.anchor1Y) < distanceEpsilon;
152
+ }
153
+ convexTo(next) {
154
+ const prevVertex = pt(this.anchor0X, this.anchor0Y);
155
+ const currVertex = pt(this.anchor1X, this.anchor1Y);
156
+ const nextVertex = pt(next.anchor1X, next.anchor1Y);
157
+ return convex(prevVertex, currVertex, nextVertex);
158
+ }
159
+ split(t) {
160
+ const u = 1 - t;
161
+ const poc = this.pointOnCurve(t);
162
+ return [
163
+ new _Cubic(
164
+ this.anchor0X,
165
+ this.anchor0Y,
166
+ this.anchor0X * u + this.control0X * t,
167
+ this.anchor0Y * u + this.control0Y * t,
168
+ this.anchor0X * (u * u) + this.control0X * (2 * u * t) + this.control1X * (t * t),
169
+ this.anchor0Y * (u * u) + this.control0Y * (2 * u * t) + this.control1Y * (t * t),
170
+ poc.x,
171
+ poc.y
172
+ ),
173
+ new _Cubic(
174
+ poc.x,
175
+ poc.y,
176
+ this.control0X * (u * u) + this.control1X * (2 * u * t) + this.anchor1X * (t * t),
177
+ this.control0Y * (u * u) + this.control1Y * (2 * u * t) + this.anchor1Y * (t * t),
178
+ this.control1X * u + this.anchor1X * t,
179
+ this.control1Y * u + this.anchor1Y * t,
180
+ this.anchor1X,
181
+ this.anchor1Y
182
+ )
183
+ ];
184
+ }
185
+ reverse() {
186
+ return new _Cubic(
187
+ this.anchor1X,
188
+ this.anchor1Y,
189
+ this.control1X,
190
+ this.control1Y,
191
+ this.control0X,
192
+ this.control0Y,
193
+ this.anchor0X,
194
+ this.anchor0Y
195
+ );
196
+ }
197
+ transformed(f) {
198
+ const newPoints = new Float64Array(8);
199
+ for (let i = 0; i < 8; i += 2) {
200
+ const r = f(this.points[i], this.points[i + 1]);
201
+ newPoints[i] = r.x;
202
+ newPoints[i + 1] = r.y;
203
+ }
204
+ return new _Cubic(newPoints);
205
+ }
206
+ calculateBounds(approximate = true) {
207
+ if (this.zeroLength()) {
208
+ return [this.anchor0X, this.anchor0Y, this.anchor0X, this.anchor0Y];
209
+ }
210
+ let minX = Math.min(this.anchor0X, this.anchor1X);
211
+ let minY = Math.min(this.anchor0Y, this.anchor1Y);
212
+ let maxX = Math.max(this.anchor0X, this.anchor1X);
213
+ let maxY = Math.max(this.anchor0Y, this.anchor1Y);
214
+ if (approximate) {
215
+ return [
216
+ Math.min(minX, Math.min(this.control0X, this.control1X)),
217
+ Math.min(minY, Math.min(this.control0Y, this.control1Y)),
218
+ Math.max(maxX, Math.max(this.control0X, this.control1X)),
219
+ Math.max(maxY, Math.max(this.control0Y, this.control1Y))
220
+ ];
221
+ }
222
+ const xCoeffs = [
223
+ -this.anchor0X + 3 * this.control0X - 3 * this.control1X + this.anchor1X,
224
+ 2 * this.anchor0X - 4 * this.control0X + 2 * this.control1X,
225
+ -this.anchor0X + this.control0X
226
+ ];
227
+ for (const t of axisExtremaTs(...xCoeffs)) {
228
+ const v = this.pointOnCurve(t).x;
229
+ minX = Math.min(minX, v);
230
+ maxX = Math.max(maxX, v);
231
+ }
232
+ const yCoeffs = [
233
+ -this.anchor0Y + 3 * this.control0Y - 3 * this.control1Y + this.anchor1Y,
234
+ 2 * this.anchor0Y - 4 * this.control0Y + 2 * this.control1Y,
235
+ -this.anchor0Y + this.control0Y
236
+ ];
237
+ for (const t of axisExtremaTs(...yCoeffs)) {
238
+ const v = this.pointOnCurve(t).y;
239
+ minY = Math.min(minY, v);
240
+ maxY = Math.max(maxY, v);
241
+ }
242
+ return [minX, minY, maxX, maxY];
243
+ }
244
+ static straightLine(x0, y0, x1, y1) {
245
+ return new _Cubic(
246
+ x0,
247
+ y0,
248
+ interpolate(x0, x1, 1 / 3),
249
+ interpolate(y0, y1, 1 / 3),
250
+ interpolate(x0, x1, 2 / 3),
251
+ interpolate(y0, y1, 2 / 3),
252
+ x1,
253
+ y1
254
+ );
255
+ }
256
+ static circularArc(centerX, centerY, x0, y0, x1, y1) {
257
+ const p0d = directionVector(x0 - centerX, y0 - centerY);
258
+ const p1d = directionVector(x1 - centerX, y1 - centerY);
259
+ const rotatedP0 = ptRotate90(p0d);
260
+ const rotatedP1 = ptRotate90(p1d);
261
+ const clockwise = rotatedP0.x * (x1 - centerX) + rotatedP0.y * (y1 - centerY) >= 0;
262
+ const cosa = p0d.x * p1d.x + p0d.y * p1d.y;
263
+ if (cosa > 0.999) {
264
+ return _Cubic.straightLine(x0, y0, x1, y1);
265
+ }
266
+ const k = distance(x0 - centerX, y0 - centerY) * 4 * (Math.sqrt(2 * (1 - cosa)) - Math.sqrt(1 - cosa * cosa)) / (3 * (1 - cosa)) * (clockwise ? 1 : -1);
267
+ return new _Cubic(
268
+ x0,
269
+ y0,
270
+ x0 + rotatedP0.x * k,
271
+ y0 + rotatedP0.y * k,
272
+ x1 - rotatedP1.x * k,
273
+ y1 - rotatedP1.y * k,
274
+ x1,
275
+ y1
276
+ );
277
+ }
278
+ static empty(x0, y0) {
279
+ return new _Cubic(x0, y0, x0, y0, x0, y0, x0, y0);
280
+ }
281
+ };
282
+ function featureTransformed(f, transform) {
283
+ const newCubics = f.cubics.map((c) => c.transformed(transform));
284
+ if (f.type === "edge") {
285
+ return { type: "edge", cubics: newCubics };
286
+ }
287
+ return { type: "corner", cubics: newCubics, convex: f.convex };
288
+ }
289
+
290
+ // src/core/polygon.ts
291
+ var unrounded = { radius: 0, smoothing: 0 };
292
+ function cornerRounding(radius, smoothing = 0) {
293
+ return { radius, smoothing };
294
+ }
295
+ var RoundedPolygon = class _RoundedPolygon {
296
+ features;
297
+ center;
298
+ cubics;
299
+ constructor(features, center) {
300
+ this.features = features;
301
+ this.center = center;
302
+ this.cubics = buildCubicList(features, center);
303
+ }
304
+ get centerX() {
305
+ return this.center.x;
306
+ }
307
+ get centerY() {
308
+ return this.center.y;
309
+ }
310
+ transformed(f) {
311
+ const newCenter = f(this.center.x, this.center.y);
312
+ const newFeatures = this.features.map(
313
+ (feat) => featureTransformed(feat, f)
314
+ );
315
+ return new _RoundedPolygon(newFeatures, newCenter);
316
+ }
317
+ normalized() {
318
+ const bounds = this.calculateBounds();
319
+ const width = bounds[2] - bounds[0];
320
+ const height = bounds[3] - bounds[1];
321
+ const side = Math.max(width, height);
322
+ const offsetX = (side - width) / 2 - bounds[0];
323
+ const offsetY = (side - height) / 2 - bounds[1];
324
+ return this.transformed(
325
+ (x, y) => pt((x + offsetX) / side, (y + offsetY) / side)
326
+ );
327
+ }
328
+ calculateBounds(approximate = true) {
329
+ let minX = Number.MAX_VALUE;
330
+ let minY = Number.MAX_VALUE;
331
+ let maxX = -Number.MAX_VALUE;
332
+ let maxY = -Number.MAX_VALUE;
333
+ for (const cubic of this.cubics) {
334
+ const b = cubic.calculateBounds(approximate);
335
+ minX = Math.min(minX, b[0]);
336
+ minY = Math.min(minY, b[1]);
337
+ maxX = Math.max(maxX, b[2]);
338
+ maxY = Math.max(maxY, b[3]);
339
+ }
340
+ return [minX, minY, maxX, maxY];
341
+ }
342
+ calculateMaxBounds() {
343
+ let maxDistSq = 0;
344
+ for (const cubic of this.cubics) {
345
+ const anchorDist = distanceSquared(
346
+ cubic.anchor0X - this.centerX,
347
+ cubic.anchor0Y - this.centerY
348
+ );
349
+ const mid = cubic.pointOnCurve(0.5);
350
+ const midDist = distanceSquared(
351
+ mid.x - this.centerX,
352
+ mid.y - this.centerY
353
+ );
354
+ maxDistSq = Math.max(maxDistSq, Math.max(anchorDist, midDist));
355
+ }
356
+ const d = Math.sqrt(maxDistSq);
357
+ return [
358
+ this.centerX - d,
359
+ this.centerY - d,
360
+ this.centerX + d,
361
+ this.centerY + d
362
+ ];
363
+ }
364
+ };
365
+ function resolveFeatureCubics(features, index, splitStart, splitEnd) {
366
+ if (index === 0 && splitEnd) {
367
+ return splitEnd;
368
+ }
369
+ if (index === features.length) {
370
+ return splitStart;
371
+ }
372
+ return features[index].cubics;
373
+ }
374
+ function processCubic(cubic, state) {
375
+ if (cubic.zeroLength()) {
376
+ if (state.last) {
377
+ const newPoints = new Float64Array(state.last.points);
378
+ newPoints[6] = cubic.anchor1X;
379
+ newPoints[7] = cubic.anchor1Y;
380
+ state.last = new Cubic(newPoints);
381
+ }
382
+ return;
383
+ }
384
+ if (state.last) {
385
+ state.result.push(state.last);
386
+ }
387
+ state.last = cubic;
388
+ if (!state.first) {
389
+ state.first = cubic;
390
+ }
391
+ }
392
+ function buildCubicList(features, center) {
393
+ let splitStart = null;
394
+ let splitEnd = null;
395
+ if (features.length > 0 && features[0].cubics.length === 3) {
396
+ const centerCubic = features[0].cubics[1];
397
+ const [start, end] = centerCubic.split(0.5);
398
+ splitStart = [features[0].cubics[0], start];
399
+ splitEnd = [end, features[0].cubics[2]];
400
+ }
401
+ const state = {
402
+ first: null,
403
+ last: null,
404
+ result: []
405
+ };
406
+ for (let i = 0; i <= features.length; i++) {
407
+ const featureCubics = resolveFeatureCubics(
408
+ features,
409
+ i,
410
+ splitStart,
411
+ splitEnd
412
+ );
413
+ if (!featureCubics) {
414
+ break;
415
+ }
416
+ for (const cubic of featureCubics) {
417
+ processCubic(cubic, state);
418
+ }
419
+ }
420
+ if (state.last && state.first) {
421
+ state.result.push(
422
+ new Cubic(
423
+ state.last.anchor0X,
424
+ state.last.anchor0Y,
425
+ state.last.control0X,
426
+ state.last.control0Y,
427
+ state.last.control1X,
428
+ state.last.control1Y,
429
+ state.first.anchor0X,
430
+ state.first.anchor0Y
431
+ )
432
+ );
433
+ } else {
434
+ state.result.push(Cubic.empty(center.x, center.y));
435
+ }
436
+ return state.result;
437
+ }
438
+ var RoundedCorner = class {
439
+ d1;
440
+ d2;
441
+ cornerRadius;
442
+ smoothing;
443
+ cosAngle;
444
+ sinAngle;
445
+ expectedRoundCut;
446
+ center = pt(0, 0);
447
+ p0;
448
+ p1;
449
+ p2;
450
+ constructor(p0, p1, p2, rounding) {
451
+ this.p0 = p0;
452
+ this.p1 = p1;
453
+ this.p2 = p2;
454
+ const v01 = ptSub(p0, p1);
455
+ const v21 = ptSub(p2, p1);
456
+ const d01 = ptDistance(v01);
457
+ const d21 = ptDistance(v21);
458
+ if (d01 > 0 && d21 > 0) {
459
+ this.d1 = ptDiv(v01, d01);
460
+ this.d2 = ptDiv(v21, d21);
461
+ this.cornerRadius = rounding.radius;
462
+ this.smoothing = rounding.smoothing;
463
+ this.cosAngle = ptDot(this.d1, this.d2);
464
+ this.sinAngle = Math.sqrt(1 - square(this.cosAngle));
465
+ this.expectedRoundCut = this.sinAngle > 1e-3 ? this.cornerRadius * (this.cosAngle + 1) / this.sinAngle : 0;
466
+ } else {
467
+ this.d1 = pt(0, 0);
468
+ this.d2 = pt(0, 0);
469
+ this.cornerRadius = 0;
470
+ this.smoothing = 0;
471
+ this.cosAngle = 0;
472
+ this.sinAngle = 0;
473
+ this.expectedRoundCut = 0;
474
+ }
475
+ }
476
+ get expectedCut() {
477
+ return (1 + this.smoothing) * this.expectedRoundCut;
478
+ }
479
+ calculateActualSmoothingValue(allowedCut) {
480
+ if (allowedCut > this.expectedCut) {
481
+ return this.smoothing;
482
+ }
483
+ if (allowedCut > this.expectedRoundCut) {
484
+ return this.smoothing * (allowedCut - this.expectedRoundCut) / (this.expectedCut - this.expectedRoundCut);
485
+ }
486
+ return 0;
487
+ }
488
+ lineIntersection(p0, d0, p1, d1) {
489
+ const rotatedD1 = ptRotate90(d1);
490
+ const den = ptDot(d0, rotatedD1);
491
+ if (Math.abs(den) < distanceEpsilon) {
492
+ return null;
493
+ }
494
+ const diff = ptSub(p1, p0);
495
+ const num = ptDot(diff, rotatedD1);
496
+ if (Math.abs(den) < distanceEpsilon * Math.abs(num)) {
497
+ return null;
498
+ }
499
+ const k = num / den;
500
+ return ptAdd(p0, ptMul(d0, k));
501
+ }
502
+ computeFlankingCurve(actualRoundCut, actualSmoothingValues, corner, sideStart, circleSegmentIntersection, otherCircleSegmentIntersection, circleCenter, actualR) {
503
+ const sideDirection = ptDirection(ptSub(sideStart, corner));
504
+ const curveStart = ptAdd(
505
+ corner,
506
+ ptMul(sideDirection, actualRoundCut * (1 + actualSmoothingValues))
507
+ );
508
+ const p = interpolatePoint(
509
+ circleSegmentIntersection,
510
+ ptDiv(
511
+ ptAdd(circleSegmentIntersection, otherCircleSegmentIntersection),
512
+ 2
513
+ ),
514
+ actualSmoothingValues
515
+ );
516
+ const curveEnd = ptAdd(
517
+ circleCenter,
518
+ ptMul(
519
+ directionVector(p.x - circleCenter.x, p.y - circleCenter.y),
520
+ actualR
521
+ )
522
+ );
523
+ const circleTangent = ptRotate90(ptSub(curveEnd, circleCenter));
524
+ const anchorEnd = this.lineIntersection(
525
+ sideStart,
526
+ sideDirection,
527
+ curveEnd,
528
+ circleTangent
529
+ ) ?? circleSegmentIntersection;
530
+ const anchorStart = ptDiv(ptAdd(curveStart, ptMul(anchorEnd, 2)), 3);
531
+ return new Cubic(
532
+ curveStart.x,
533
+ curveStart.y,
534
+ anchorStart.x,
535
+ anchorStart.y,
536
+ anchorEnd.x,
537
+ anchorEnd.y,
538
+ curveEnd.x,
539
+ curveEnd.y
540
+ );
541
+ }
542
+ getCubics(allowedCut0, allowedCut1 = allowedCut0) {
543
+ const allowedCut = Math.min(allowedCut0, allowedCut1);
544
+ if (this.expectedRoundCut < distanceEpsilon || allowedCut < distanceEpsilon || this.cornerRadius < distanceEpsilon) {
545
+ this.center = this.p1;
546
+ return [Cubic.straightLine(this.p1.x, this.p1.y, this.p1.x, this.p1.y)];
547
+ }
548
+ const actualRoundCut = Math.min(allowedCut, this.expectedRoundCut);
549
+ const actualSmoothing0 = this.calculateActualSmoothingValue(allowedCut0);
550
+ const actualSmoothing1 = this.calculateActualSmoothingValue(allowedCut1);
551
+ const actualR = this.cornerRadius * (actualRoundCut / this.expectedRoundCut);
552
+ const centerDistance = Math.sqrt(square(actualR) + square(actualRoundCut));
553
+ const halfDir = ptDiv(ptAdd(this.d1, this.d2), 2);
554
+ const halfDirNorm = ptDirection(halfDir);
555
+ this.center = ptAdd(this.p1, ptMul(halfDirNorm, centerDistance));
556
+ const circleIntersection0 = ptAdd(this.p1, ptMul(this.d1, actualRoundCut));
557
+ const circleIntersection2 = ptAdd(this.p1, ptMul(this.d2, actualRoundCut));
558
+ const flanking0 = this.computeFlankingCurve(
559
+ actualRoundCut,
560
+ actualSmoothing0,
561
+ this.p1,
562
+ this.p0,
563
+ circleIntersection0,
564
+ circleIntersection2,
565
+ this.center,
566
+ actualR
567
+ );
568
+ const flanking2 = this.computeFlankingCurve(
569
+ actualRoundCut,
570
+ actualSmoothing1,
571
+ this.p1,
572
+ this.p2,
573
+ circleIntersection2,
574
+ circleIntersection0,
575
+ this.center,
576
+ actualR
577
+ ).reverse();
578
+ return [
579
+ flanking0,
580
+ Cubic.circularArc(
581
+ this.center.x,
582
+ this.center.y,
583
+ flanking0.anchor1X,
584
+ flanking0.anchor1Y,
585
+ flanking2.anchor0X,
586
+ flanking2.anchor0Y
587
+ ),
588
+ flanking2
589
+ ];
590
+ }
591
+ };
592
+ function calculateCenter(vertices) {
593
+ let cx = 0;
594
+ let cy = 0;
595
+ for (let i = 0; i < vertices.length; i += 2) {
596
+ cx += vertices[i];
597
+ cy += vertices[i + 1];
598
+ }
599
+ const n = vertices.length / 2;
600
+ return pt(cx / n, cy / n);
601
+ }
602
+ function verticesFromNumVerts(numVertices, radius, centerX, centerY) {
603
+ const result = [];
604
+ for (let i = 0; i < numVertices; i++) {
605
+ const v = radialToCartesian(
606
+ radius,
607
+ floatPi / numVertices * 2 * i,
608
+ pt(centerX, centerY)
609
+ );
610
+ result.push(v.x, v.y);
611
+ }
612
+ return result;
613
+ }
614
+ function createPolygonFromVertices(vertices, rounding = unrounded, perVertexRounding = null, centerX = Number.MIN_VALUE, centerY = Number.MIN_VALUE) {
615
+ const n = vertices.length / 2;
616
+ const roundedCorners = [];
617
+ for (let i = 0; i < n; i++) {
618
+ const vtxRounding = perVertexRounding?.[i] ?? rounding;
619
+ const prevIndex = (i + n - 1) % n * 2;
620
+ const nextIndex = (i + 1) % n * 2;
621
+ roundedCorners.push(
622
+ new RoundedCorner(
623
+ pt(vertices[prevIndex], vertices[prevIndex + 1]),
624
+ pt(vertices[i * 2], vertices[i * 2 + 1]),
625
+ pt(vertices[nextIndex], vertices[nextIndex + 1]),
626
+ vtxRounding
627
+ )
628
+ );
629
+ }
630
+ const cutAdjusts = [];
631
+ for (let ix = 0; ix < n; ix++) {
632
+ const expectedRoundCut = roundedCorners[ix].expectedRoundCut + roundedCorners[(ix + 1) % n].expectedRoundCut;
633
+ const expectedCut = roundedCorners[ix].expectedCut + roundedCorners[(ix + 1) % n].expectedCut;
634
+ const vtxX = vertices[ix * 2];
635
+ const vtxY = vertices[ix * 2 + 1];
636
+ const nextVtxX = vertices[(ix + 1) % n * 2];
637
+ const nextVtxY = vertices[(ix + 1) % n * 2 + 1];
638
+ const sideSize = distance(vtxX - nextVtxX, vtxY - nextVtxY);
639
+ if (expectedRoundCut > sideSize) {
640
+ cutAdjusts.push([sideSize / expectedRoundCut, 0]);
641
+ } else if (expectedCut > sideSize) {
642
+ cutAdjusts.push([
643
+ 1,
644
+ (sideSize - expectedRoundCut) / (expectedCut - expectedRoundCut)
645
+ ]);
646
+ } else {
647
+ cutAdjusts.push([1, 1]);
648
+ }
649
+ }
650
+ const corners = [];
651
+ for (let i = 0; i < n; i++) {
652
+ const allowedCuts = [];
653
+ for (let delta = 0; delta <= 1; delta++) {
654
+ const [roundCutRatio, cutRatio] = cutAdjusts[(i + n - 1 + delta) % n];
655
+ allowedCuts.push(
656
+ roundedCorners[i].expectedRoundCut * roundCutRatio + (roundedCorners[i].expectedCut - roundedCorners[i].expectedRoundCut) * cutRatio
657
+ );
658
+ }
659
+ corners.push(roundedCorners[i].getCubics(allowedCuts[0], allowedCuts[1]));
660
+ }
661
+ const tempFeatures = [];
662
+ for (let i = 0; i < n; i++) {
663
+ const prevVtxIndex = (i + n - 1) % n;
664
+ const nextVtxIndex = (i + 1) % n;
665
+ const currVertex = pt(vertices[i * 2], vertices[i * 2 + 1]);
666
+ const prevVertex = pt(
667
+ vertices[prevVtxIndex * 2],
668
+ vertices[prevVtxIndex * 2 + 1]
669
+ );
670
+ const nextVertex = pt(
671
+ vertices[nextVtxIndex * 2],
672
+ vertices[nextVtxIndex * 2 + 1]
673
+ );
674
+ const isConvex = convexFn(prevVertex, currVertex, nextVertex);
675
+ tempFeatures.push({ type: "corner", cubics: corners[i], convex: isConvex });
676
+ tempFeatures.push({
677
+ type: "edge",
678
+ cubics: [
679
+ Cubic.straightLine(
680
+ corners[i][corners[i].length - 1].anchor1X,
681
+ corners[i][corners[i].length - 1].anchor1Y,
682
+ corners[(i + 1) % n][0].anchor0X,
683
+ corners[(i + 1) % n][0].anchor0Y
684
+ )
685
+ ]
686
+ });
687
+ }
688
+ const center = centerX === Number.MIN_VALUE || centerY === Number.MIN_VALUE ? calculateCenter(vertices) : pt(centerX, centerY);
689
+ return new RoundedPolygon(tempFeatures, center);
690
+ }
691
+ function convexFn(prev, curr, next) {
692
+ const v1 = ptSub(curr, prev);
693
+ const v2 = ptSub(next, curr);
694
+ return v1.x * v2.y - v1.y * v2.x > 0;
695
+ }
696
+ function createPolygon(numVertices, radius = 1, centerX = 0, centerY = 0, rounding = unrounded, perVertexRounding = null) {
697
+ const vertices = verticesFromNumVerts(numVertices, radius, centerX, centerY);
698
+ return createPolygonFromVertices(
699
+ vertices,
700
+ rounding,
701
+ perVertexRounding,
702
+ centerX,
703
+ centerY
704
+ );
705
+ }
706
+ function createCircle(numVertices = 8, radius = 1, centerX = 0, centerY = 0) {
707
+ const theta = floatPi / numVertices;
708
+ const polygonRadius = radius / Math.cos(theta);
709
+ return createPolygon(
710
+ numVertices,
711
+ polygonRadius,
712
+ centerX,
713
+ centerY,
714
+ cornerRounding(radius)
715
+ );
716
+ }
717
+ function createRectangle(width = 2, height = 2, rounding = unrounded, perVertexRounding = null, centerX = 0, centerY = 0) {
718
+ const left = centerX - width / 2;
719
+ const top = centerY - height / 2;
720
+ const right = centerX + width / 2;
721
+ const bottom = centerY + height / 2;
722
+ return createPolygonFromVertices(
723
+ [right, bottom, left, bottom, left, top, right, top],
724
+ rounding,
725
+ perVertexRounding,
726
+ centerX,
727
+ centerY
728
+ );
729
+ }
730
+ function starVerticesFromNumVerts(numVerticesPerRadius, radius, innerRadius, centerX, centerY) {
731
+ const result = [];
732
+ for (let i = 0; i < numVerticesPerRadius; i++) {
733
+ let v = radialToCartesian(radius, floatPi / numVerticesPerRadius * 2 * i);
734
+ result.push(v.x + centerX, v.y + centerY);
735
+ v = radialToCartesian(
736
+ innerRadius,
737
+ floatPi / numVerticesPerRadius * (2 * i + 1)
738
+ );
739
+ result.push(v.x + centerX, v.y + centerY);
740
+ }
741
+ return result;
742
+ }
743
+ function createStar(numVerticesPerRadius, radius = 1, innerRadius = 0.5, rounding = unrounded, innerRounding = null, perVertexRounding = null, centerX = 0, centerY = 0) {
744
+ let pvRounding = perVertexRounding;
745
+ if (!pvRounding && innerRounding) {
746
+ pvRounding = [];
747
+ for (let i = 0; i < numVerticesPerRadius; i++) {
748
+ pvRounding.push(rounding, innerRounding);
749
+ }
750
+ }
751
+ const vertices = starVerticesFromNumVerts(
752
+ numVerticesPerRadius,
753
+ radius,
754
+ innerRadius,
755
+ centerX,
756
+ centerY
757
+ );
758
+ return createPolygonFromVertices(
759
+ vertices,
760
+ rounding,
761
+ pvRounding,
762
+ centerX,
763
+ centerY
764
+ );
765
+ }
766
+
767
+ // src/core/material-shapes.ts
768
+ function pnr(x, y, r = unrounded) {
769
+ return { x, y, r };
770
+ }
771
+ function angleDegrees(x, y) {
772
+ return Math.atan2(y, x) * 180 / floatPi;
773
+ }
774
+ function toRadians(degrees) {
775
+ return degrees / 360 * 2 * floatPi;
776
+ }
777
+ function distancePt(x, y) {
778
+ return Math.sqrt(x * x + y * y);
779
+ }
780
+ function rotateDegrees(px, py, angle, cx = 0, cy = 0) {
781
+ const a = toRadians(angle);
782
+ const ox = px - cx;
783
+ const oy = py - cy;
784
+ return [
785
+ ox * Math.cos(a) - oy * Math.sin(a) + cx,
786
+ ox * Math.sin(a) + oy * Math.cos(a) + cy
787
+ ];
788
+ }
789
+ function doRepeatMirrored(points, reps, centerX, centerY) {
790
+ const angles = points.map((p) => angleDegrees(p.x - centerX, p.y - centerY));
791
+ const distances = points.map((p) => distancePt(p.x - centerX, p.y - centerY));
792
+ const actualReps = reps * 2;
793
+ const sectionAngle = 360 / actualReps;
794
+ const result = [];
795
+ for (let it = 0; it < actualReps; it++) {
796
+ const mirrored = it % 2 !== 0;
797
+ for (let index = 0; index < points.length; index++) {
798
+ const i = mirrored ? points.length - 1 - index : index;
799
+ if (i > 0 || !mirrored) {
800
+ const angleOffset = mirrored ? sectionAngle - angles[i] + 2 * angles[0] : angles[i];
801
+ const a = toRadians(sectionAngle * it + angleOffset);
802
+ const finalX = Math.cos(a) * distances[i] + centerX;
803
+ const finalY = Math.sin(a) * distances[i] + centerY;
804
+ result.push(pnr(finalX, finalY, points[i].r));
805
+ }
806
+ }
807
+ }
808
+ return result;
809
+ }
810
+ function doRepeatRotated(points, reps, centerX, centerY) {
811
+ const np = points.length;
812
+ const result = [];
813
+ for (let it = 0; it < np * reps; it++) {
814
+ const srcPoint = points[it % np];
815
+ const [rx, ry] = rotateDegrees(
816
+ srcPoint.x,
817
+ srcPoint.y,
818
+ Math.floor(it / np) * (360 / reps),
819
+ centerX,
820
+ centerY
821
+ );
822
+ result.push(pnr(rx, ry, srcPoint.r));
823
+ }
824
+ return result;
825
+ }
826
+ function doRepeat(points, reps, centerX, centerY, mirroring) {
827
+ if (mirroring) {
828
+ return doRepeatMirrored(points, reps, centerX, centerY);
829
+ }
830
+ return doRepeatRotated(points, reps, centerX, centerY);
831
+ }
832
+ function customPolygon(points, reps, centerX = 0.5, centerY = 0.5, mirroring = false) {
833
+ const actualPoints = doRepeat(points, reps, centerX, centerY, mirroring);
834
+ const vertices = [];
835
+ const pvRounding = [];
836
+ for (const p of actualPoints) {
837
+ vertices.push(p.x, p.y);
838
+ pvRounding.push(p.r);
839
+ }
840
+ return createPolygonFromVertices(
841
+ vertices,
842
+ unrounded,
843
+ pvRounding,
844
+ centerX,
845
+ centerY
846
+ );
847
+ }
848
+ function rotatePolygon(polygon, degrees) {
849
+ const rad = toRadians(degrees);
850
+ const cos = Math.cos(rad);
851
+ const sin = Math.sin(rad);
852
+ return polygon.transformed((x, y) => ({
853
+ x: x * cos - y * sin,
854
+ y: x * sin + y * cos
855
+ }));
856
+ }
857
+ var r15 = cornerRounding(0.15);
858
+ var r20 = cornerRounding(0.2);
859
+ var r30 = cornerRounding(0.3);
860
+ var r50 = cornerRounding(0.5);
861
+ var r100 = cornerRounding(1);
862
+ function circle() {
863
+ return createCircle(10);
864
+ }
865
+ function square2() {
866
+ return createRectangle(1, 1, r30);
867
+ }
868
+ function slanted() {
869
+ return customPolygon(
870
+ [
871
+ pnr(0.926, 0.97, cornerRounding(0.189, 0.811)),
872
+ pnr(-0.021, 0.967, cornerRounding(0.187, 0.057))
873
+ ],
874
+ 2
875
+ );
876
+ }
877
+ function arch() {
878
+ return rotatePolygon(
879
+ createPolygonFromVertices(
880
+ // 4 vertices for a square, manually positioning
881
+ (() => {
882
+ const r = 1;
883
+ const verts = [];
884
+ for (let i = 0; i < 4; i++) {
885
+ const angle = floatPi / 4 * 2 * i;
886
+ verts.push(Math.cos(angle) * r, Math.sin(angle) * r);
887
+ }
888
+ return verts;
889
+ })(),
890
+ unrounded,
891
+ [r100, r100, r20, r20],
892
+ 0,
893
+ 0
894
+ ),
895
+ -135
896
+ );
897
+ }
898
+ function fan() {
899
+ return customPolygon(
900
+ [
901
+ pnr(1.004, 1, cornerRounding(0.148, 0.417)),
902
+ pnr(0, 1, cornerRounding(0.151)),
903
+ pnr(0, -3e-3, cornerRounding(0.148)),
904
+ pnr(0.978, 0.02, cornerRounding(0.803))
905
+ ],
906
+ 1
907
+ );
908
+ }
909
+ function arrow() {
910
+ return customPolygon(
911
+ [
912
+ pnr(0.5, 0.892, cornerRounding(0.313)),
913
+ pnr(-0.216, 1.05, cornerRounding(0.207)),
914
+ pnr(0.499, -0.16, cornerRounding(0.215, 1)),
915
+ pnr(1.225, 1.06, cornerRounding(0.211))
916
+ ],
917
+ 1
918
+ );
919
+ }
920
+ function semiCircle() {
921
+ return createRectangle(1.6, 1, unrounded, [r20, r20, r100, r100]);
922
+ }
923
+ function oval() {
924
+ const scaled = createCircle().transformed((x, y) => ({ x, y: y * 0.64 }));
925
+ return rotatePolygon(scaled, -45);
926
+ }
927
+ function pill() {
928
+ return customPolygon(
929
+ [
930
+ pnr(0.961, 0.039, cornerRounding(0.426)),
931
+ pnr(1.001, 0.428),
932
+ pnr(1, 0.609, cornerRounding(1))
933
+ ],
934
+ 2,
935
+ 0.5,
936
+ 0.5,
937
+ true
938
+ );
939
+ }
940
+ function triangle() {
941
+ return rotatePolygon(
942
+ createPolygonFromVertices(
943
+ (() => {
944
+ const verts = [];
945
+ for (let i = 0; i < 3; i++) {
946
+ verts.push(
947
+ Math.cos(floatPi / 3 * 2 * i),
948
+ Math.sin(floatPi / 3 * 2 * i)
949
+ );
950
+ }
951
+ return verts;
952
+ })(),
953
+ r20,
954
+ null,
955
+ 0,
956
+ 0
957
+ ),
958
+ -90
959
+ );
960
+ }
961
+ function diamond() {
962
+ return customPolygon(
963
+ [
964
+ pnr(0.5, 1.096, cornerRounding(0.151, 0.524)),
965
+ pnr(0.04, 0.5, cornerRounding(0.159))
966
+ ],
967
+ 2
968
+ );
969
+ }
970
+ function clamShell() {
971
+ return customPolygon(
972
+ [
973
+ pnr(0.171, 0.841, cornerRounding(0.159)),
974
+ pnr(-0.02, 0.5, cornerRounding(0.14)),
975
+ pnr(0.17, 0.159, cornerRounding(0.159))
976
+ ],
977
+ 2
978
+ );
979
+ }
980
+ function pentagon() {
981
+ return customPolygon(
982
+ [
983
+ pnr(0.5, -9e-3, cornerRounding(0.172)),
984
+ pnr(1.03, 0.365, cornerRounding(0.164)),
985
+ pnr(0.828, 0.97, cornerRounding(0.169))
986
+ ],
987
+ 1,
988
+ 0.5,
989
+ 0.5,
990
+ true
991
+ );
992
+ }
993
+ function gem() {
994
+ return customPolygon(
995
+ [
996
+ pnr(0.499, 1.023, cornerRounding(0.241, 0.778)),
997
+ pnr(-5e-3, 0.792, cornerRounding(0.208)),
998
+ pnr(0.073, 0.258, cornerRounding(0.228)),
999
+ pnr(0.433, 0, cornerRounding(0.491))
1000
+ ],
1001
+ 1,
1002
+ 0.5,
1003
+ 0.5,
1004
+ true
1005
+ );
1006
+ }
1007
+ function sunny() {
1008
+ return createStar(8, 1, 0.8, r15);
1009
+ }
1010
+ function verySunny() {
1011
+ return customPolygon(
1012
+ [
1013
+ pnr(0.5, 1.08, cornerRounding(0.085)),
1014
+ pnr(0.358, 0.843, cornerRounding(0.085))
1015
+ ],
1016
+ 8
1017
+ );
1018
+ }
1019
+ function cookie4() {
1020
+ return customPolygon(
1021
+ [
1022
+ pnr(1.237, 1.236, cornerRounding(0.258)),
1023
+ pnr(0.5, 0.918, cornerRounding(0.233))
1024
+ ],
1025
+ 4
1026
+ );
1027
+ }
1028
+ function cookie6() {
1029
+ return customPolygon(
1030
+ [
1031
+ pnr(0.723, 0.884, cornerRounding(0.394)),
1032
+ pnr(0.5, 1.099, cornerRounding(0.398))
1033
+ ],
1034
+ 6
1035
+ );
1036
+ }
1037
+ function cookie7() {
1038
+ return rotatePolygon(createStar(7, 1, 0.75, r50), -90);
1039
+ }
1040
+ function cookie9() {
1041
+ return rotatePolygon(createStar(9, 1, 0.8, r50), -90);
1042
+ }
1043
+ function cookie12() {
1044
+ return rotatePolygon(createStar(12, 1, 0.8, r50), -90);
1045
+ }
1046
+ function ghostish() {
1047
+ return customPolygon(
1048
+ [
1049
+ pnr(0.5, 0, cornerRounding(1)),
1050
+ pnr(1, 0, cornerRounding(1)),
1051
+ pnr(1, 1.14, cornerRounding(0.254, 0.106)),
1052
+ pnr(0.575, 0.906, cornerRounding(0.253))
1053
+ ],
1054
+ 1,
1055
+ 0.5,
1056
+ 0.5,
1057
+ true
1058
+ );
1059
+ }
1060
+ function clover4() {
1061
+ return customPolygon(
1062
+ [pnr(0.5, 0.074), pnr(0.725, -0.099, cornerRounding(0.476))],
1063
+ 4,
1064
+ 0.5,
1065
+ 0.5,
1066
+ true
1067
+ );
1068
+ }
1069
+ function clover8() {
1070
+ return customPolygon(
1071
+ [pnr(0.5, 0.036), pnr(0.758, -0.101, cornerRounding(0.209))],
1072
+ 8
1073
+ );
1074
+ }
1075
+ function burst() {
1076
+ return customPolygon(
1077
+ [
1078
+ pnr(0.5, -6e-3, cornerRounding(6e-3)),
1079
+ pnr(0.592, 0.158, cornerRounding(6e-3))
1080
+ ],
1081
+ 12
1082
+ );
1083
+ }
1084
+ function softBurst() {
1085
+ return customPolygon(
1086
+ [
1087
+ pnr(0.193, 0.277, cornerRounding(0.053)),
1088
+ pnr(0.176, 0.055, cornerRounding(0.053))
1089
+ ],
1090
+ 10
1091
+ );
1092
+ }
1093
+ function boom() {
1094
+ return customPolygon(
1095
+ [
1096
+ pnr(0.457, 0.296, cornerRounding(7e-3)),
1097
+ pnr(0.5, -0.051, cornerRounding(7e-3))
1098
+ ],
1099
+ 15
1100
+ );
1101
+ }
1102
+ function softBoom() {
1103
+ return customPolygon(
1104
+ [
1105
+ pnr(0.733, 0.454),
1106
+ pnr(0.839, 0.437, cornerRounding(0.532)),
1107
+ pnr(0.949, 0.449, cornerRounding(0.439, 1)),
1108
+ pnr(0.998, 0.478, cornerRounding(0.174))
1109
+ ],
1110
+ 16,
1111
+ 0.5,
1112
+ 0.5,
1113
+ true
1114
+ );
1115
+ }
1116
+ function flower() {
1117
+ return customPolygon(
1118
+ [
1119
+ pnr(0.37, 0.187),
1120
+ pnr(0.416, 0.049, cornerRounding(0.381)),
1121
+ pnr(0.479, 1e-3, cornerRounding(0.095))
1122
+ ],
1123
+ 8,
1124
+ 0.5,
1125
+ 0.5,
1126
+ true
1127
+ );
1128
+ }
1129
+ function puffy() {
1130
+ const base = customPolygon(
1131
+ [
1132
+ pnr(0.5, 0.053),
1133
+ pnr(0.545, -0.04, cornerRounding(0.405)),
1134
+ pnr(0.67, -0.035, cornerRounding(0.426)),
1135
+ pnr(0.717, 0.066, cornerRounding(0.574)),
1136
+ pnr(0.722, 0.128),
1137
+ pnr(0.777, 2e-3, cornerRounding(0.36)),
1138
+ pnr(0.914, 0.149, cornerRounding(0.66)),
1139
+ pnr(0.926, 0.289, cornerRounding(0.66)),
1140
+ pnr(0.881, 0.346),
1141
+ pnr(0.94, 0.344, cornerRounding(0.126)),
1142
+ pnr(1.003, 0.437, cornerRounding(0.255))
1143
+ ],
1144
+ 2,
1145
+ 0.5,
1146
+ 0.5,
1147
+ true
1148
+ );
1149
+ return base.transformed((x, y) => ({ x, y: y * 0.742 }));
1150
+ }
1151
+ function puffyDiamond() {
1152
+ return customPolygon(
1153
+ [
1154
+ pnr(0.87, 0.13, cornerRounding(0.146)),
1155
+ pnr(0.818, 0.357),
1156
+ pnr(1, 0.332, cornerRounding(0.853))
1157
+ ],
1158
+ 4,
1159
+ 0.5,
1160
+ 0.5,
1161
+ true
1162
+ );
1163
+ }
1164
+ function pixelCircle() {
1165
+ return customPolygon(
1166
+ [
1167
+ pnr(0.5, 0),
1168
+ pnr(0.704, 0),
1169
+ pnr(0.704, 0.065),
1170
+ pnr(0.843, 0.065),
1171
+ pnr(0.843, 0.148),
1172
+ pnr(0.926, 0.148),
1173
+ pnr(0.926, 0.296),
1174
+ pnr(1, 0.296)
1175
+ ],
1176
+ 2,
1177
+ 0.5,
1178
+ 0.5,
1179
+ true
1180
+ );
1181
+ }
1182
+ function pixelTriangle() {
1183
+ return customPolygon(
1184
+ [
1185
+ pnr(0.11, 0.5),
1186
+ pnr(0.113, 0),
1187
+ pnr(0.287, 0),
1188
+ pnr(0.287, 0.087),
1189
+ pnr(0.421, 0.087),
1190
+ pnr(0.421, 0.17),
1191
+ pnr(0.56, 0.17),
1192
+ pnr(0.56, 0.265),
1193
+ pnr(0.674, 0.265),
1194
+ pnr(0.675, 0.344),
1195
+ pnr(0.789, 0.344),
1196
+ pnr(0.789, 0.439),
1197
+ pnr(0.888, 0.439)
1198
+ ],
1199
+ 1,
1200
+ 0.5,
1201
+ 0.5,
1202
+ true
1203
+ );
1204
+ }
1205
+ function bun() {
1206
+ return customPolygon(
1207
+ [
1208
+ pnr(0.796, 0.5),
1209
+ pnr(0.853, 0.518, cornerRounding(1)),
1210
+ pnr(0.992, 0.631, cornerRounding(1)),
1211
+ pnr(0.968, 1, cornerRounding(1))
1212
+ ],
1213
+ 2,
1214
+ 0.5,
1215
+ 0.5,
1216
+ true
1217
+ );
1218
+ }
1219
+ function heart() {
1220
+ return customPolygon(
1221
+ [
1222
+ pnr(0.5, 0.268, cornerRounding(0.016)),
1223
+ pnr(0.792, -0.066, cornerRounding(0.958)),
1224
+ pnr(1.064, 0.276, cornerRounding(1)),
1225
+ pnr(0.501, 0.946, cornerRounding(0.129))
1226
+ ],
1227
+ 1,
1228
+ 0.5,
1229
+ 0.5,
1230
+ true
1231
+ );
1232
+ }
1233
+ var shapeFactories = {
1234
+ Circle: circle,
1235
+ Square: square2,
1236
+ Slanted: slanted,
1237
+ Arch: arch,
1238
+ Fan: fan,
1239
+ Arrow: arrow,
1240
+ SemiCircle: semiCircle,
1241
+ Oval: oval,
1242
+ Pill: pill,
1243
+ Triangle: triangle,
1244
+ Diamond: diamond,
1245
+ ClamShell: clamShell,
1246
+ Pentagon: pentagon,
1247
+ Gem: gem,
1248
+ Sunny: sunny,
1249
+ VerySunny: verySunny,
1250
+ Cookie4Sided: cookie4,
1251
+ Cookie6Sided: cookie6,
1252
+ Cookie7Sided: cookie7,
1253
+ Cookie9Sided: cookie9,
1254
+ Cookie12Sided: cookie12,
1255
+ Ghostish: ghostish,
1256
+ Clover4Leaf: clover4,
1257
+ Clover8Leaf: clover8,
1258
+ Burst: burst,
1259
+ SoftBurst: softBurst,
1260
+ Boom: boom,
1261
+ SoftBoom: softBoom,
1262
+ Flower: flower,
1263
+ Puffy: puffy,
1264
+ PuffyDiamond: puffyDiamond,
1265
+ PixelCircle: pixelCircle,
1266
+ PixelTriangle: pixelTriangle,
1267
+ Bun: bun,
1268
+ Heart: heart
1269
+ };
1270
+ var shapeNames = Object.keys(
1271
+ shapeFactories
1272
+ );
1273
+ var cache = /* @__PURE__ */ new Map();
1274
+ function getShape(name) {
1275
+ let shape = cache.get(name);
1276
+ if (!shape) {
1277
+ const factory = shapeFactories[name];
1278
+ shape = factory().normalized();
1279
+ cache.set(name, shape);
1280
+ }
1281
+ return shape;
1282
+ }
1283
+
1284
+ // src/core/morph.ts
1285
+ var LengthMeasurer = class {
1286
+ segments = 3;
1287
+ measureCubic(c) {
1288
+ return this.closestProgressTo(c, Number.POSITIVE_INFINITY)[1];
1289
+ }
1290
+ findCubicCutPoint(c, m) {
1291
+ return this.closestProgressTo(c, m)[0];
1292
+ }
1293
+ closestProgressTo(cubic, threshold) {
1294
+ let total = 0;
1295
+ let remainder = threshold;
1296
+ let prev = pt(cubic.anchor0X, cubic.anchor0Y);
1297
+ for (let i = 1; i <= this.segments; i++) {
1298
+ const progress = i / this.segments;
1299
+ const point = cubic.pointOnCurve(progress);
1300
+ const segment = ptDistance(ptSub(point, prev));
1301
+ if (segment >= remainder) {
1302
+ return [
1303
+ progress - (1 - remainder / segment) / this.segments,
1304
+ threshold
1305
+ ];
1306
+ }
1307
+ remainder -= segment;
1308
+ total += segment;
1309
+ prev = point;
1310
+ }
1311
+ return [1, total];
1312
+ }
1313
+ };
1314
+ function cutMeasuredCubicAtProgress(mc, cutOutlineProgress, measurer) {
1315
+ const bounded = Math.max(
1316
+ mc.startOutlineProgress,
1317
+ Math.min(mc.endOutlineProgress, cutOutlineProgress)
1318
+ );
1319
+ const outlineProgressSize = mc.endOutlineProgress - mc.startOutlineProgress;
1320
+ const progressFromStart = bounded - mc.startOutlineProgress;
1321
+ const relativeProgress = progressFromStart / outlineProgressSize;
1322
+ const t = measurer.findCubicCutPoint(
1323
+ mc.cubic,
1324
+ relativeProgress * mc.measuredSize
1325
+ );
1326
+ const [c1, c2] = mc.cubic.split(t);
1327
+ return [
1328
+ {
1329
+ cubic: c1,
1330
+ startOutlineProgress: mc.startOutlineProgress,
1331
+ endOutlineProgress: bounded,
1332
+ measuredSize: measurer.measureCubic(c1)
1333
+ },
1334
+ {
1335
+ cubic: c2,
1336
+ startOutlineProgress: bounded,
1337
+ endOutlineProgress: mc.endOutlineProgress,
1338
+ measuredSize: measurer.measureCubic(c2)
1339
+ }
1340
+ ];
1341
+ }
1342
+ function measurePolygon(measurer, polygon) {
1343
+ const cubics = [];
1344
+ const featureToCubic = [];
1345
+ for (const feature of polygon.features) {
1346
+ for (let cubicIndex = 0; cubicIndex < feature.cubics.length; cubicIndex++) {
1347
+ if (feature.type === "corner" && cubicIndex === Math.floor(feature.cubics.length / 2)) {
1348
+ featureToCubic.push([feature, cubics.length]);
1349
+ }
1350
+ cubics.push(feature.cubics[cubicIndex]);
1351
+ }
1352
+ }
1353
+ const measures = [0];
1354
+ let cumulative = 0;
1355
+ for (const cubic of cubics) {
1356
+ const m = measurer.measureCubic(cubic);
1357
+ cumulative += m;
1358
+ measures.push(cumulative);
1359
+ }
1360
+ const totalMeasure = cumulative;
1361
+ const outlineProgress = measures.map((m) => m / totalMeasure);
1362
+ const features = featureToCubic.map(
1363
+ ([feature, ix]) => ({
1364
+ progress: positiveModulo(
1365
+ (outlineProgress[ix] + outlineProgress[ix + 1]) / 2,
1366
+ 1
1367
+ ),
1368
+ feature
1369
+ })
1370
+ );
1371
+ const measuredCubics = [];
1372
+ let startProgress = 0;
1373
+ for (let i = 0; i < cubics.length; i++) {
1374
+ if (outlineProgress[i + 1] - outlineProgress[i] > distanceEpsilon) {
1375
+ measuredCubics.push({
1376
+ cubic: cubics[i],
1377
+ startOutlineProgress: startProgress,
1378
+ endOutlineProgress: outlineProgress[i + 1],
1379
+ measuredSize: measurer.measureCubic(cubics[i])
1380
+ });
1381
+ startProgress = outlineProgress[i + 1];
1382
+ }
1383
+ }
1384
+ const lastMeasured = measuredCubics.at(-1);
1385
+ if (lastMeasured) {
1386
+ lastMeasured.endOutlineProgress = 1;
1387
+ }
1388
+ return { cubics: measuredCubics, features, measurer };
1389
+ }
1390
+ function cutAndShift(mp, cuttingPoint) {
1391
+ if (cuttingPoint < distanceEpsilon) {
1392
+ return mp;
1393
+ }
1394
+ const cubics = mp.cubics;
1395
+ let targetIndex = cubics.findIndex(
1396
+ (c) => cuttingPoint >= c.startOutlineProgress && cuttingPoint <= c.endOutlineProgress
1397
+ );
1398
+ if (targetIndex === -1) {
1399
+ targetIndex = cubics.length - 1;
1400
+ }
1401
+ const [b1, b2] = cutMeasuredCubicAtProgress(
1402
+ cubics[targetIndex],
1403
+ cuttingPoint,
1404
+ mp.measurer
1405
+ );
1406
+ const retCubics = [b2.cubic];
1407
+ for (let i = 1; i < cubics.length; i++) {
1408
+ retCubics.push(cubics[(i + targetIndex) % cubics.length].cubic);
1409
+ }
1410
+ retCubics.push(b1.cubic);
1411
+ const retOutlineProgress = [];
1412
+ for (let index = 0; index < cubics.length + 2; index++) {
1413
+ if (index === 0) {
1414
+ retOutlineProgress.push(0);
1415
+ } else if (index === cubics.length + 1) {
1416
+ retOutlineProgress.push(1);
1417
+ } else {
1418
+ const cubicIndex = (targetIndex + index - 1) % cubics.length;
1419
+ retOutlineProgress.push(
1420
+ positiveModulo(cubics[cubicIndex].endOutlineProgress - cuttingPoint, 1)
1421
+ );
1422
+ }
1423
+ }
1424
+ const newFeatures = mp.features.map((f) => ({
1425
+ progress: positiveModulo(f.progress - cuttingPoint, 1),
1426
+ feature: f.feature
1427
+ }));
1428
+ const measuredCubics = [];
1429
+ let startProgress = 0;
1430
+ for (let i = 0; i < retCubics.length; i++) {
1431
+ if (retOutlineProgress[i + 1] - retOutlineProgress[i] > distanceEpsilon) {
1432
+ measuredCubics.push({
1433
+ cubic: retCubics[i],
1434
+ startOutlineProgress: startProgress,
1435
+ endOutlineProgress: retOutlineProgress[i + 1],
1436
+ measuredSize: mp.measurer.measureCubic(retCubics[i])
1437
+ });
1438
+ startProgress = retOutlineProgress[i + 1];
1439
+ }
1440
+ }
1441
+ const lastCut = measuredCubics.at(-1);
1442
+ if (lastCut) {
1443
+ lastCut.endOutlineProgress = 1;
1444
+ }
1445
+ return {
1446
+ cubics: measuredCubics,
1447
+ features: newFeatures,
1448
+ measurer: mp.measurer
1449
+ };
1450
+ }
1451
+ function progressInRange(progress, from, to) {
1452
+ if (to >= from) {
1453
+ return progress >= from && progress <= to;
1454
+ }
1455
+ return progress >= from || progress <= to;
1456
+ }
1457
+ function progressDistance(p1, p2) {
1458
+ const d = Math.abs(p1 - p2);
1459
+ return Math.min(d, 1 - d);
1460
+ }
1461
+ function linearMap(xValues, yValues, x) {
1462
+ let segStartIndex = 0;
1463
+ for (let i = 0; i < xValues.length; i++) {
1464
+ if (progressInRange(x, xValues[i], xValues[(i + 1) % xValues.length])) {
1465
+ segStartIndex = i;
1466
+ break;
1467
+ }
1468
+ }
1469
+ const segEndIndex = (segStartIndex + 1) % xValues.length;
1470
+ const segSizeX = positiveModulo(
1471
+ xValues[segEndIndex] - xValues[segStartIndex],
1472
+ 1
1473
+ );
1474
+ const segSizeY = positiveModulo(
1475
+ yValues[segEndIndex] - yValues[segStartIndex],
1476
+ 1
1477
+ );
1478
+ const posInSeg = segSizeX < 1e-3 ? 0.5 : positiveModulo(x - xValues[segStartIndex], 1) / segSizeX;
1479
+ return positiveModulo(yValues[segStartIndex] + segSizeY * posInSeg, 1);
1480
+ }
1481
+ var DoubleMapper = class _DoubleMapper {
1482
+ sourceValues;
1483
+ targetValues;
1484
+ constructor(mappings) {
1485
+ this.sourceValues = mappings.map((m) => m[0]);
1486
+ this.targetValues = mappings.map((m) => m[1]);
1487
+ }
1488
+ map(x) {
1489
+ return linearMap(this.sourceValues, this.targetValues, x);
1490
+ }
1491
+ mapBack(x) {
1492
+ return linearMap(this.targetValues, this.sourceValues, x);
1493
+ }
1494
+ static Identity = new _DoubleMapper([
1495
+ [0, 0],
1496
+ [0.5, 0.5]
1497
+ ]);
1498
+ };
1499
+ function featureRepresentativePoint(f) {
1500
+ const first = f.cubics[0];
1501
+ const last = f.cubics.at(-1) ?? first;
1502
+ return pt(
1503
+ (first.anchor0X + last.anchor1X) / 2,
1504
+ (first.anchor0Y + last.anchor1Y) / 2
1505
+ );
1506
+ }
1507
+ function featureDistSquared(f1, f2) {
1508
+ if (f1.type === "corner" && f2.type === "corner" && f1.convex !== f2.convex) {
1509
+ return Number.MAX_VALUE;
1510
+ }
1511
+ const p1 = featureRepresentativePoint(f1);
1512
+ const p2 = featureRepresentativePoint(f2);
1513
+ const d = ptSub(p1, p2);
1514
+ return d.x * d.x + d.y * d.y;
1515
+ }
1516
+ function buildCandidateList(features1, features2) {
1517
+ const result = [];
1518
+ for (const f1 of features1) {
1519
+ for (const f2 of features2) {
1520
+ const d = featureDistSquared(f1.feature, f2.feature);
1521
+ if (d !== Number.MAX_VALUE) {
1522
+ result.push({ distance: d, f1, f2 });
1523
+ }
1524
+ }
1525
+ }
1526
+ result.sort((a, b) => a.distance - b.distance);
1527
+ return result;
1528
+ }
1529
+ function canInsertMapping(mapping, insertionIndex, p1, p2) {
1530
+ const n = mapping.length;
1531
+ if (n === 0) {
1532
+ return true;
1533
+ }
1534
+ const [before1, before2] = mapping[(insertionIndex + n - 1) % n];
1535
+ const [after1, after2] = mapping[insertionIndex % n];
1536
+ if (progressDistance(p1, before1) < distanceEpsilon || progressDistance(p1, after1) < distanceEpsilon || progressDistance(p2, before2) < distanceEpsilon || progressDistance(p2, after2) < distanceEpsilon) {
1537
+ return false;
1538
+ }
1539
+ return n <= 1 || progressInRange(p2, before2, after2);
1540
+ }
1541
+ function doMapping(features1, features2) {
1542
+ const candidates = buildCandidateList(features1, features2);
1543
+ if (candidates.length === 0) {
1544
+ return [
1545
+ [0, 0],
1546
+ [0.5, 0.5]
1547
+ ];
1548
+ }
1549
+ if (candidates.length === 1) {
1550
+ const dv = candidates[0];
1551
+ return [
1552
+ [dv.f1.progress, dv.f2.progress],
1553
+ [(dv.f1.progress + 0.5) % 1, (dv.f2.progress + 0.5) % 1]
1554
+ ];
1555
+ }
1556
+ const mapping = [];
1557
+ const usedF1 = /* @__PURE__ */ new Set();
1558
+ const usedF2 = /* @__PURE__ */ new Set();
1559
+ for (const dv of candidates) {
1560
+ if (usedF1.has(dv.f1) || usedF2.has(dv.f2)) {
1561
+ continue;
1562
+ }
1563
+ let insertionIndex = 0;
1564
+ for (let i = 0; i < mapping.length; i++) {
1565
+ if (mapping[i][0] < dv.f1.progress) {
1566
+ insertionIndex = i + 1;
1567
+ }
1568
+ }
1569
+ if (!canInsertMapping(mapping, insertionIndex, dv.f1.progress, dv.f2.progress)) {
1570
+ continue;
1571
+ }
1572
+ mapping.splice(insertionIndex, 0, [dv.f1.progress, dv.f2.progress]);
1573
+ usedF1.add(dv.f1);
1574
+ usedF2.add(dv.f2);
1575
+ }
1576
+ return mapping;
1577
+ }
1578
+ function featureMapper(features1, features2) {
1579
+ const filtered1 = features1.filter((f) => f.feature.type === "corner");
1580
+ const filtered2 = features2.filter((f) => f.feature.type === "corner");
1581
+ const mapping = doMapping(filtered1, filtered2);
1582
+ return new DoubleMapper(mapping);
1583
+ }
1584
+ var Morph = class {
1585
+ morphMatch;
1586
+ constructor(start, end) {
1587
+ this.morphMatch = matchPolygons(start, end);
1588
+ }
1589
+ asCubics(progress) {
1590
+ const result = [];
1591
+ let firstCubic = null;
1592
+ let lastCubic = null;
1593
+ for (const [a, b] of this.morphMatch) {
1594
+ const points = new Float64Array(8);
1595
+ for (let j = 0; j < 8; j++) {
1596
+ points[j] = interpolate(a.points[j], b.points[j], progress);
1597
+ }
1598
+ const cubic = new Cubic(points);
1599
+ if (!firstCubic) {
1600
+ firstCubic = cubic;
1601
+ }
1602
+ if (lastCubic) {
1603
+ result.push(lastCubic);
1604
+ }
1605
+ lastCubic = cubic;
1606
+ }
1607
+ if (lastCubic && firstCubic) {
1608
+ result.push(
1609
+ new Cubic(
1610
+ lastCubic.anchor0X,
1611
+ lastCubic.anchor0Y,
1612
+ lastCubic.control0X,
1613
+ lastCubic.control0Y,
1614
+ lastCubic.control1X,
1615
+ lastCubic.control1Y,
1616
+ firstCubic.anchor0X,
1617
+ firstCubic.anchor0Y
1618
+ )
1619
+ );
1620
+ }
1621
+ return result;
1622
+ }
1623
+ };
1624
+ function matchPolygons(p1, p2) {
1625
+ const measurer = new LengthMeasurer();
1626
+ const measuredPolygon1 = measurePolygon(measurer, p1);
1627
+ const measuredPolygon2 = measurePolygon(measurer, p2);
1628
+ const doubleMapper = featureMapper(
1629
+ measuredPolygon1.features,
1630
+ measuredPolygon2.features
1631
+ );
1632
+ const polygon2CutPoint = doubleMapper.map(0);
1633
+ const bs1 = measuredPolygon1;
1634
+ const bs2 = cutAndShift(measuredPolygon2, polygon2CutPoint);
1635
+ const ret = [];
1636
+ let i1 = 0;
1637
+ let i2 = 0;
1638
+ let b1 = bs1.cubics[i1++] ?? null;
1639
+ let b2 = bs2.cubics[i2++] ?? null;
1640
+ while (b1 !== null && b2 !== null) {
1641
+ const b1a = i1 === bs1.cubics.length ? 1 : b1.endOutlineProgress;
1642
+ const b2a = i2 === bs2.cubics.length ? 1 : doubleMapper.mapBack(
1643
+ positiveModulo(b2.endOutlineProgress + polygon2CutPoint, 1)
1644
+ );
1645
+ const minb = Math.min(b1a, b2a);
1646
+ let seg1;
1647
+ let newb1;
1648
+ if (b1a > minb + angleEpsilon) {
1649
+ const [s, n] = cutMeasuredCubicAtProgress(b1, minb, measurer);
1650
+ seg1 = s;
1651
+ newb1 = n;
1652
+ } else {
1653
+ seg1 = b1;
1654
+ newb1 = bs1.cubics[i1++] ?? null;
1655
+ }
1656
+ let seg2;
1657
+ let newb2;
1658
+ if (b2a > minb + angleEpsilon) {
1659
+ const [s, n] = cutMeasuredCubicAtProgress(
1660
+ b2,
1661
+ positiveModulo(doubleMapper.map(minb) - polygon2CutPoint, 1),
1662
+ measurer
1663
+ );
1664
+ seg2 = s;
1665
+ newb2 = n;
1666
+ } else {
1667
+ seg2 = b2;
1668
+ newb2 = bs2.cubics[i2++] ?? null;
1669
+ }
1670
+ ret.push([seg1.cubic, seg2.cubic]);
1671
+ b1 = newb1;
1672
+ b2 = newb2;
1673
+ }
1674
+ return ret;
1675
+ }
1676
+
1677
+ // src/output/clip-path.ts
1678
+ function toClipPathPath(cubics, size = 100) {
1679
+ if (cubics.length === 0) {
1680
+ return 'path("")';
1681
+ }
1682
+ const s = size;
1683
+ let d = `M${(cubics[0].anchor0X * s).toFixed(2)},${(cubics[0].anchor0Y * s).toFixed(2)}`;
1684
+ for (const c of cubics) {
1685
+ d += `C${(c.control0X * s).toFixed(2)},${(c.control0Y * s).toFixed(2)} ${(c.control1X * s).toFixed(2)},${(c.control1Y * s).toFixed(2)} ${(c.anchor1X * s).toFixed(2)},${(c.anchor1Y * s).toFixed(2)}`;
1686
+ }
1687
+ d += "Z";
1688
+ return `path("${d}")`;
1689
+ }
1690
+ function toClipPathPolygon(cubics, samplesPerCubic = 4) {
1691
+ if (cubics.length === 0) {
1692
+ return "polygon(0% 0%)";
1693
+ }
1694
+ const points = [];
1695
+ for (const cubic of cubics) {
1696
+ for (let i = 0; i < samplesPerCubic; i++) {
1697
+ const t = i / samplesPerCubic;
1698
+ const point = cubic.pointOnCurve(t);
1699
+ points.push(
1700
+ `${(point.x * 100).toFixed(2)}% ${(point.y * 100).toFixed(2)}%`
1701
+ );
1702
+ }
1703
+ }
1704
+ return `polygon(${points.join(",")})`;
1705
+ }
1706
+ function toMorphPair(start, end, samplesPerCubic = 4) {
1707
+ const morph = new Morph(start, end);
1708
+ return [
1709
+ toClipPathPolygon(morph.asCubics(0), samplesPerCubic),
1710
+ toClipPathPolygon(morph.asCubics(1), samplesPerCubic)
1711
+ ];
1712
+ }
1713
+
1714
+ // src/output/svg-path.ts
1715
+ function toPathD(cubics, size = 100) {
1716
+ if (cubics.length === 0) {
1717
+ return "";
1718
+ }
1719
+ const s = size;
1720
+ const parts = [
1721
+ `M${(cubics[0].anchor0X * s).toFixed(2)},${(cubics[0].anchor0Y * s).toFixed(2)}`
1722
+ ];
1723
+ for (const c of cubics) {
1724
+ parts.push(
1725
+ `C${(c.control0X * s).toFixed(2)},${(c.control0Y * s).toFixed(2)} ${(c.control1X * s).toFixed(2)},${(c.control1Y * s).toFixed(2)} ${(c.anchor1X * s).toFixed(2)},${(c.anchor1Y * s).toFixed(2)}`
1726
+ );
1727
+ }
1728
+ parts.push("Z");
1729
+ return parts.join("");
1730
+ }
1731
+ function toSvgPath(polygon, size = 100) {
1732
+ return toPathD(polygon.cubics, size);
1733
+ }
1734
+
1735
+ exports.Cubic = Cubic;
1736
+ exports.Morph = Morph;
1737
+ exports.RoundedPolygon = RoundedPolygon;
1738
+ exports.angleEpsilon = angleEpsilon;
1739
+ exports.cornerRounding = cornerRounding;
1740
+ exports.createCircle = createCircle;
1741
+ exports.createPolygon = createPolygon;
1742
+ exports.createPolygonFromVertices = createPolygonFromVertices;
1743
+ exports.createRectangle = createRectangle;
1744
+ exports.createStar = createStar;
1745
+ exports.distanceEpsilon = distanceEpsilon;
1746
+ exports.floatPi = floatPi;
1747
+ exports.getShape = getShape;
1748
+ exports.relaxedDistanceEpsilon = relaxedDistanceEpsilon;
1749
+ exports.shapeNames = shapeNames;
1750
+ exports.toClipPathPath = toClipPathPath;
1751
+ exports.toClipPathPolygon = toClipPathPolygon;
1752
+ exports.toMorphPair = toMorphPair;
1753
+ exports.toPathD = toPathD;
1754
+ exports.toSvgPath = toSvgPath;
1755
+ exports.twoPi = twoPi;
1756
+ exports.unrounded = unrounded;
1757
+ //# sourceMappingURL=index.cjs.map
1758
+ //# sourceMappingURL=index.cjs.map