Skip to content

Commit f89fff5

Browse files
committed
add shape iteration helpers
1 parent ccb7c53 commit f89fff5

File tree

7 files changed

+128
-122
lines changed

7 files changed

+128
-122
lines changed

internal/animate/prepare.ts

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
copyPoint,
32
length,
43
reverse,
54
shift,
@@ -8,6 +7,8 @@ import {
87
mod,
98
angleOf,
109
coordEqual,
10+
mapShape,
11+
forShape,
1112
} from "../util";
1213
import {Point, Shape} from "../types";
1314

@@ -40,21 +41,21 @@ const optimizeOrder = (a: Shape, b: Shape): Shape => {
4041
};
4142

4243
// OPT allow extra division
43-
export const divideShape = (count: number, points: Shape): Shape => {
44-
if (points.length < 3) throw new Error("not enough points");
45-
if (count < points.length) throw new Error("cannot remove points");
46-
if (count === points.length) return points.slice();
44+
export const divideShape = (count: number, shape: Shape): Shape => {
45+
if (shape.length < 3) throw new Error("not enough points");
46+
if (count < shape.length) throw new Error("cannot remove points");
47+
if (count === shape.length) return shape.slice();
4748

48-
const lengths = [];
49-
for (let i = 0; i < points.length; i++) {
50-
lengths.push(length(points[i], points[mod(i + 1, points.length)]));
51-
}
49+
const lengths: number[] = [];
50+
forShape(shape, ({curr, next}) => {
51+
lengths.push(length(curr, next()));
52+
});
5253

53-
const divisors = divideLengths(lengths, count - points.length);
54+
const divisors = divideLengths(lengths, count - shape.length);
5455
const out: Shape = [];
55-
for (let i = 0; i < points.length; i++) {
56-
const curr: Point = out[out.length - 1] || points[i];
57-
const next = points[mod(i + 1, points.length)];
56+
for (let i = 0; i < shape.length; i++) {
57+
const curr: Point = out[out.length - 1] || shape[i];
58+
const next = shape[mod(i + 1, shape.length)];
5859
out.pop();
5960
out.push(...insertCount(divisors[i], curr, next));
6061
}
@@ -66,39 +67,29 @@ export const divideShape = (count: number, points: Shape): Shape => {
6667

6768
// OPT disable
6869
const fixAnglesWith = (fixee: Shape, fixer: Shape): Shape => {
69-
const out: Shape = [];
70-
for (let i = 0; i < fixee.length; i++) {
71-
const before = fixee[mod(i - 1, fixee.length)];
72-
const after = fixee[mod(i + 1, fixee.length)];
73-
const point = copyPoint(fixee[i]);
74-
if (point.handleIn.length === 0 && coordEqual(before, point)) {
75-
point.handleIn.angle = fixer[i].handleIn.angle;
70+
return mapShape(fixee, ({index, curr, prev, next}) => {
71+
if (curr.handleIn.length === 0 && coordEqual(prev(), curr)) {
72+
curr.handleIn.angle = fixer[index].handleIn.angle;
7673
}
77-
if (point.handleOut.length === 0 && coordEqual(after, point)) {
78-
point.handleOut.angle = fixer[i].handleOut.angle;
74+
if (curr.handleOut.length === 0 && coordEqual(next(), curr)) {
75+
curr.handleOut.angle = fixer[index].handleOut.angle;
7976
}
80-
out.push(point);
81-
}
82-
return out;
77+
return curr;
78+
});
8379
};
8480

8581
// OPT disable
8682
const fixAnglesSelf = (shape: Shape): Shape => {
87-
const out: Shape = [];
88-
for (let i = 0; i < shape.length; i++) {
89-
const before = shape[mod(i - 1, shape.length)];
90-
const after = shape[mod(i + 1, shape.length)];
91-
const angle = angleOf(before, after);
92-
const point = copyPoint(shape[i]);
93-
if (point.handleIn.length === 0) {
94-
point.handleIn.angle = angle + Math.PI;
83+
return mapShape(shape, ({curr, prev, next}) => {
84+
const angle = angleOf(prev(), next());
85+
if (curr.handleIn.length === 0) {
86+
curr.handleIn.angle = angle + Math.PI;
9587
}
96-
if (point.handleOut.length === 0) {
97-
point.handleOut.angle = angle;
88+
if (curr.handleOut.length === 0) {
89+
curr.handleOut.angle = angle;
9890
}
99-
out.push(point);
100-
}
101-
return out;
91+
return curr;
92+
});
10293
};
10394

10495
const divideLengths = (lengths: number[], add: number): number[] => {
@@ -124,9 +115,9 @@ const divideLengths = (lengths: number[], add: number): number[] => {
124115
};
125116

126117
export const prepShapes = (a: Shape, b: Shape): [Shape, Shape] => {
127-
const points = Math.max(a.length, b.length);
128-
const aNorm = divideShape(points, a);
129-
const bNorm = divideShape(points, b);
118+
const pointCount = Math.max(a.length, b.length);
119+
const aNorm = divideShape(pointCount, a);
120+
const bNorm = divideShape(pointCount, b);
130121
const bOpt = optimizeOrder(aNorm, bNorm);
131122
return [fixAnglesWith(fixAnglesSelf(aNorm), bNorm), fixAnglesWith(fixAnglesSelf(bOpt), aNorm)];
132123
};

internal/animate/testing/script.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {interpolateBetweenLoop} from "../interpolate";
22
import {divideShape, prepShapes} from "../prepare";
33
import {Coord, Point, Shape} from "../../types";
4-
import {length, insertAt, insertCount, rad, mod} from "../../util";
4+
import {length, insertAt, insertCount, rad, mod, mapShape, forShape} from "../../util";
55
import {clear, drawInfo, drawShape} from "../../render/canvas";
66
import {genBlob} from "../../blobs";
77
import {rand} from "../../rand";
@@ -33,31 +33,29 @@ const point = (x: number, y: number, ia: number, il: number, oa: number, ol: num
3333
};
3434

3535
const testSplitAt = (percentage: number) => {
36-
let points: Shape = [
36+
let shape: Shape = [
3737
point(0.15, 0.15, 135, 0.1, 315, 0.2),
3838
point(0.85, 0.15, 225, 0.1, 45, 0.2),
3939
point(0.85, 0.85, 315, 0.1, 135, 0.2),
4040
point(0.15, 0.85, 45, 0.1, 225, 0.2),
4141
];
4242

43-
const count = points.length;
43+
const count = shape.length;
4444
const stop = 2 * count - 1;
4545
for (let i = 0; i < count; i++) {
4646
const double = i * 2;
4747
const next = mod(double + 1, stop);
48-
points.splice(double, 2, ...insertAt(percentage, points[double], points[next]));
48+
shape.splice(double, 2, ...insertAt(percentage, shape[double], shape[next]));
4949
}
50-
points.splice(0, 1);
50+
shape.splice(0, 1);
5151

5252
let sum = 0;
53-
for (let i = 0; i < points.length; i++) {
54-
const curr = points[i];
55-
const next = points[mod(i + 1, points.length)];
56-
sum += length(curr, next);
57-
}
53+
forShape(shape, ({curr, next}) => {
54+
sum += length(curr, next());
55+
});
5856
drawInfo(ctx, 1, "split at lengths sum", sum);
5957

60-
drawShape(ctx, debug, points);
58+
drawShape(ctx, debug, shape);
6159
};
6260

6361
const testSplitBy = () => {
@@ -155,15 +153,15 @@ const testPrepShapesD = (percentage: number) => {
155153
const blob = (seed: string, count: number, scale: number, offset: Coord): Shape => {
156154
const rgen = rand(seed);
157155
const shape = genBlob(count, () => 0.3 + 0.2 * rgen());
158-
for (let i = 0; i < shape.length; i++) {
159-
shape[i].x *= scale * size;
160-
shape[i].y *= scale * size;
161-
shape[i].x += offset.x * size;
162-
shape[i].y += offset.y * size;
163-
shape[i].handleIn.length *= scale * size;
164-
shape[i].handleOut.length *= scale * size;
165-
}
166-
return shape;
156+
return mapShape(shape, ({curr}) => {
157+
curr.x *= scale * size;
158+
curr.y *= scale * size;
159+
curr.x += offset.x * size;
160+
curr.y += offset.y * size;
161+
curr.handleIn.length *= scale * size;
162+
curr.handleOut.length *= scale * size;
163+
return curr;
164+
});
167165
};
168166

169167
(() => {

internal/blobs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {Shape} from "./types";
22
import {smooth} from "../internal/util";
33

4-
export const genBlob = (points: number, offset: () => number): Shape => {
5-
const angle = (Math.PI * 2) / points;
4+
export const genBlob = (pointCount: number, offset: () => number): Shape => {
5+
const angle = (Math.PI * 2) / pointCount;
66
const boundingSize = 1;
77
const boundingCenter = boundingSize / 2;
88

99
const shape: Shape = [];
10-
for (let i = 0; i < points; i++) {
10+
for (let i = 0; i < pointCount; i++) {
1111
const randPointOffset = offset();
1212
const pointX = Math.sin(i * angle);
1313
const pointY = Math.cos(i * angle);

internal/render/canvas.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Coord, Shape} from "../types";
2-
import {expandHandle, mod} from "../util";
2+
import {expandHandle, forShape} from "../util";
33

44
const pointSize = 2;
55
const infoSpacing = 20;
@@ -35,10 +35,10 @@ const drawPoint = (ctx: CanvasRenderingContext2D, p: Coord, style: string) => {
3535
export const drawShape = (ctx: CanvasRenderingContext2D, debug: boolean, shape: Shape) => {
3636
if (shape.length < 2) throw new Error("not enough points");
3737

38-
for (let i = 0; i < shape.length; i++) {
38+
forShape(shape, ({curr, next: getNext}) => {
39+
const next = getNext();
40+
3941
// Compute coordinates of handles.
40-
const curr = shape[i];
41-
const next = shape[mod(i + 1, shape.length)];
4242
const currHandle = expandHandle(curr, curr.handleOut);
4343
const nextHandle = expandHandle(next, next.handleIn);
4444

@@ -53,5 +53,5 @@ export const drawShape = (ctx: CanvasRenderingContext2D, debug: boolean, shape:
5353
ctx.moveTo(curr.x, curr.y);
5454
ctx.bezierCurveTo(currHandle.x, currHandle.y, nextHandle.x, nextHandle.y, next.x, next.y);
5555
ctx.stroke();
56-
}
56+
});
5757
};

internal/render/svg.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {xml, XmlElement} from "../../editable";
22
import {Shape} from "../types";
3-
import {expandHandle, mod} from "../util";
3+
import {expandHandle, forShape} from "../util";
44

55
export interface RenderOptions {
66
// Viewport size.
@@ -24,20 +24,19 @@ export interface RenderOptions {
2424
boundingBox?: boolean;
2525
}
2626

27-
// OPT render path only
27+
// TODO render path only
2828

2929
// Renders a shape made up of the input points to an editable data structure
3030
// which can be rendered to svg.
31-
export const renderEditable = (points: Shape, opt: RenderOptions): XmlElement => {
31+
export const renderEditable = (shape: Shape, opt: RenderOptions): XmlElement => {
3232
// Render path data attribute from points and handles.
33-
let path = `M${points[0].x},${points[0].y}`;
34-
for (let i = 0; i < points.length; i++) {
35-
const curr = points[i];
36-
const next = points[mod(i + 1, points.length)];
33+
let path = `M${shape[0].x},${shape[0].y}`;
34+
forShape(shape, ({curr, next: getNext}) => {
35+
const next = getNext();
3736
const currControl = expandHandle(curr, curr.handleOut);
3837
const nextControl = expandHandle(next, next.handleIn);
3938
path += `C${currControl.x},${currControl.y},${nextControl.x},${nextControl.y},${next.x},${next.y}`;
40-
}
39+
});
4140

4241
const stroke = opt.stroke || (opt.guides ? "black" : "none");
4342
const strokeWidth = opt.strokeWidth || (opt.guides ? 1 : 0);
@@ -80,9 +79,8 @@ export const renderEditable = (points: Shape, opt: RenderOptions): XmlElement =>
8079
}
8180

8281
// Points and handles.
83-
for (let i = 0; i < points.length; i++) {
84-
const curr = points[i];
85-
const next = points[mod(i + 1, points.length)];
82+
forShape(shape, ({curr, next: getNext}) => {
83+
const next = getNext();
8684
const currControl = expandHandle(curr, curr.handleOut);
8785
const nextControl = expandHandle(next, next.handleIn);
8886

@@ -126,7 +124,7 @@ export const renderEditable = (points: Shape, opt: RenderOptions): XmlElement =>
126124
xmlContentGroup.children.push(xmlOutgoingHandleCircle);
127125
xmlContentGroup.children.push(xmlIncomingHandleCircle);
128126
xmlContentGroup.children.push(xmlPointCircle);
129-
}
127+
});
130128
}
131129

132130
return xmlRoot;

0 commit comments

Comments
 (0)