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