@logomarks/core 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +9 -2
- package/package.json +47 -4
- package/src/analyse/index.d.ts +1 -0
- package/src/analyse/index.js +3 -2
- package/src/analyse/optical.d.ts +20 -0
- package/src/analyse/optical.js +76 -0
- package/src/edit/cells.d.ts +13 -0
- package/src/edit/cells.js +26 -0
- package/src/edit/index.d.ts +3 -1
- package/src/edit/index.js +4 -2
- package/src/edit/pixelate.d.ts +2 -0
- package/src/edit/pixelate.js +15 -0
- package/src/font/glyphs.d.ts +31 -0
- package/src/font/glyphs.js +228 -0
- package/src/font/index.d.ts +10 -0
- package/src/font/index.js +10 -0
- package/src/font/measure.d.ts +16 -0
- package/src/font/measure.js +201 -0
- package/src/font/moves.d.ts +42 -0
- package/src/font/moves.js +263 -0
- package/src/forms/discs.d.ts +20 -0
- package/src/forms/discs.js +114 -0
- package/src/forms/emblems.d.ts +22 -0
- package/src/forms/emblems.js +109 -0
- package/src/forms/flow.d.ts +21 -0
- package/src/forms/flow.js +80 -0
- package/src/forms/folds.d.ts +18 -0
- package/src/forms/folds.js +147 -0
- package/src/forms/geo.d.ts +46 -0
- package/src/forms/geo.js +233 -0
- package/src/forms/hooks.d.ts +40 -0
- package/src/forms/hooks.js +204 -0
- package/src/forms/index.d.ts +22 -0
- package/src/forms/index.js +24 -0
- package/src/forms/jog.d.ts +33 -0
- package/src/forms/jog.js +162 -0
- package/src/forms/lines.d.ts +32 -0
- package/src/forms/lines.js +233 -0
- package/src/forms/ref.d.ts +41 -0
- package/src/forms/ref.js +246 -0
- package/src/forms/shatter.d.ts +40 -0
- package/src/forms/shatter.js +341 -0
- package/src/forms/slats.d.ts +35 -0
- package/src/forms/slats.js +78 -0
- package/src/forms/swash.d.ts +23 -0
- package/src/forms/swash.js +74 -0
- package/src/forms/tech.d.ts +40 -0
- package/src/forms/tech.js +293 -0
- package/src/forms/tracks.d.ts +36 -0
- package/src/forms/tracks.js +121 -0
- package/src/index.js +2 -1
- package/src/iso/index.d.ts +5 -0
- package/src/iso/index.js +13 -0
- package/src/iso/objects.d.ts +168 -0
- package/src/iso/objects.js +1042 -0
- package/src/iso/planes.d.ts +27 -0
- package/src/iso/planes.js +138 -0
- package/src/iso/voxels.d.ts +43 -0
- package/src/iso/voxels.js +255 -0
- package/src/letters/blackletter.d.ts +31 -0
- package/src/letters/blackletter.js +199 -0
- package/src/letters/dotstroke.d.ts +16 -0
- package/src/letters/dotstroke.js +77 -0
- package/src/letters/echo.d.ts +42 -0
- package/src/letters/echo.js +246 -0
- package/src/letters/frame.d.ts +6 -0
- package/src/letters/frame.js +14 -0
- package/src/letters/index.d.ts +16 -0
- package/src/letters/index.js +18 -0
- package/src/letters/inktrap.d.ts +66 -0
- package/src/letters/inktrap.js +244 -0
- package/src/letters/mono.d.ts +145 -0
- package/src/letters/mono.js +684 -0
- package/src/letters/pillar.d.ts +31 -0
- package/src/letters/pillar.js +257 -0
- package/src/letters/serif.d.ts +109 -0
- package/src/letters/serif.js +666 -0
- package/src/letters/stair.d.ts +8 -0
- package/src/letters/stair.js +43 -0
- package/src/letters/stem.d.ts +29 -0
- package/src/letters/stem.js +205 -0
- package/src/objects/animals.d.ts +105 -0
- package/src/objects/animals.js +469 -0
- package/src/objects/bio.d.ts +14 -0
- package/src/objects/bio.js +76 -0
- package/src/objects/devglyphs.d.ts +34 -0
- package/src/objects/devglyphs.js +226 -0
- package/src/objects/devices.d.ts +31 -0
- package/src/objects/devices.js +114 -0
- package/src/objects/faces.d.ts +94 -0
- package/src/objects/faces.js +344 -0
- package/src/objects/food.d.ts +12 -0
- package/src/objects/food.js +74 -0
- package/src/objects/index.d.ts +13 -0
- package/src/objects/index.js +18 -0
- package/src/objects/rosettes.d.ts +14 -0
- package/src/objects/rosettes.js +64 -0
- package/src/objects/sport.d.ts +8 -0
- package/src/objects/sport.js +37 -0
- package/src/objects/weapons.d.ts +31 -0
- package/src/objects/weapons.js +179 -0
- package/src/pixels/dither.d.ts +78 -0
- package/src/pixels/dither.js +399 -0
- package/src/pixels/emote.d.ts +6 -0
- package/src/pixels/emote.js +76 -0
- package/src/pixels/index.d.ts +7 -0
- package/src/pixels/index.js +8 -0
- package/src/sdf/index.d.ts +2 -0
- package/src/sdf/index.js +2 -0
- package/src/sdf/y2k.d.ts +12 -0
- package/src/sdf/y2k.js +80 -0
- package/src/util/vec.d.ts +14 -0
- package/src/util/vec.js +61 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bold discs: one solid disc of radius DISC_R, centred, and the cuts that keep its gap exact when a piece moves. A
|
|
3
|
+
* moving piece is I(region, moved disc): the region's edge (the cut) stays put and only the rim moves, so the gap along
|
|
4
|
+
* the cut is still g. From the Logomarks tools (discparts.mjs, batch 201, "Bold · Discs", and its editions in batch 220).
|
|
5
|
+
*/
|
|
6
|
+
import { C, D, I, U, circle, poly, rot, sample, stroke } from '../geometry/index.js';
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('../geometry/types.js').Point} Point */
|
|
9
|
+
/** @typedef {import('../geometry/types.js').Polygon} Polygon */
|
|
10
|
+
/** @typedef {import('../geometry/types.js').Shape} Shape */
|
|
11
|
+
/** @typedef {import('../geometry/types.js').Geom} Geom */
|
|
12
|
+
|
|
13
|
+
/** The disc's radius. */
|
|
14
|
+
export const DISC_R = 224;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The disc (720 points), moved by (dx, dy).
|
|
18
|
+
*
|
|
19
|
+
* @param {number} [dx=0] Moved right.
|
|
20
|
+
* @param {number} [dy=0] Moved down.
|
|
21
|
+
* @param {number} [r=DISC_R] Its radius.
|
|
22
|
+
* @returns {Polygon} The disc.
|
|
23
|
+
*/
|
|
24
|
+
export const disc = (dx = 0, dy = 0, r = DISC_R) => circle(C + dx, C + dy, r, 720);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A curve across the disc: `f(u)` gives [u, v] in a frame turned by `deg` about the centre, u running from −span·R to
|
|
28
|
+
* span·R.
|
|
29
|
+
*
|
|
30
|
+
* @param {(u: number) => readonly number[]} f The curve in its own frame.
|
|
31
|
+
* @param {number} [deg=0] The frame's turn.
|
|
32
|
+
* @param {number} [span=1.5] How far either side it runs, in radii.
|
|
33
|
+
* @param {number} [n=300] Points along it.
|
|
34
|
+
* @returns {Point[]} The curve on the canvas.
|
|
35
|
+
*/
|
|
36
|
+
export const across = (f, deg = 0, span = 1.5, n = 300) => {
|
|
37
|
+
const a = (deg * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a);
|
|
38
|
+
return sample(f, -span * DISC_R, span * DISC_R, n).map(([u, v]) => [C + u * c - v * s, C + u * s + v * c]);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The half plane on one side of an open curve that runs right across the disc: the curve closed 3000 out along the
|
|
43
|
+
* normal of its first-to-last chord.
|
|
44
|
+
*
|
|
45
|
+
* @param {readonly Point[]} L The curve.
|
|
46
|
+
* @param {number} s Which side: 1 or −1.
|
|
47
|
+
* @returns {Polygon} The half plane.
|
|
48
|
+
*/
|
|
49
|
+
export const discSide = (L, s) => {
|
|
50
|
+
const a = L[0], b = L.at(-1), d = [b[0] - a[0], b[1] - a[1]], l = Math.hypot(...d), n = [(-d[1] / l) * s * 3000, (d[0] / l) * s * 3000];
|
|
51
|
+
return poly([...L, [b[0] + n[0], b[1] + n[1]], [a[0] + n[0], a[1] + n[1]]]);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The disc parted along the curve L by the gap g, each half's rim moved by sa / sb (the cut stays put, so the gap stays g).
|
|
56
|
+
*
|
|
57
|
+
* @param {readonly Point[]} L The cut.
|
|
58
|
+
* @param {number} g The gap.
|
|
59
|
+
* @param {readonly number[]} [sa=[0, 0]] The left half's rim, moved.
|
|
60
|
+
* @param {readonly number[]} [sb=[0, 0]] The right half's rim, moved.
|
|
61
|
+
* @returns {Shape} The two halves.
|
|
62
|
+
*/
|
|
63
|
+
export const split = (L, g, sa = [0, 0], sb = [0, 0]) => {
|
|
64
|
+
const z = stroke(L, g, { chunk: 8 });
|
|
65
|
+
return U(D(I(disc(...sa), discSide(L, 1)), z), D(I(disc(...sb), discSide(L, -1)), z));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A solid band of width b along L, parted from both halves of the disc by g; the band's ends follow a disc moved by sm, so
|
|
70
|
+
* it rides out past the rim at one end.
|
|
71
|
+
*
|
|
72
|
+
* @param {readonly Point[]} L The band's centreline.
|
|
73
|
+
* @param {number | ((i: number, n: number) => number)} b Its width, or a width per point.
|
|
74
|
+
* @param {number} g The gap.
|
|
75
|
+
* @param {readonly number[]} [sm=[0, 0]] The band's disc, moved.
|
|
76
|
+
* @param {readonly number[]} [sa=[0, 0]] The left half's rim, moved.
|
|
77
|
+
* @param {readonly number[]} [sb=[0, 0]] The right half's rim, moved.
|
|
78
|
+
* @returns {Shape} The halves and the band.
|
|
79
|
+
*/
|
|
80
|
+
export const bandCut = (L, b, g, sm = [0, 0], sa = [0, 0], sb = [0, 0]) => {
|
|
81
|
+
const wz = typeof b === 'function' ? (i, n) => b(i, n) + 2 * g : b + 2 * g, z = stroke(L, wz, { chunk: 8 });
|
|
82
|
+
return U(D(I(disc(...sa), discSide(L, 1)), z), D(I(disc(...sb), discSide(L, -1)), z), I(disc(...sm), stroke(L, b, { chunk: 8 })));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A point on the disc's frame: radius r from the centre at angle deg (0 right, 90 down).
|
|
87
|
+
*
|
|
88
|
+
* @param {number} r The radius.
|
|
89
|
+
* @param {number} deg The angle.
|
|
90
|
+
* @returns {Point} The point.
|
|
91
|
+
*/
|
|
92
|
+
export const discAt = (r, deg) => [C + r * Math.cos((deg * Math.PI) / 180), C + r * Math.sin((deg * Math.PI) / 180)];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A shape turned by deg about the point p.
|
|
96
|
+
*
|
|
97
|
+
* @param {Geom} s The shape.
|
|
98
|
+
* @param {number} deg The turn, degrees.
|
|
99
|
+
* @param {readonly number[]} p The pivot.
|
|
100
|
+
* @returns {Geom} The shape, turned.
|
|
101
|
+
*/
|
|
102
|
+
export const turn = (s, deg, p) => rot(s, deg, p[0], p[1]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The two sides of a circular cut (centre P, radius Ra) through the disc, parted by g; the inner side turned th° about P,
|
|
106
|
+
* so it glides along the cut.
|
|
107
|
+
*
|
|
108
|
+
* @param {readonly number[]} P The cut's centre.
|
|
109
|
+
* @param {number} Ra The cut's radius.
|
|
110
|
+
* @param {number} g The gap.
|
|
111
|
+
* @param {number} th The inner side's turn, degrees.
|
|
112
|
+
* @returns {Shape} The two sides.
|
|
113
|
+
*/
|
|
114
|
+
export const arcSplit = (P, Ra, g, th) => U(I(turn(disc(), th, P), circle(...P, Ra - g / 2, 900)), D(disc(), circle(...P, Ra + g / 2, 900)));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Geom, Point, Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** The frame's left edge (32). */
|
|
4
|
+
export const EMBLEM_X0: number;
|
|
5
|
+
/** The frame's right edge (480). */
|
|
6
|
+
export const EMBLEM_X1: number;
|
|
7
|
+
/** The frame's bottom edge (480). */
|
|
8
|
+
export const EMBLEM_Y1: number;
|
|
9
|
+
/** The frame's width (448). */
|
|
10
|
+
export const EMBLEM_W: number;
|
|
11
|
+
/** The scene parted by the horizon: everything within g/2 of y = hz cut away. */
|
|
12
|
+
export function horizon(shape: Geom, hz: number, g: number): Shape;
|
|
13
|
+
/** A sine line across the frame at height y. */
|
|
14
|
+
export function sine(y: number, amp: number, cycles: number, ph?: number, x0?: number, x1?: number): Point[];
|
|
15
|
+
/** The sea: a block cut by n wave bands g wide. */
|
|
16
|
+
export function sea(y0: number, y1: number, g: number, o?: { n?: number; amp?: number; cycles?: number; ph?: number; block?: Geom }): Shape;
|
|
17
|
+
/** A ploughed field: a block cut by n bands fanning from a vanishing point. */
|
|
18
|
+
export function field(y0: number, y1: number, g: number, n?: number, vy?: number, spread?: number, block?: Geom): Shape;
|
|
19
|
+
/** A triangle, its base on y from xa to xb, its apex at (ax, ay). */
|
|
20
|
+
export function tri(xa: number, xb: number, y: number, ax: number, ay: number): Polygon;
|
|
21
|
+
/** A cloud: a flat base on y, three bumps. */
|
|
22
|
+
export function cloud(cx: number, y: number, w: number, h: number): Shape;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split emblems: a 448 frame (x and y from 32 to 480) whose scene is drawn whole and then parted by the horizon gap
|
|
3
|
+
* (`horizon()`), so the cut is always exactly g; the lower half is a ground block carved by gap-wide bands (`sea()`: sine
|
|
4
|
+
* bands; `field()`: bands fanning from a vanishing point). Each mark sets its own horizon. From the Logomarks tools
|
|
5
|
+
* (emblemparts.mjs, batch 216, "Bold · Emblems", and its editions in batch 241).
|
|
6
|
+
*/
|
|
7
|
+
import { U, D, I, poly, rect, circle, sample, mitreBand as band } from '../geometry/index.js';
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('../geometry/types.js').Point} Point */
|
|
10
|
+
/** @typedef {import('../geometry/types.js').Polygon} Polygon */
|
|
11
|
+
/** @typedef {import('../geometry/types.js').Shape} Shape */
|
|
12
|
+
/** @typedef {import('../geometry/types.js').Geom} Geom */
|
|
13
|
+
|
|
14
|
+
/** The frame's left edge. */
|
|
15
|
+
export const EMBLEM_X0 = 32;
|
|
16
|
+
|
|
17
|
+
/** The frame's right edge. */
|
|
18
|
+
export const EMBLEM_X1 = 480;
|
|
19
|
+
|
|
20
|
+
/** The frame's bottom edge. */
|
|
21
|
+
export const EMBLEM_Y1 = 480;
|
|
22
|
+
|
|
23
|
+
/** The frame's width. */
|
|
24
|
+
export const EMBLEM_W = EMBLEM_X1 - EMBLEM_X0;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The scene parted by the horizon: everything within g/2 of y = hz cut away.
|
|
28
|
+
*
|
|
29
|
+
* @param {Geom} shape The scene.
|
|
30
|
+
* @param {number} hz The horizon's height.
|
|
31
|
+
* @param {number} g The gap.
|
|
32
|
+
* @returns {Shape} The scene, parted.
|
|
33
|
+
*/
|
|
34
|
+
export const horizon = (shape, hz, g) => D(shape, rect(-200, hz - g / 2, 900, g));
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A sine line across the frame at height y: amp tall, `cycles` waves over the frame's width, starting at phase ph.
|
|
38
|
+
*
|
|
39
|
+
* @param {number} y Its middle height.
|
|
40
|
+
* @param {number} amp Its amplitude.
|
|
41
|
+
* @param {number} cycles Waves across the frame.
|
|
42
|
+
* @param {number} [ph=0] The phase, degrees.
|
|
43
|
+
* @param {number} [x0=EMBLEM_X0 - 40] Where it starts.
|
|
44
|
+
* @param {number} [x1=EMBLEM_X1 + 40] Where it ends.
|
|
45
|
+
* @returns {Point[]} The line (160 steps).
|
|
46
|
+
*/
|
|
47
|
+
export const sine = (y, amp, cycles, ph = 0, x0 = EMBLEM_X0 - 40, x1 = EMBLEM_X1 + 40) =>
|
|
48
|
+
sample((x) => [x, y + amp * Math.sin(((x - EMBLEM_X0) / EMBLEM_W) * cycles * 2 * Math.PI + (ph * Math.PI) / 180)], x0, x1, 160);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The sea: the block from y0 to y1 with n wave bands g wide cut across it, evenly spaced.
|
|
52
|
+
*
|
|
53
|
+
* @param {number} y0 The block's top.
|
|
54
|
+
* @param {number} y1 Its bottom.
|
|
55
|
+
* @param {number} g The gap (the bands' width).
|
|
56
|
+
* @param {{ n?: number, amp?: number, cycles?: number, ph?: number, block?: Geom }} [o] Bands (2), their amplitude (16),
|
|
57
|
+
* waves across (2), phase (0) and the block to carve (the frame's width from y0 to y1).
|
|
58
|
+
* @returns {Shape} The sea.
|
|
59
|
+
*/
|
|
60
|
+
export const sea = (y0, y1, g, { n = 2, amp = 16, cycles = 2, ph = 0, block = rect(EMBLEM_X0, y0, EMBLEM_W, y1 - y0) } = {}) => {
|
|
61
|
+
const step = (y1 - y0) / (n + 1);
|
|
62
|
+
return D(block, ...[...Array(n)].map((_, i) => band(sine(y0 + step * (i + 1), amp, cycles, ph), g)));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A ploughed field in perspective: the block cut by n bands g wide fanning down from a vanishing point (256, vy) above
|
|
67
|
+
* the horizon, their feet spread across the width (spread 1 lays the outer feet on the frame's corners).
|
|
68
|
+
*
|
|
69
|
+
* @param {number} y0 The block's top.
|
|
70
|
+
* @param {number} y1 Its bottom.
|
|
71
|
+
* @param {number} g The gap (the bands' width).
|
|
72
|
+
* @param {number} [n=3] The bands.
|
|
73
|
+
* @param {number} [vy=y0 - 150] The vanishing point's height.
|
|
74
|
+
* @param {number} [spread=1.15] How far the feet spread.
|
|
75
|
+
* @param {Geom} [block] The block to carve (the frame's width from y0 to y1).
|
|
76
|
+
* @returns {Shape} The field.
|
|
77
|
+
*/
|
|
78
|
+
export const field = (y0, y1, g, n = 3, vy = y0 - 150, spread = 1.15, block = rect(EMBLEM_X0, y0, EMBLEM_W, y1 - y0)) =>
|
|
79
|
+
D(block, ...[...Array(n)].map((_, i) => {
|
|
80
|
+
const t = (i - (n - 1) / 2) / ((n + 1) / 2), foot = [256 + t * (EMBLEM_W / 2) * spread * 1.6, y1 + 60];
|
|
81
|
+
return I(band([[256, vy], foot], g), rect(-200, y0, 900, 900));
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A triangle with its base on y from xa to xb and its apex at (ax, ay).
|
|
86
|
+
*
|
|
87
|
+
* @param {number} xa The base's left end.
|
|
88
|
+
* @param {number} xb The base's right end.
|
|
89
|
+
* @param {number} y The base's height.
|
|
90
|
+
* @param {number} ax The apex.
|
|
91
|
+
* @param {number} ay The apex.
|
|
92
|
+
* @returns {Polygon} The triangle.
|
|
93
|
+
*/
|
|
94
|
+
export const tri = (xa, xb, y, ax, ay) => poly([[xa, y], [ax, ay], [xb, y]]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A cloud: a flat base on y and three bumps, w wide and h tall about cx.
|
|
98
|
+
*
|
|
99
|
+
* @param {number} cx Its middle.
|
|
100
|
+
* @param {number} y Its base.
|
|
101
|
+
* @param {number} w Its width.
|
|
102
|
+
* @param {number} h Its height.
|
|
103
|
+
* @returns {Shape} The cloud.
|
|
104
|
+
*/
|
|
105
|
+
export const cloud = (cx, y, w, h) => {
|
|
106
|
+
const r1 = h * 0.42, r2 = h * 0.62, r3 = h * 0.36;
|
|
107
|
+
return I(U(circle(cx - w * 0.26, y - r1, r1, 240), circle(cx + w * 0.04, y - h + r2, r2, 240), circle(cx + w * 0.3, y - r3, r3, 240),
|
|
108
|
+
rect(cx - w * 0.26, y - r1, w * 0.56, r1)), rect(cx - w, y - 2 * h, 2 * w, 2 * h));
|
|
109
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Point, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** A flow grid's drawing: n × m discs of radius r, gap g, the path, the bridges' neck, blend, fillet and skipped cells. */
|
|
4
|
+
export interface FlowOptions {
|
|
5
|
+
n: number;
|
|
6
|
+
m?: number;
|
|
7
|
+
r: number;
|
|
8
|
+
g: number;
|
|
9
|
+
path: string;
|
|
10
|
+
neck?: number;
|
|
11
|
+
k?: number;
|
|
12
|
+
fillet?: number;
|
|
13
|
+
skip?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A flow path ("a0 b0 b1", a trailing "~" closing it) as cells [column, row]. */
|
|
17
|
+
export function cells(s: string): { path: [number, number][]; closed: boolean };
|
|
18
|
+
/** A flow grid's pitch and each cell's centre, for n × m discs of radius r and gap g centred on (cx, cy). */
|
|
19
|
+
export function grid(n: number, m: number, r: number, g: number, cx?: number, cy?: number): { p: number; at: (cell: readonly number[]) => Point };
|
|
20
|
+
/** A flow grid as shapes: the path, the free discs, both, and the narrowest gap between them. */
|
|
21
|
+
export function flowShape(o: FlowOptions): { path: Shape; free: Shape; shape: Shape; clearance: number };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow grids: discs on a small square grid where one path of neighbouring discs is joined by capsule bridges into a
|
|
3
|
+
* single smooth stroke (a letter, a wave, a loop), every join filleted, while the other discs stay single dots.
|
|
4
|
+
*
|
|
5
|
+
* The grid's pitch is 2r + g, so a free disc sits exactly the gap g from any path disc beside it. A path is a string of
|
|
6
|
+
* cells, "a0 b0 b1 …" (column letter a–d, row digit 0–3, y down), each cell an orthogonal neighbour of the one before; a
|
|
7
|
+
* trailing "~" closes it into a loop. A tube (`neck` 1) is drawn with the kit: a pill per bridge, each turn's inner
|
|
8
|
+
* corner filleted by closing just that turn's two pills, so a fillet never reaches across to a cell it isn't joined to.
|
|
9
|
+
* A bead string (`neck` under 1) is a distance field: each bridge melted into its two discs by a smooth minimum, the
|
|
10
|
+
* bridges then joined sharply, so two path discs side by side but unbridged keep their gap. The smooth minimum bulges
|
|
11
|
+
* where two of its fields run level, so it suits waisted bridges only.
|
|
12
|
+
* From the Logomarks tools (flowparts.mjs, batch 236, "Modular · Flow").
|
|
13
|
+
*/
|
|
14
|
+
import { circle, pill, U, soften } from '../geometry/index.js';
|
|
15
|
+
import { sdCircle, sdCapsule, smin, fmin, shapeOf } from '../sdf/index.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A flow path parsed into cells: "a0 b0 b1" → [[0, 0], [1, 0], [1, 1]]; a trailing "~" marks a closed loop.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} s The path.
|
|
21
|
+
* @returns {{ path: [number, number][], closed: boolean }} Its cells (column, row) and whether it closes.
|
|
22
|
+
*/
|
|
23
|
+
export const cells = (s) => {
|
|
24
|
+
const t = s.trim().split(/\s+/), closed = t[t.length - 1] === '~';
|
|
25
|
+
return { path: (closed ? t.slice(0, -1) : t).map((c) => [c.charCodeAt(0) - 97, +c.slice(1)]), closed };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A flow grid's geometry: its pitch and each cell's centre, for an n × m grid of discs of radius r and gap g centred on
|
|
30
|
+
* (cx, cy).
|
|
31
|
+
*
|
|
32
|
+
* @param {number} n Columns.
|
|
33
|
+
* @param {number} m Rows.
|
|
34
|
+
* @param {number} r The discs' radius.
|
|
35
|
+
* @param {number} g The gap.
|
|
36
|
+
* @param {number} [cx=256] The grid's centre.
|
|
37
|
+
* @param {number} [cy=256] The grid's centre.
|
|
38
|
+
* @returns {{ p: number, at: (cell: readonly number[]) => [number, number] }} The pitch and a cell's centre.
|
|
39
|
+
*/
|
|
40
|
+
export const grid = (n, m, r, g, cx = 256, cy = 256) => {
|
|
41
|
+
const p = 2 * r + g, x0 = cx - ((n - 1) * p) / 2, y0 = cy - ((m - 1) * p) / 2;
|
|
42
|
+
return { p, at: ([i, j]) => [x0 + i * p, y0 + j * p] };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A flow grid as shapes: the path, the free discs, both together, and the narrowest gap between them (so a fillet that
|
|
47
|
+
* bulges into the gap is caught).
|
|
48
|
+
*
|
|
49
|
+
* @param {{ n: number, m?: number, r: number, g: number, path: string, neck?: number, k?: number, fillet?: number, skip?: string }} o
|
|
50
|
+
* The grid (n × m, m = n), the discs' radius r and the gap g; the path (cells()); `neck` the bridges' width as a share
|
|
51
|
+
* of the disc (1, a tube; under 1 a bead string melted with blend k, 0.6 r); `fillet` the tube's inner-corner radius
|
|
52
|
+
* (0.6 r); `skip` cells left empty (no free disc either).
|
|
53
|
+
* @returns {{ path: import('../geometry/types.js').Shape, free: import('../geometry/types.js').Shape, shape: import('../geometry/types.js').Shape, clearance: number }}
|
|
54
|
+
* The path, the free discs, both, and the narrowest gap from the path to a free disc.
|
|
55
|
+
* @throws {Error} When a step of the path isn't to a neighbouring cell.
|
|
56
|
+
*/
|
|
57
|
+
export function flowShape({ n, m = n, r, g, path, neck = 1, k = r * 0.6, fillet = r * 0.6, skip = '' }) {
|
|
58
|
+
const G = grid(n, m, r, g), { path: P, closed } = cells(path), key = ([i, j]) => `${i},${j}`, on = new Set(P.map(key));
|
|
59
|
+
const off = new Set(skip.trim() ? cells(skip).path.map(key) : []);
|
|
60
|
+
P.forEach((c, i) => { if (i && Math.abs(c[0] - P[i - 1][0]) + Math.abs(c[1] - P[i - 1][1]) !== 1) throw new Error(`flow: ${key(P[i - 1])} → ${key(c)} is not a neighbour step`); });
|
|
61
|
+
const B = P.slice(1).map((c, i) => [P[i], c]); if (closed) B.push([P[P.length - 1], P[0]]);
|
|
62
|
+
let pathS;
|
|
63
|
+
if (neck >= 1) {
|
|
64
|
+
const pills = B.map(([a, b]) => pill(G.at(a), G.at(b), 2 * r));
|
|
65
|
+
// a turn: two bridges meeting at a cell at a right angle; close just those two so the fillet stays in that corner
|
|
66
|
+
const turns = B.map((b, i) => [b, B[(i + 1) % B.length], i]).filter(([a, b, i]) => (closed || i < B.length - 1) && a[1] === b[0]
|
|
67
|
+
&& (a[1][0] - a[0][0]) * (b[1][0] - b[0][0]) + (a[1][1] - a[0][1]) * (b[1][1] - b[0][1]) === 0);
|
|
68
|
+
pathS = U(...pills, ...P.map((c) => circle(...G.at(c), r)), ...turns.map(([, , i]) => soften(U(pills[i], pills[(i + 1) % pills.length]), fillet, 0)));
|
|
69
|
+
} else pathS = shapeOf(fmin(...B.map(([a, b]) => smin(k, sdCircle(...G.at(a), r), sdCircle(...G.at(b), r), sdCapsule(G.at(a), G.at(b), 2 * r * neck)))));
|
|
70
|
+
const freeC = []; for (let j = 0; j < m; j++) for (let i = 0; i < n; i++) if (!on.has(key([i, j])) && !off.has(key([i, j]))) freeC.push(G.at([i, j]));
|
|
71
|
+
const free = freeC.length ? U(...freeC.map((c) => circle(...c, r))) : [];
|
|
72
|
+
// the narrowest gap: from each free disc's centre to the path's outline, less the radius
|
|
73
|
+
const seg = ([ax, ay], [bx, by], [x, y]) => {
|
|
74
|
+
const dx = bx - ax, dy = by - ay, t = Math.max(0, Math.min(1, ((x - ax) * dx + (y - ay) * dy) / (dx * dx + dy * dy || 1e-9)));
|
|
75
|
+
return Math.hypot(x - ax - t * dx, y - ay - t * dy);
|
|
76
|
+
};
|
|
77
|
+
let clearance = Infinity;
|
|
78
|
+
for (const c of freeC) for (const pg of pathS) for (const ring of pg) for (let i = 1; i < ring.length; i++) clearance = Math.min(clearance, seg(ring[i - 1], ring[i], c) - r);
|
|
79
|
+
return { path: pathS, free, shape: freeC.length ? U(pathS, free) : pathS, clearance };
|
|
80
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Point, Polygon, Shape, Geom } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** A seam g wide along a polyline, round ends (two points: a pill). */
|
|
4
|
+
export function seamLine(g: number, pts: readonly Point[]): Geom;
|
|
5
|
+
/** A shape split into facets by seams g wide. */
|
|
6
|
+
export function crease(shape: Geom, g: number, ...seams: Point[][]): Shape;
|
|
7
|
+
/** A smooth seam through control points (Catmull-Rom, n points a span). */
|
|
8
|
+
export function curve(pts: readonly Point[], n?: number): Point[];
|
|
9
|
+
/** A quadratic Bézier from a to b pulled towards c, n + 1 points. */
|
|
10
|
+
export function foldQuad(a: readonly number[], c: readonly number[], b: readonly number[], n?: number): Point[];
|
|
11
|
+
/** A strap: a pill w wide from a to b. */
|
|
12
|
+
export function strap(a: Point, b: Point, w: number): Polygon;
|
|
13
|
+
/** A flat strip w wide from a to b, its ends cut on the lines y = y0 and y = y1 if given. */
|
|
14
|
+
export function strip(a: readonly number[], b: readonly number[], w: number, o?: { y0?: number; y1?: number }): Polygon;
|
|
15
|
+
/** A hand-cut edge: every ring wobbled along its normal by a smooth seeded wave of amplitude amp. */
|
|
16
|
+
export function wobble(shape: Geom, amp: number, rnd?: () => number, o?: { step?: number; waves?: number }): Shape;
|
|
17
|
+
/** Scissor-cut edges: every ring as straight snips about `step` long, ends nudged by up to `jit`. */
|
|
18
|
+
export function snip(shape: Geom, step?: number, jit?: number, rnd?: () => number, corner?: number): Shape;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fold seams, straps and hand-cut edges, the parts the Crease, Strap and Snip marks share. A seam is half the house gap
|
|
3
|
+
* (11 once fitted, against the 22 gap): a fold or an overlap drawn as a hairline that shows at large sizes and fades at
|
|
4
|
+
* 32 px, where the mark must read as its silhouette alone. Every build takes the seam width g (exact() with a target of
|
|
5
|
+
* 11 keeps it 11 once fitted), so a part that is truly separate stands 2g clear. Laying parts one over another, each
|
|
6
|
+
* cut free by the seam, is the parts' chain().
|
|
7
|
+
* From the Logomarks tools (foldparts.mjs, batch 170).
|
|
8
|
+
*/
|
|
9
|
+
import { U, D, poly, pill, stroke, dedupe } from '../geometry/index.js';
|
|
10
|
+
import { spline } from '../parts/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A seam along a polyline: g wide with round ends, so seams that meet at a node leave the facets' corners sharp.
|
|
14
|
+
*
|
|
15
|
+
* @param {number} g The seam's width.
|
|
16
|
+
* @param {readonly import('../geometry/types.js').Point[]} pts The seam's line (two points: a straight seam).
|
|
17
|
+
* @returns {import('../geometry/types.js').Geom} The seam.
|
|
18
|
+
*/
|
|
19
|
+
export const seamLine = (g, pts) => (pts.length === 2 ? pill(pts[0], pts[1], g) : stroke(dedupe(pts), g, { roundCaps: true, chunk: 8 }));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A shape split into facets by seams. Run a seam that ends on the outline a little past it, and end inner seams exactly
|
|
23
|
+
* on a node where they meet another.
|
|
24
|
+
*
|
|
25
|
+
* @param {import('../geometry/types.js').Geom} shape The shape.
|
|
26
|
+
* @param {number} g The seam's width.
|
|
27
|
+
* @param {...import('../geometry/types.js').Point[]} seams Each seam's points (two for a straight seam, more for a curve).
|
|
28
|
+
* @returns {import('../geometry/types.js').Shape} The facets.
|
|
29
|
+
*/
|
|
30
|
+
export const crease = (shape, g, ...seams) => D(shape, ...seams.map((s) => seamLine(g, s)));
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A smooth seam through control points (Catmull-Rom, the ends held).
|
|
34
|
+
*
|
|
35
|
+
* @param {readonly import('../geometry/types.js').Point[]} pts The control points.
|
|
36
|
+
* @param {number} [n=24] Points a span.
|
|
37
|
+
* @returns {import('../geometry/types.js').Point[]} The seam's line.
|
|
38
|
+
*/
|
|
39
|
+
export const curve = (pts, n = 24) => spline(pts, n);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A quadratic Bézier from a to b pulled towards c, as n + 1 points.
|
|
43
|
+
*
|
|
44
|
+
* @param {readonly number[]} a The start.
|
|
45
|
+
* @param {readonly number[]} c The control point.
|
|
46
|
+
* @param {readonly number[]} b The end.
|
|
47
|
+
* @param {number} [n=48] Steps.
|
|
48
|
+
* @returns {import('../geometry/types.js').Point[]} The curve.
|
|
49
|
+
*/
|
|
50
|
+
export const foldQuad = (a, c, b, n = 48) => [...Array(n + 1)].map((_, i) => {
|
|
51
|
+
const t = i / n, u = 1 - t;
|
|
52
|
+
return [u * u * a[0] + 2 * u * t * c[0] + t * t * b[0], u * u * a[1] + 2 * u * t * c[1] + t * t * b[1]];
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A strap: a pill stroke w wide from a to b (strap letters lay one strap over another with the parts' chain()).
|
|
57
|
+
*
|
|
58
|
+
* @param {import('../geometry/types.js').Point} a One end.
|
|
59
|
+
* @param {import('../geometry/types.js').Point} b The other.
|
|
60
|
+
* @param {number} w The width.
|
|
61
|
+
* @returns {import('../geometry/types.js').Polygon} The strap.
|
|
62
|
+
*/
|
|
63
|
+
export const strap = (a, b, w) => pill(a, b, w);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A flat strip w wide from a to b, each end cut on a horizontal line (y0 at a, y1 at b) if given, else square.
|
|
67
|
+
*
|
|
68
|
+
* @param {readonly number[]} a One end.
|
|
69
|
+
* @param {readonly number[]} b The other.
|
|
70
|
+
* @param {number} w The width.
|
|
71
|
+
* @param {{ y0?: number, y1?: number }} [o] The heights the ends are cut on.
|
|
72
|
+
* @returns {import('../geometry/types.js').Polygon} The strip.
|
|
73
|
+
*/
|
|
74
|
+
export const strip = (a, b, w, { y0, y1 } = {}) => {
|
|
75
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy), n = [-dy / L * w / 2, dx / L * w / 2];
|
|
76
|
+
const ends = (p, y, sgn) => {
|
|
77
|
+
if (y == null) return [[p[0] + n[0], p[1] + n[1]], [p[0] - n[0], p[1] - n[1]]];
|
|
78
|
+
// slide each edge along the strip's direction until it meets y
|
|
79
|
+
return [n, [-n[0], -n[1]]].map((o) => { const q = [p[0] + o[0], p[1] + o[1]], t = (y - q[1]) / dy; return [q[0] + dx * t, y]; });
|
|
80
|
+
};
|
|
81
|
+
const [a1, a2] = ends(a, y0), [b1, b2] = ends(b, y1); return poly([a1, b1, b2, a2]);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A hand-cut edge: every ring of a shape resampled every `step` and pushed along its normal by a smooth, seeded wobble
|
|
86
|
+
* of amplitude `amp` (three sine waves round the ring with random phases from `rnd`, about `waves` bumps per 1000 units
|
|
87
|
+
* of outline), so the cut reads as scissors on paper, not as noise. The same seed gives the same edge.
|
|
88
|
+
*
|
|
89
|
+
* @param {import('../geometry/types.js').Geom} shape The shape.
|
|
90
|
+
* @param {number} amp The wobble's amplitude.
|
|
91
|
+
* @param {() => number} [rnd=Math.random] The random source (seeded, for a repeatable edge).
|
|
92
|
+
* @param {{ step?: number, waves?: number }} [o] The resampling step (3) and the bumps per 1000 units (9).
|
|
93
|
+
* @returns {import('../geometry/types.js').Shape} The shape with its edges wobbled.
|
|
94
|
+
*/
|
|
95
|
+
export const wobble = (shape, amp, rnd = Math.random, { step = 3, waves = 9 } = {}) => {
|
|
96
|
+
const polys = shape.length && typeof shape[0][0][0] === 'number' ? [shape] : shape;
|
|
97
|
+
const ph = [rnd(), rnd(), rnd()].map((v) => v * 2 * Math.PI);
|
|
98
|
+
const ringW = (ring) => {
|
|
99
|
+
const r = dedupe(ring.slice(0, -1).concat(ring.at(-1)[0] === ring[0][0] && ring.at(-1)[1] === ring[0][1] ? [] : [ring.at(-1)]));
|
|
100
|
+
const dense = []; r.forEach((p, i) => {
|
|
101
|
+
const q = r[(i + 1) % r.length], m = Math.max(1, Math.ceil(Math.hypot(q[0] - p[0], q[1] - p[1]) / step));
|
|
102
|
+
for (let j = 0; j < m; j++) dense.push([p[0] + ((q[0] - p[0]) * j) / m, p[1] + ((q[1] - p[1]) * j) / m]);
|
|
103
|
+
});
|
|
104
|
+
const n = dense.length, S = [0]; for (let i = 1; i < n; i++) S.push(S[i - 1] + Math.hypot(dense[i][0] - dense[i - 1][0], dense[i][1] - dense[i - 1][1]));
|
|
105
|
+
const L = S[n - 1] + Math.hypot(dense[0][0] - dense[n - 1][0], dense[0][1] - dense[n - 1][1]), k = Math.max(3, Math.round((waves * L) / 1000));
|
|
106
|
+
return dense.map((p, i) => {
|
|
107
|
+
const a = dense[(i - 1 + n) % n], b = dense[(i + 1) % n], dx = b[0] - a[0], dy = b[1] - a[1], l = Math.hypot(dx, dy) || 1, t = (2 * Math.PI * S[i]) / L;
|
|
108
|
+
const w = amp * (0.6 * Math.sin(k * t + ph[0]) + 0.3 * Math.sin((2 * k + 1) * t + ph[1]) + 0.2 * Math.sin((3 * k + 2) * t + ph[2]));
|
|
109
|
+
return [p[0] - (dy / l) * w, p[1] + (dx / l) * w];
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return U(...polys.map((pg) => D(poly(ringW(pg[0])), ...pg.slice(1).map((h) => poly(ringW(h))))));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Scissor-cut edges: every ring of a shape redrawn as straight snips about `step` long (corners sharper than `corner`°
|
|
117
|
+
* kept where they are), each snip's end nudged along the normal by up to `jit` from the seeded `rnd`, so curves turn
|
|
118
|
+
* into short straight cuts and straight edges kink slightly, as paper cut by hand.
|
|
119
|
+
*
|
|
120
|
+
* @param {import('../geometry/types.js').Geom} shape The shape.
|
|
121
|
+
* @param {number} [step=34] The snips' length.
|
|
122
|
+
* @param {number} [jit=3] The most a snip's end moves.
|
|
123
|
+
* @param {() => number} [rnd=Math.random] The random source (seeded, for a repeatable edge).
|
|
124
|
+
* @param {number} [corner=140] Corners sharper than this (degrees) stay put.
|
|
125
|
+
* @returns {import('../geometry/types.js').Shape} The shape, snipped.
|
|
126
|
+
*/
|
|
127
|
+
export const snip = (shape, step = 34, jit = 3, rnd = Math.random, corner = 140) => {
|
|
128
|
+
const polys = shape.length && typeof shape[0][0][0] === 'number' ? [shape] : shape;
|
|
129
|
+
const ringS = (ring) => {
|
|
130
|
+
const r = dedupe(ring.at(-1)[0] === ring[0][0] && ring.at(-1)[1] === ring[0][1] ? ring.slice(0, -1) : ring), n = r.length;
|
|
131
|
+
const ang = (i) => {
|
|
132
|
+
const a = r[(i - 1 + n) % n], p = r[i], b = r[(i + 1) % n], u = [a[0] - p[0], a[1] - p[1]], v = [b[0] - p[0], b[1] - p[1]];
|
|
133
|
+
return (Math.acos(Math.max(-1, Math.min(1, (u[0] * v[0] + u[1] * v[1]) / (Math.hypot(...u) * Math.hypot(...v) || 1)))) * 180) / Math.PI;
|
|
134
|
+
};
|
|
135
|
+
const out = []; let acc = 0;
|
|
136
|
+
for (let i = 0; i < n; i++) {
|
|
137
|
+
const p = r[i], q = r[(i + 1) % n], sharp = ang(i) < corner;
|
|
138
|
+
if (sharp || acc >= step || i === 0) {
|
|
139
|
+
const a = r[(i - 1 + n) % n], dx = q[0] - a[0], dy = q[1] - a[1], l = Math.hypot(dx, dy) || 1, j = sharp ? 0 : (rnd() * 2 - 1) * jit;
|
|
140
|
+
out.push([p[0] - (dy / l) * j, p[1] + (dx / l) * j]); acc = 0;
|
|
141
|
+
}
|
|
142
|
+
acc += Math.hypot(q[0] - p[0], q[1] - p[1]);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
};
|
|
146
|
+
return U(...polys.map((pg) => D(poly(ringS(pg[0])), ...pg.slice(1).map((h) => poly(ringS(h))))));
|
|
147
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Geom, Point, Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** The base square's side (420). */
|
|
4
|
+
export const GEO_SQ: number;
|
|
5
|
+
/** The base square's left edge. */
|
|
6
|
+
export const GEO_X0: number;
|
|
7
|
+
/** The base square's right edge. */
|
|
8
|
+
export const GEO_X1: number;
|
|
9
|
+
/** The base square, side s, centred. */
|
|
10
|
+
export function sq(s?: number): Polygon;
|
|
11
|
+
/** The unit vector from a to b, and the distance. */
|
|
12
|
+
export function dirOf(a: readonly number[], b: readonly number[]): [Point, number];
|
|
13
|
+
/** A needle gap from a (width w0) to b (width w1), run on past b by ext at the same taper. */
|
|
14
|
+
export function needle(a: readonly number[], b: readonly number[], w0: number, w1: number, ext?: number): Polygon;
|
|
15
|
+
/** A straight band of width w along a → b, square ends. */
|
|
16
|
+
export function band(a: readonly number[], b: readonly number[], w: number): Polygon;
|
|
17
|
+
/** The half plane on one side (s = ±1) of an open polyline that runs right across a shape. */
|
|
18
|
+
export function side(L: readonly Point[], s: number): Polygon;
|
|
19
|
+
/** A front strand laid over a closed band near p: the band cut free round it by g within R of p. */
|
|
20
|
+
export function crossOver(all: Geom, front: Geom, p: readonly number[], R: number, g: number): Shape;
|
|
21
|
+
/** A disc with at least 180 points. */
|
|
22
|
+
export function geoDisc(cx: number, cy: number, r: number): Polygon;
|
|
23
|
+
/** A knob: a disc with one slot at deg, split right across (full) or a pointer from the centre. */
|
|
24
|
+
export function knob(cx: number, cy: number, r: number, deg: number, w: number, full?: boolean): Shape;
|
|
25
|
+
/** Knobs on a grid of tokens ('|', '-', '\\', '/', 'pN', '.'), discs of radius r on a pitch of 2r + g. */
|
|
26
|
+
export function knobs(rows: readonly (readonly string[])[], g: number, w: number, r: number): Shape;
|
|
27
|
+
/** The disc radius that makes n discs and n − 1 gaps span 448. */
|
|
28
|
+
export function rFor(n: number, g: number): number;
|
|
29
|
+
/** A roof tile: a square with a half disc on its far side, the cap pointing at deg. */
|
|
30
|
+
export function roofTile(cx: number, cy: number, a: number, deg?: number): Shape;
|
|
31
|
+
/** Tiles laid back to front, each over the ones before and cut free by g. */
|
|
32
|
+
export function layered(list: readonly Geom[], g: number): Geom;
|
|
33
|
+
/** A triangle's three corners, circumradius R about (cx, cy). */
|
|
34
|
+
export function triPts(cx: number, cy: number, R: number, deg?: number): Point[];
|
|
35
|
+
/** The half of a disc on the side sideDeg points to. */
|
|
36
|
+
export function halfDisc(cx: number, cy: number, r: number, sideDeg: number): Shape;
|
|
37
|
+
/** The split disc's radius (224). */
|
|
38
|
+
export const R7: number;
|
|
39
|
+
/** The disc parted along L by g, each half's rim moved by sa / sb. */
|
|
40
|
+
export function splitDisc(L: readonly Point[], g: number, sa: readonly number[], sb: readonly number[]): Shape;
|
|
41
|
+
/** A roof tile's side in scales() (150). */
|
|
42
|
+
export const SA: number;
|
|
43
|
+
/** Roof tiles on a turned lattice, laid back to front; tiles in `up` face back up. */
|
|
44
|
+
export function scales(cells: readonly Point[], g: number, o?: { a?: number; pi?: number; pj?: number; deg?: number; up?: readonly Point[] }): Shape;
|
|
45
|
+
/** The nine points of a 3 × 3 lattice. */
|
|
46
|
+
export const grid3: Point[];
|