Skip to content

Commit b2732c1

Browse files
committed
refactor internals into more focused modules
1 parent eca81f1 commit b2732c1

File tree

10 files changed

+206
-199
lines changed

10 files changed

+206
-199
lines changed

index.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,38 @@
11
// https://www.blobmaker.app/
2-
// https://math.stackexchange.com/questions/873224/calculate-control-points-of-cubic-bezier-curve-approximating-a-part-of-a-circle
32

4-
import {rad, smooth, rand} from "./util";
5-
import {render} from "./render";
6-
import {Point, BlobOptions} from "./types";
3+
import {rand} from "./internal/math/rand";
4+
import {Point} from "./internal/math/geometry";
5+
import {rad} from "./internal/math/unit";
6+
import {smooth} from "./internal/svg/smooth";
7+
import {render} from "./internal/svg/render";
78

8-
export {BlobOptions} from "./types";
9+
export interface BlobOptions {
10+
// Bounding box dimensions.
11+
size: number;
12+
13+
// Shape complexity.
14+
complexity: number;
15+
16+
// Shape contrast.
17+
contrast: number;
18+
19+
// Fill color.
20+
color?: string;
21+
22+
stroke?: {
23+
// Stroke color.
24+
color: string;
25+
26+
// Stroke width.
27+
width: number;
28+
};
29+
30+
// Value to seed random number generator.
31+
seed?: string;
32+
33+
// Render points, handles and stroke.
34+
guides?: boolean;
35+
}
936

