react-native-molecules 0.5.0-beta.32 → 0.5.0-beta.34

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
- function splitAll(cmds: Cmd[]): Cmd[] {
110
- const out: Cmd[] = [];
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 [l, ri] = splitCubic(cx, cy, cmd.a);
120
- out.push({ t: 'C', a: l }, { t: 'C', a: ri });
121
- cx = cmd.a[4];
122
- cy = cmd.a[5];
123
- } else {
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
- function splitN(cmds: Cmd[], n: number): Cmd[] {
131
- const total = cmds.filter(c => c.t === 'C').length;
132
- const stride = total / n;
133
- const toSplit = new Set<number>();
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
- function expandToCount(cmds: Cmd[], target: number): Cmd[] {
164
- let cur = cmds;
165
- while (true) {
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 max = Math.max(...allCubic.map(c => c.filter(x => x.t === 'C').length));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-molecules",
3
- "version": "0.5.0-beta.32",
3
+ "version": "0.5.0-beta.34",
4
4
  "author": "Thet Aung <thetaung.dev@gmail.com>",
5
5
  "license": "MIT",
6
6
  "main": "index.ts",