Skip to content

Commit 272c9f5

Browse files
committed
add interpolation func that smoothes on every frame
1 parent 101fabb commit 272c9f5

File tree

2 files changed

+62
-18
lines changed

2 files changed

+62
-18
lines changed

internal/animate/interpolate.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {Shape} from "../types";
2-
import {split, splitLine, mod} from "../util";
3-
4-
// OPT? loop
5-
// TODO smooth between
2+
import {split, splitLine, mod, smooth, mapShape} from "../util";
63

4+
// Interpolates between angles a and b. Angles are normalized to avoid unnecessary rotation.
5+
// Direction is chosen to produce the smallest possible movement.
76
const interpolateAngle = (percentage: number, a: number, b: number): number => {
87
const tau = Math.PI * 2;
98
let aNorm = mod(a, tau);
@@ -18,9 +17,16 @@ const interpolateAngle = (percentage: number, a: number, b: number): number => {
1817
return split(percentage, aNorm, bNorm);
1918
};
2019

21-
const interpolateBetween = (percentage: number, a: Shape, b: Shape): Shape => {
20+
// Interpolates linearly between shapes a and b. Can only interpolate between shapes that have the
21+
// same number of points. Easing effects can be applied to the percentage given to this function.
22+
// Percentages outside the 0-1 range are supported.
23+
export const interpolateBetween = (percentage: number, a: Shape, b: Shape): Shape => {
2224
if (a.length !== b.length) throw new Error("shapes have different number of points");
25+
26+
// Clamped range for use in values that could look incorrect otherwise.
27+
// ex. Handles that invert if their value goes negative (creates loops at corners).
2328
const clamped = Math.min(1, Math.max(0, percentage));
29+
2430
const shape: Shape = [];
2531
for (let i = 0; i < a.length; i++) {
2632
shape.push({
@@ -38,10 +44,20 @@ const interpolateBetween = (percentage: number, a: Shape, b: Shape): Shape => {
3844
return shape;
3945
};
4046

41-
export const interpolateBetweenLoop = (percentage: number, a: Shape, b: Shape): Shape => {
42-
if (percentage < 0.5) {
43-
return interpolateBetween(2 * percentage, a, b);
44-
} else {
45-
return interpolateBetween(-2 * percentage + 2, a, b);
46-
}
47+
// OPT smooth strength
48+
// Interpolates between shapes a and b while applying a smoothing effect. Smoothing effect's
49+
// strength is relative to how far away the percentage is from either 0 or 1. It is strongest in the
50+
// middle of the animation (percentage = 0.5) or when bounds are exceeded (percentage = 1.8).
51+
export const interpolateBetweenSmooth = (percentage: number, a: Shape, b: Shape): Shape => {
52+
const strength = Math.min(1, Math.min(Math.abs(0 - percentage), Math.abs(1 - percentage)));
53+
const interpolated = interpolateBetween(percentage, a, b);
54+
const smoothed = smooth(interpolated, strength);
55+
return mapShape(interpolated, ({index, curr}) => {
56+
const sp = smoothed[index];
57+
curr.handleIn.angle = interpolateAngle(strength, curr.handleIn.angle, sp.handleIn.angle);
58+
curr.handleIn.length = split(strength, curr.handleIn.length, sp.handleIn.length);
59+
curr.handleOut.angle = interpolateAngle(strength, curr.handleOut.angle, sp.handleOut.angle);
60+
curr.handleOut.length = split(strength, curr.handleOut.length, sp.handleOut.length);
61+
return curr;
62+
});
4763
};

internal/animate/testing/script.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {interpolateBetweenLoop} from "../interpolate";
1+
import {interpolateBetweenSmooth} from "../interpolate";
22
import {divideShape, prepShapes} from "../prepare";
33
import {Coord, Point, Shape} from "../../types";
4-
import {length, insertAt, insertCount, rad, mod, mapShape, forShape} from "../../util";
4+
import {length, insertAt, insertCount, rad, mod, mapShape, forShape, smooth} from "../../util";
55
import {clear, drawInfo, drawShape} from "../../render/canvas";
66
import {genBlob} from "../../blobs";
77
import {rand} from "../../rand";
@@ -101,13 +101,13 @@ const testInterpolateBetween = (percentage: number) => {
101101
point(0.35, 0.82, 360 * 10, 0, 180, 0),
102102
point(0.3, 0.77, 90, 0, -90, 0),
103103
];
104-
drawShape(ctx, debug, interpolateBetweenLoop(percentage, a, b));
104+
drawShape(ctx, debug, loopBetween(percentage, a, b));
105105
};
106106

107107
const testPrepShapesA = (percentage: number) => {
108108
const a = blob("a", 6, 0.15, {x: 0.45, y: 0.1});
109109
const b = blob("b", 10, 0.15, {x: 0.45, y: 0.1});
110-
drawShape(ctx, debug, interpolateBetweenLoop(percentage, ...prepShapes(a, b)));
110+
drawShape(ctx, debug, loopBetween(percentage, ...prepShapes(a, b)));
111111
};
112112

113113
const testPrepShapesB = (percentage: number) => {
@@ -118,7 +118,7 @@ const testPrepShapesB = (percentage: number) => {
118118
point(0.6, 0.4, 0, 0, 0, 0),
119119
point(0.45, 0.4, 0, 0, 0, 0),
120120
];
121-
drawShape(ctx, debug, interpolateBetweenLoop(percentage, ...prepShapes(a, b)));
121+
drawShape(ctx, debug, loopBetween(percentage, ...prepShapes(a, b)));
122122
};
123123

124124
const testPrepShapesC = (percentage: number) => {
@@ -137,7 +137,7 @@ const testPrepShapesC = (percentage: number) => {
137137
point(0.45, 0.5, 0, 0, 0, 0),
138138
point(0.5, 0.5, 0, 0, 0, 0),
139139
];
140-
drawShape(ctx, debug, interpolateBetweenLoop(percentage, ...prepShapes(b, a)));
140+
drawShape(ctx, debug, loopBetween(percentage, ...prepShapes(b, a)));
141141
};
142142

143143
const testPrepShapesD = (percentage: number) => {
@@ -147,7 +147,26 @@ const testPrepShapesD = (percentage: number) => {
147147
point(0.525, 0.725, 0, 0, 0, 0),
148148
point(0.525, 0.725, 0, 0, 0, 0),
149149
];
150-
drawShape(ctx, debug, interpolateBetweenLoop(percentage, ...prepShapes(a, b)));
150+
drawShape(ctx, debug, loopBetween(percentage, ...prepShapes(a, b)));
151+
};
152+
153+
const testPrepLetters = (percentage: number) => {
154+
const a: Shape = [
155+
point(0.65, 0.2, 0, 0, 0, 0),
156+
point(0.85, 0.2, 0, 0, 0, 0),
157+
point(0.85, 0.25, 0, 0, 0, 0),
158+
point(0.7, 0.25, 0, 0, 0, 0),
159+
point(0.7, 0.4, 0, 0, 0, 0),
160+
point(0.8, 0.4, 0, 0, 0, 0),
161+
point(0.8, 0.35, 0, 0, 0, 0),
162+
point(0.75, 0.35, 0, 0, 0, 0),
163+
point(0.75, 0.3, 0, 0, 0, 0),
164+
point(0.85, 0.3, 0, 0, 0, 0),
165+
point(0.85, 0.45, 0, 0, 0, 0),
166+
point(0.65, 0.45, 0, 0, 0, 0),
167+
];
168+
const b: Shape = blob("lettersa", 8, 0.25, {x: 0.65, y: 0.2});
169+
drawShape(ctx, debug, loopBetween(percentage, ...prepShapes(a, b)));
151170
};
152171

153172
const blob = (seed: string, count: number, scale: number, offset: Coord): Shape => {
@@ -164,6 +183,14 @@ const blob = (seed: string, count: number, scale: number, offset: Coord): Shape
164183
});
165184
};
166185

186+
const loopBetween = (percentage: number, a: Shape, b: Shape): Shape => {
187+
if (percentage < 0.5) {
188+
return interpolateBetweenSmooth(2 * percentage, a, b);
189+
} else {
190+
return interpolateBetweenSmooth(-2 * percentage + 2, a, b);
191+
}
192+
};
193+
167194
(() => {
168195
let percentage = animationStart;
169196

@@ -179,6 +206,7 @@ const blob = (seed: string, count: number, scale: number, offset: Coord): Shape
179206
testPrepShapesB(percentage);
180207
testPrepShapesC(percentage);
181208
testPrepShapesD(percentage);
209+
testPrepLetters(percentage);
182210

183211
percentage += animationSpeed / 1000;
184212
percentage = mod(percentage, 1);

0 commit comments

Comments
 (0)