react-native-molecules 0.5.0-beta.32 → 0.5.0-beta.33
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.
|
@@ -106,68 +106,62 @@ function splitCubic(p0x: number, p0y: number, a: number[]): [number[], number[]]
|
|
|
106
106
|
];
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// Angular span of a bezier endpoint-to-endpoint from center (cx, cy).
|
|
110
|
+
// Returns the shorter arc [0, π] so no single segment exceeds a half-circle.
|
|
111
|
+
function angularSpan(x0: number, y0: number, x1: number, y1: number, cx = 19, cy = 19): number {
|
|
112
|
+
const a0 = Math.atan2(y0 - cy, x0 - cx);
|
|
113
|
+
const a1 = Math.atan2(y1 - cy, x1 - cx);
|
|
114
|
+
let diff = a1 - a0;
|
|
115
|
+
while (diff > Math.PI) diff -= 2 * Math.PI;
|
|
116
|
+
while (diff < -Math.PI) diff += 2 * Math.PI;
|
|
117
|
+
return Math.abs(diff);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Expand a path to `target` C commands by always splitting the segment with the
|
|
121
|
+
// largest angular span. This keeps the angular distribution approximately uniform
|
|
122
|
+
// so that command i in any two shapes always corresponds to the same arc around
|
|
123
|
+
// the center — the key requirement for smooth SMIL/worklet morphing.
|
|
124
|
+
function expandToCount(cmds: Cmd[], target: number): Cmd[] {
|
|
125
|
+
type Seg = { x0: number; y0: number; cmd: Cmd; span: number };
|
|
126
|
+
|
|
111
127
|
let cx = 0,
|
|
112
128
|
cy = 0;
|
|
129
|
+
const segs: Seg[] = [];
|
|
113
130
|
for (const cmd of cmds) {
|
|
114
131
|
if (cmd.t === 'M') {
|
|
115
132
|
cx = cmd.a[0];
|
|
116
133
|
cy = cmd.a[1];
|
|
117
|
-
out.push(cmd);
|
|
118
134
|
} else if (cmd.t === 'C') {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
cx
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
out.push(cmd);
|
|
135
|
+
const x3 = cmd.a[4],
|
|
136
|
+
y3 = cmd.a[5];
|
|
137
|
+
segs.push({ x0: cx, y0: cy, cmd, span: angularSpan(cx, cy, x3, y3) });
|
|
138
|
+
cx = x3;
|
|
139
|
+
cy = y3;
|
|
125
140
|
}
|
|
126
141
|
}
|
|
127
|
-
return out;
|
|
128
|
-
}
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
for (let i = 0; i < n; i++) {
|
|
135
|
-
toSplit.add(Math.round(i * stride));
|
|
136
|
-
}
|
|
137
|
-
const out: Cmd[] = [];
|
|
138
|
-
let cx = 0,
|
|
139
|
-
cy = 0,
|
|
140
|
-
idx = 0;
|
|
141
|
-
for (const cmd of cmds) {
|
|
142
|
-
if (cmd.t === 'M') {
|
|
143
|
-
cx = cmd.a[0];
|
|
144
|
-
cy = cmd.a[1];
|
|
145
|
-
out.push(cmd);
|
|
146
|
-
} else if (cmd.t === 'C') {
|
|
147
|
-
if (toSplit.has(idx)) {
|
|
148
|
-
const [l, ri] = splitCubic(cx, cy, cmd.a);
|
|
149
|
-
out.push({ t: 'C', a: l }, { t: 'C', a: ri });
|
|
150
|
-
} else {
|
|
151
|
-
out.push(cmd);
|
|
152
|
-
}
|
|
153
|
-
cx = cmd.a[4];
|
|
154
|
-
cy = cmd.a[5];
|
|
155
|
-
idx++;
|
|
156
|
-
} else {
|
|
157
|
-
out.push(cmd);
|
|
143
|
+
while (segs.length < target) {
|
|
144
|
+
let maxIdx = 0;
|
|
145
|
+
for (let i = 1; i < segs.length; i++) {
|
|
146
|
+
if (segs[i].span > segs[maxIdx].span) maxIdx = i;
|
|
158
147
|
}
|
|
148
|
+
const { x0, y0, cmd } = segs[maxIdx];
|
|
149
|
+
const [left, right] = splitCubic(x0, y0, cmd.a);
|
|
150
|
+
const mx = left[4],
|
|
151
|
+
my = left[5];
|
|
152
|
+
const ex = cmd.a[4],
|
|
153
|
+
ey = cmd.a[5];
|
|
154
|
+
segs.splice(
|
|
155
|
+
maxIdx,
|
|
156
|
+
1,
|
|
157
|
+
{ x0, y0, cmd: { t: 'C', a: left }, span: angularSpan(x0, y0, mx, my) },
|
|
158
|
+
{ x0: mx, y0: my, cmd: { t: 'C', a: right }, span: angularSpan(mx, my, ex, ey) },
|
|
159
|
+
);
|
|
159
160
|
}
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const count = cur.filter(c => c.t === 'C').length;
|
|
167
|
-
if (count >= target) return cur;
|
|
168
|
-
const needed = target - count;
|
|
169
|
-
cur = needed >= count ? splitAll(cur) : splitN(cur, needed);
|
|
170
|
-
}
|
|
162
|
+
const M = cmds.find(c => c.t === 'M')!;
|
|
163
|
+
const Z = cmds.find(c => c.t === 'Z');
|
|
164
|
+
return [M, ...segs.map(s => s.cmd), ...(Z ? [Z] : [])];
|
|
171
165
|
}
|
|
172
166
|
|
|
173
167
|
function serialize(cmds: Cmd[]): string {
|
|
@@ -212,7 +206,12 @@ function alignToAngle(cmds: Cmd[], targetAngle: number, cx = 19, cy = 19): Cmd[]
|
|
|
212
206
|
*/
|
|
213
207
|
export function normalizePaths(paths: string[]): string[] {
|
|
214
208
|
const allCubic = paths.map(p => lToC(tokenize(p)));
|
|
215
|
-
const
|
|
209
|
+
const naturalMax = Math.max(...allCubic.map(c => c.filter(x => x.t === 'C').length));
|
|
210
|
+
// Expand to 3× the natural maximum so each original segment is subdivided at least 3×.
|
|
211
|
+
// At lower targets (e.g. 16), greedy bisection leaves 2 cookie9 segments at 40° while
|
|
212
|
+
// the rest are at 20° — a 17.5° angular mismatch with softBurst's 22.5° commands that
|
|
213
|
+
// produces visible unevenness. At 3× (48), the mismatch shrinks to ~2.5°.
|
|
214
|
+
const max = naturalMax * 3;
|
|
216
215
|
const expanded = allCubic.map(c => expandToCount(c, max));
|
|
217
216
|
return expanded.map(c => serialize(alignToAngle(c, -Math.PI / 2)));
|
|
218
217
|
}
|