| 302 | } |
| 303 | |
| 304 | function buildSmoothCurve(/** @type {{ x: number, y: number }[]} **/ samples) { |
| 305 | const get = (/** @type {number} **/ index) => { |
| 306 | if (index < 0) { |
| 307 | // Reflect first segment to derive a virtual point with matching tangent |
| 308 | const a = samples[0]; |
| 309 | const b = samples[1]; |
| 310 | return { x: 2 * a.x - b.x, y: 2 * a.y - b.y }; |
| 311 | } |
| 312 | if (index >= samples.length) { |
| 313 | const a = samples[samples.length - 1]; |
| 314 | const b = samples[samples.length - 2]; |
| 315 | return { x: 2 * a.x - b.x, y: 2 * a.y - b.y }; |
| 316 | } |
| 317 | return samples[index]; |
| 318 | }; |
| 319 | |
| 320 | // Catmull-Rom-to-cubic-Bezier across the sample chain for a smooth surface curve |
| 321 | return samples |
| 322 | .slice(0, -1) |
| 323 | .map((p1, i) => { |
| 324 | const p0 = get(i - 1); |
| 325 | const p2 = samples[i + 1]; |
| 326 | const p3 = get(i + 2); |
| 327 | |
| 328 | const cp1x = p1.x + (p2.x - p0.x) / 6; |
| 329 | const cp1y = p1.y + (p2.y - p0.y) / 6; |
| 330 | const cp2x = p2.x - (p3.x - p1.x) / 6; |
| 331 | const cp2y = p2.y - (p3.y - p1.y) / 6; |
| 332 | |
| 333 | return `C ${cp1x.toFixed(2)},${cp1y.toFixed(2)} ${cp2x.toFixed(2)},${cp2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)} `; |
| 334 | }) |
| 335 | .join(""); |
| 336 | } |