1037
// Generates a random rounded shape.
1138
const blobs = (opt: BlobOptions): string => {

internal/math/geometry.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {deg} from "./unit";
2+
3+
export interface Point {
4+
x: number;
5+
y: number;
6+
}
7+
8+
// Calculates distance between two points.
9+
export const distance = (p1: Point, p2: Point): number => {
10+
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
11+
};
12+
13+
// Calculates the angle of the line from p1 to p2 in degrees.
14+
export const angle = (p1: Point, p2: Point): number => {
15+
return deg(Math.atan2(p2.y - p1.y, p2.x - p1.x));
16+
};

internal/math/rand.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Seeded random number generator.
2+
// https://stackoverflow.com/a/47593316/3053361
3+
export const rand = (seed: string) => {
4+
const xfnv1a = (str: string) => {
5+
let h = 2166136261 >>> 0;
6+
for (let i = 0; i < str.length; i++) {
7+
h = Math.imul(h ^ str.charCodeAt(i), 16777619);
8+
}
9+
return () => {
10+
h += h << 13;
11+
h ^= h >>> 7;
12+
h += h << 3;
13+
h ^= h >>> 17;
14+
return (h += h << 5) >>> 0;
15+
};
16+
};
17+
18+
const sfc32 = (a: number, b: number, c: number, d: number) => () => {
19+
a >>>= 0;
20+
b >>>= 0;
21+
c >>>= 0;
22+
d >>>= 0;
23+
var t = (a + b) | 0;
24+
a = b ^ (b >>> 9);
25+
b = (c + (c << 3)) | 0;
26+
c = (c << 21) | (c >>> 11);
27+
d = (d + 1) | 0;
28+
t = (t + d) | 0;
29+
c = (c + t) | 0;
30+
return (t >>> 0) / 4294967296;
31+
};
32+
33+
const seedGenerator = xfnv1a(seed);
34+
return sfc32(seedGenerator(), seedGenerator(), seedGenerator(), seedGenerator());
35+
};

internal/math/unit.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Converts degrees to radians.
2+
export const rad = (deg: number) => {
3+
return (deg / 360) * 2 * Math.PI;
4+
};
5+
6+
// Converts radians to degrees.
7+
export const deg = (rad: number) => {
8+
return (((rad / Math.PI) * 1) / 2) * 360;
9+
};

internal/svg/point.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {rad} from "../math/unit";
2+
3+
export interface Point {
4+
// Cartesian coordinates (starting at [0,0] in the bottom left).
5+
x: number;
6+
y: number;
7+
8+
// Optional cubic bezier handle configuration.
9+
handles?: {
10+
// Direction of the outgoing path in degrees. Value is relative to the 3:00 position
11+
// on a clock and the positive direction is counter-clockwise.
12+
angle: number;
13+
14+
// Distance between each handle and the point.
15+
out: number;
16+
in: number;
17+
};
18+
}
19+
20+
export interface SVGPoint {
21+
// Coordinates of the point in the SVG viewport.
22+
x: number;
23+
y: number;
24+
25+
// Cubic bezier handle configuration.
26+
handles: {
27+
// Direction of the outgoing path in radians. Value is relative to the 9:00 position
28+
// on a clock and the positive direction is counter-clockwise.
29+
angle: number;
30+
31+
// Distance between each handle and the point.
32+
out: number;
33+
in: number;
34+
};
35+
}
36+
37+
// Translates a point's [x,y] cartesian coordinates into values relative to the viewport.
38+
// Translates the angle from degrees to radians and moves the start angle a half rotation.
39+
export const interpolate = (point: Point, height: number): SVGPoint => {
40+
const handles = point.handles || {angle: 0, out: 0, in: 0};
41+
handles.angle = Math.PI + rad(handles.angle);
42+
return {
43+
x: point.x,
44+
y: height - point.y,
45+
handles,
46+
};
47+
};

render.ts renamed to internal/svg/render.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1-
import {loopAccess, interpolate} from "./util";
2-
import {Point, RenderOptions} from "./types";
1+
import {loopAccess} from "./util";
2+
import {Point, interpolate} from "./point";
3+
4+
export interface RenderOptions {
5+
// Viewport size.
6+
width: number;
7+
height: number;
8+
9+
// Transformation applied to all drawn points.
10+
transform?: string;
11+
12+
// Declare whether the path should be closed.
13+
// This option is currently always true.
14+
closed: true;
15+
16+
// Output path styling.
17+
fill?: string;
18+
stroke?: string;
19+
strokeWidth?: number;
20+
21+
// Option to render guides (points, handles and viewport).
22+
guides?: boolean;
23+
boundingBox?: boolean;
24+
}
325

426
// Renders a shape made up of the input points.
527
export const render = (p: Point[], opt: RenderOptions): string => {
6-
const points = p.map((point) => interpolate(point, opt));
28+
const points = p.map((point) => interpolate(point, opt.height));
729

830
// Compute guides from input point data.
931
const handles: {x1: number; y1: number; x2: number; y2: number}[] = [];

internal/svg/smooth.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {Point} from "./point";
2+
import {loopAccess} from "./util";
3+
import {angle, distance} from "../math/geometry";
4+
5+
export interface SmoothingOptions {
6+
// Declare whether the path is closed.
7+
// This option is currently always true.
8+
closed: true;
9+
10+
// Smoothing strength as ration [0,1].
11+
strength: number;
12+
}
13+
14+
// Smooths out the path made up of the given points. This will override the existing handles.
15+
// https://math.stackexchange.com/questions/873224/calculate-control-points-of-cubic-bezier-curve-approximating-a-part-of-a-circle
16+
export const smooth = (points: Point[], opt: SmoothingOptions): Point[] => {
17+
if (points.length === 2) return points;
18+
19+
const out: Point[] = [];
20+
21+
for (let i = 0; i < points.length; i++) {
22+
const point = loopAccess(points)(i);
23+
const before = loopAccess(points)(i - 1);
24+
const after = loopAccess(points)(i + 1);
25+
26+
out.push({
27+
x: point.x,
28+
y: point.y,
29+
handles: {
30+
angle: angle(before, after),
31+
in: opt.strength * (1 / 2) * distance(point, before),
32+
out: opt.strength * (1 / 2) * distance(point, after),
33+
},
34+
});
35+
}
36+
37+
return out;
38+
};

internal/svg/util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Safe array access at any index using a modulo operation that will always be positive.
2+
export const loopAccess = <T>(arr: T[]) => (i: number): T => {
3+
return arr[((i % arr.length) + arr.length) % arr.length];
4+
};

types.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)