Skip to content

Commit 9f58e3c

Browse files
committed
add helper to interpolate between matched lists of points
1 parent 3a9f170 commit 9f58e3c

File tree

1 file changed

+77
-18
lines changed

1 file changed

+77
-18
lines changed

animate/index.ts

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// http://www.cad.zju.edu.cn/home/zhx/papers/PoissonMorphing.pdf
22
// https://medium.com/@adrian_cooney/bezier-interpolation-13b68563313a
33
// http://www.iscriptdesign.com/?sketch=tutorial/splitbezier
4+
// http://www.wikiwand.com/en/Hungarian_algorithm
45

56
let ctx: CanvasRenderingContext2D;
67

@@ -15,7 +16,7 @@ const infoSpacing = 20;
1516
const pointSize = 2;
1617
const size = 1000;
1718

18-
interface Coordinates {
19+
interface Coord {
1920
// Horizontal distance towards the right from the left edge of the canvas.
2021
x: number;
2122
// Vertical distance downwards from the top of the canvas.
@@ -61,11 +62,11 @@ const interpolate = (...keyframes: Keyframe[]) => {
6162
// - Output using generator?
6263
};
6364

64-
export const rad = (deg: number) => {
65+
const rad = (deg: number) => {
6566
return (deg / 360) * 2 * Math.PI;
6667
};
6768

68-
export const distance = (a: Coordinates, b: Coordinates): number => {
69+
const distance = (a: Coord, b: Coord): number => {
6970
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
7071
};
7172

@@ -85,14 +86,14 @@ const copyPoint = (p: Point): Point => ({
8586
handleOut: {...p.handleOut},
8687
});
8788

88-
const expandHandle = (origin: Coordinates, handle: Handle): Coordinates => {
89+
const expandHandle = (origin: Coord, handle: Handle): Coord => {
8990
return {
9091
x: origin.x + handle.length * Math.cos(handle.angle),
9192
y: origin.y + handle.length * Math.sin(handle.angle),
9293
};
9394
};
9495

95-
const collapseHandle = (origin: Coordinates, handle: Coordinates): Handle => {
96+
const collapseHandle = (origin: Coord, handle: Coord): Handle => {
9697
const dx = handle.x - origin.x;
9798
const dy = -handle.y + origin.y;
9899
let angle = Math.atan2(dy, dx);
@@ -102,7 +103,7 @@ const collapseHandle = (origin: Coordinates, handle: Coordinates): Handle => {
102103
};
103104
};
104105

105-
const drawLine = (a: Coordinates, b: Coordinates, style: string) => {
106+
const drawLine = (a: Coord, b: Coord, style: string) => {
106107
const backupStrokeStyle = ctx.strokeStyle;
107108
ctx.beginPath();
108109
ctx.moveTo(a.x, a.y);
@@ -112,7 +113,7 @@ const drawLine = (a: Coordinates, b: Coordinates, style: string) => {
112113
ctx.strokeStyle = backupStrokeStyle;
113114
};
114115

115-
const drawPoint = (p: Coordinates, style: string) => {
116+
const drawPoint = (p: Coord, style: string) => {
116117
const backupFillStyle = ctx.fillStyle;
117118
ctx.beginPath();
118119
ctx.arc(p.x, p.y, pointSize, 0, 2 * Math.PI);
@@ -133,10 +134,28 @@ const drawInfo = (() => {
133134
};
134135
})();
135136

136-
const splitLine = (percentage: number, a: Coordinates, b: Coordinates): Coordinates => {
137+
const split = (percentage: number, a: number, b: number): number => {
138+
return a + percentage * (b - a);
139+
};
140+
141+
const splitAngle = (percentage: number, a: number, b: number): number => {
142+
const tau = Math.PI * 2;
143+
let aNorm = ((a % tau) + tau) % tau;
144+
let bNorm = ((b % tau) + tau) % tau;
145+
if (Math.abs(aNorm - bNorm) > Math.PI) {
146+
if (aNorm < bNorm) {
147+
aNorm += tau;
148+
} else {
149+
bNorm += tau;
150+
}
151+
}
152+
return split(percentage, aNorm, bNorm);
153+
};
154+
155+
const splitLine = (percentage: number, a: Coord, b: Coord): Coord => {
137156
return {
138-
x: a.x + percentage * (b.x - a.x),
139-
y: a.y + percentage * (b.y - a.y),
157+
x: split(percentage, a.x, b.x),
158+
y: split(percentage, a.y, b.y),
140159
};
141160
};
142161

@@ -226,7 +245,7 @@ const splitCurveAt = (percentage: number, a: Point, b: Point): [Point, Point, Po
226245
const f = splitLine(percentage, aHandle, bHandle);
227246
const g = splitLine(percentage, cHandle, f);
228247
const h = splitLine(1 - percentage, eHandle, f);
229-
const dCoordinates = splitLine(percentage, g, h);
248+
const dCoord = splitLine(percentage, g, h);
230249

231250
if (debugBezier) {
232251
drawLine(b, bHandle, debugBezierColor);
@@ -236,17 +255,17 @@ const splitCurveAt = (percentage: number, a: Point, b: Point): [Point, Point, Po
236255
drawLine(eHandle, f, debugBezierColor);
237256
drawLine(g, h, debugBezierColor);
238257
if (!debugHandles) {
239-
drawPoint(dCoordinates, debugBezierColor);
240-
drawLine(dCoordinates, g, debugBezierColor);
241-
drawLine(dCoordinates, h, debugBezierColor);
258+
drawPoint(dCoord, debugBezierColor);
259+
drawLine(dCoord, g, debugBezierColor);
260+
drawLine(dCoord, h, debugBezierColor);
242261
}
243262
}
244263

245264
const d: Point = {
246-
x: dCoordinates.x,
247-
y: dCoordinates.y,
248-
handleIn: collapseHandle(dCoordinates, g),
249-
handleOut: collapseHandle(dCoordinates, h),
265+
x: dCoord.x,
266+
y: dCoord.y,
267+
handleIn: collapseHandle(dCoord, g),
268+
handleOut: collapseHandle(dCoord, h),
250269
};
251270
return [c, d, e];
252271
};
@@ -276,6 +295,25 @@ const renderShape = (points: Point[]) => {
276295
}
277296
};
278297

298+
const interpolateBetween = (percentage: number, a: Point[], b: Point[]): Point[] => {
299+
if (a.length !== b.length) throw new Error("shapes have different number of points");
300+
const points: Point[] = [];
301+
for (let i = 0; i < a.length; i++) {
302+
points.push({
303+
...splitLine(percentage, a[i], b[i]),
304+
handleIn: {
305+
angle: splitAngle(percentage, a[i].handleIn.angle, b[i].handleIn.angle),
306+
length: split(percentage, a[i].handleIn.length, b[i].handleIn.length),
307+
},
308+
handleOut: {
309+
angle: splitAngle(percentage, a[i].handleOut.angle, b[i].handleOut.angle),
310+
length: split(percentage, a[i].handleOut.length, b[i].handleOut.length),
311+
},
312+
});
313+
}
314+
return points;
315+
};
316+
279317
const testSplitAt = (percentage: number) => {
280318
let points: Point[] = [
281319
point(0.15, 0.15, 135, 0.1, 315, 0.2),
@@ -330,6 +368,26 @@ const testDivideShape = () => {
330368
}
331369
};
332370

371+
const testInterpolateBetween = (percentage: number) => {
372+
const a = [
373+
point(0.65, 0.72, 135, 0.05, -45, 0.05),
374+
point(0.75, 0.72, -135, 0.05, 45, 0.05),
375+
point(0.75, 0.82, -45, 0.05, 135, 0.05),
376+
point(0.65, 0.82, 45, 0.05, 225, 0.05),
377+
];
378+
const b = [
379+
point(0.7, 0.72, 180, 0, 0, 0),
380+
point(0.75, 0.77, -90, 0, 90, 0),
381+
point(0.7, 0.82, 360 * 10, 0, 180, 0),
382+
point(0.65, 0.77, 90, 0, -90, 0),
383+
];
384+
if (percentage < 0.5) {
385+
renderShape(interpolateBetween(2 * percentage, a, b));
386+
} else {
387+
renderShape(interpolateBetween(2 * percentage - 1, b, a));
388+
}
389+
};
390+
333391
(() => {
334392
const canvas = document.createElement("canvas");
335393
canvas.width = size;
@@ -347,6 +405,7 @@ const testDivideShape = () => {
347405
testSplitAt(percentage);
348406
testSplitBy();
349407
testDivideShape();
408+
testInterpolateBetween(percentage);
350409
percentage += animationSpeed / 1000;
351410
percentage %= 1;
352411
if (animationSpeed > 0) requestAnimationFrame(renderFrame);

0 commit comments

Comments
 (0)