@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,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animals: Gecko Wall's build (so a new species or pose is new data rather than a redraw), the Track animals' two-track
|
|
3
|
+
* parts (two tracks 26 wide and 48 apart round one centreline), the Bold animals' smooth outlines, tapers, seams and
|
|
4
|
+
* sickle wings, pets (a flat-footed leg, a bone, a paw print), ribbon loops and animals side-on in headphones. From the
|
|
5
|
+
* Logomarks tools (animalparts.mjs, batches 160, 176, 182, 192, 196, 197, 221, 279, 291, 297, 308, 314, 326, 335).
|
|
6
|
+
*/
|
|
7
|
+
import { U, D, I, circle, ellipse, pill, bar, rr, rot, stroke, polar, poly, grow, scale, sample, trackLoop, tracks, C, rect, move, over, grown, roundPoly } from '../geometry/index.js';
|
|
8
|
+
import { duo, spline, splineClosed } from '../parts/index.js';
|
|
9
|
+
import { arc } from './faces.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A round eye hole.
|
|
13
|
+
*
|
|
14
|
+
* @param {number} x The centre, x.
|
|
15
|
+
* @param {number} y The centre, y.
|
|
16
|
+
* @param {number} [r=13] The radius.
|
|
17
|
+
* @returns {import('../geometry/types.js').Polygon} The eye.
|
|
18
|
+
*/
|
|
19
|
+
export const eye = (x, y, r = 13) => circle(x, y, r, 64);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A foot of splayed toes from (x, y): n toes `len` long fanned `fanDeg` about `deg`, each ending in a round pad (a gecko's).
|
|
23
|
+
*
|
|
24
|
+
* @param {number} x The foot, x.
|
|
25
|
+
* @param {number} y The foot, y.
|
|
26
|
+
* @param {number} deg The way the foot points, in degrees.
|
|
27
|
+
* @param {number} [n=4] How many toes.
|
|
28
|
+
* @param {number} [len=52] Their length.
|
|
29
|
+
* @param {number} [w=18] Their width.
|
|
30
|
+
* @param {number} [pad=17] The pads' radius.
|
|
31
|
+
* @param {number} [fanDeg=110] How far they fan, in degrees.
|
|
32
|
+
* @returns {import('../geometry/types.js').Shape} The foot.
|
|
33
|
+
*/
|
|
34
|
+
export const toes = (x, y, deg, n = 4, len = 52, w = 18, pad = 17, fanDeg = 110) => U(...[...Array(n)].map((_, k) => { const a = deg - fanDeg / 2 + (fanDeg * k) / (n - 1), t = polar(len, a, x, y);
|
|
35
|
+
return U(pill([x, y], t, w), circle(...t, pad, 48)); }));
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gecko Wall's width along the body, head to tail tip.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} t How far along, 0 → 1.
|
|
41
|
+
* @returns {number} The width there.
|
|
42
|
+
*/
|
|
43
|
+
export const geckoW = (t) => (t < 0.08 ? 80 + 300 * t : t < 0.5 ? 104 - 20 * Math.sin(t * 9) : 100 * (1 - t) * 1.9 + 14);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A gecko from above: the body along `spinePts` (control points, head end first), the head an ellipse on its leading
|
|
47
|
+
* end with two eyes, and `legs` [shoulder, knee, foot] each ending in a foot of padded toes turned the way the shin points.
|
|
48
|
+
*
|
|
49
|
+
* @param {readonly (readonly number[])[]} spinePts The spine's control points, head first.
|
|
50
|
+
* @param {readonly (readonly (readonly number[])[])[]} legs Each leg's [shoulder, knee, foot].
|
|
51
|
+
* @param {{ w?: (t: number) => number, legW?: number, toe?: { n?: number, len?: number, w?: number, pad?: number }, head?: number[], eyeAt?: number[] }} [o]
|
|
52
|
+
* The width along the body, the limbs' width, toes()'s options, the head's [rx, ry] and the eyes' [back, out, r].
|
|
53
|
+
* @returns {import('../geometry/types.js').Shape} The gecko.
|
|
54
|
+
*/
|
|
55
|
+
export const gecko = (spinePts, legs, { w = geckoW, legW = 36, toe = {}, head: [hrx, hry] = [62, 74], eyeAt: [eb, eo, er] = [18, 34, 12] } = {}) => {
|
|
56
|
+
const sp = spline(spinePts, 12), [a, b] = spinePts, L = Math.hypot(b[0] - a[0], b[1] - a[1]), d = [(b[0] - a[0]) / L, (b[1] - a[1]) / L], n = [-d[1], d[0]];
|
|
57
|
+
const hc = [a[0] + d[0] * 32, a[1] + d[1] * 32], th = (Math.atan2(d[1], d[0]) * 180) / Math.PI;
|
|
58
|
+
const body = stroke(sp, (i, k) => w(i / k), { roundCaps: true, chunk: 6 }), head = ellipse(hc[0], hc[1], hrx, hry, th - 90);
|
|
59
|
+
const limbs = U(...legs.map(([s0, k, f]) => U(stroke([s0, k, f], legW, { roundCaps: true }), circle(f[0], f[1], 30, 48), // the palm: closes the pin-holes between toe roots
|
|
60
|
+
toes(f[0], f[1], (Math.atan2(f[1] - k[1], f[0] - k[0]) * 180) / Math.PI, toe.n, toe.len, toe.w, toe.pad))));
|
|
61
|
+
const ey = (sg) => eye(hc[0] - d[0] * eb + n[0] * eo * sg, hc[1] - d[1] * eb + n[1] * eo * sg, er);
|
|
62
|
+
return D(U(body, head, limbs), ey(1), ey(-1)); };
|
|
63
|
+
|
|
64
|
+
/** The house two tracks for the Track animals: core's duo(22), 26 wide and 48 apart. */
|
|
65
|
+
export const DUO = duo(22);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Outline points drawn k times smaller about (300, 300), the fillets kept: so a mark whose drawing runs long fits to the
|
|
69
|
+
* same track weight as the rest.
|
|
70
|
+
*
|
|
71
|
+
* @param {readonly (readonly number[])[]} pts The points.
|
|
72
|
+
* @param {number} k The scale.
|
|
73
|
+
* @returns {import('../geometry/types.js').Point[]} The scaled points.
|
|
74
|
+
*/
|
|
75
|
+
export const sc = (pts, k) => pts.map(([x, y]) => [300 + (x - 300) * k, 300 + (y - 300) * k]);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A solid drawn k times smaller about (300, 300), as sc() does for points.
|
|
79
|
+
*
|
|
80
|
+
* @param {import('../geometry/types.js').Geom} g The solid.
|
|
81
|
+
* @param {number} k The scale.
|
|
82
|
+
* @returns {import('../geometry/types.js').Shape} The scaled solid.
|
|
83
|
+
*/
|
|
84
|
+
export const scaleS = (g, k) => scale(g, k, 300, 300);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Two tracks round a filleted outline.
|
|
88
|
+
*
|
|
89
|
+
* @param {readonly (readonly number[])[]} o The outline (the loop's centreline).
|
|
90
|
+
* @returns {import('../geometry/types.js').Shape} The loop.
|
|
91
|
+
*/
|
|
92
|
+
export const loop2 = (o) => trackLoop(o, DUO);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Two tracks along an open line, round ends.
|
|
96
|
+
*
|
|
97
|
+
* @param {readonly (readonly number[])[]} pts The line.
|
|
98
|
+
* @returns {import('../geometry/types.js').Shape} The tracks.
|
|
99
|
+
*/
|
|
100
|
+
export const line2 = (pts) => tracks(pts, DUO);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Where solids may sit inside a two-track loop: the band's half-width (37) plus the gap (22) in from the centreline, and
|
|
104
|
+
* 1 to spare.
|
|
105
|
+
*
|
|
106
|
+
* @param {readonly (readonly number[])[]} o The loop's centreline.
|
|
107
|
+
* @returns {import('../geometry/types.js').Shape} The room inside.
|
|
108
|
+
*/
|
|
109
|
+
export const inside = (o) => grow(poly(o), -60);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A solid at the end of a line, oriented along its last heading: an ellipse `len` long and `wid` across, centred `ahead`
|
|
113
|
+
* past the end (Track Snake's head).
|
|
114
|
+
*
|
|
115
|
+
* @param {readonly (readonly number[])[]} pts The line.
|
|
116
|
+
* @param {number} len The head's length.
|
|
117
|
+
* @param {number} wid Its width.
|
|
118
|
+
* @param {number} ahead How far past the end its centre sits.
|
|
119
|
+
* @returns {[import('../geometry/types.js').Polygon, import('../geometry/types.js').Point, number]} [shape, centre, heading in degrees].
|
|
120
|
+
*/
|
|
121
|
+
export const endHead = (pts, len, wid, ahead) => { const a = pts.at(-2), b = pts.at(-1), h = Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI, c = polar(ahead, h, b[0], b[1]);
|
|
122
|
+
return [ellipse(c[0], c[1], len / 2, wid / 2, h), c, h]; };
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* An eye as near (x, y) as the loop `o` allows: the nearest point inside it with the band and the gap (59) plus a little
|
|
126
|
+
* clear all round (a grid search, so it is deterministic); warns when no point within 140 has that room.
|
|
127
|
+
*
|
|
128
|
+
* @param {readonly (readonly number[])[]} o The loop's centreline.
|
|
129
|
+
* @param {readonly number[]} at Where the eye should be, [x, y].
|
|
130
|
+
* @param {number} [r=16] Its radius.
|
|
131
|
+
* @returns {import('../geometry/types.js').Polygon} The eye.
|
|
132
|
+
*/
|
|
133
|
+
export const eyeIn = (o, [x, y], r = 16) => { const inPoly = (px, py) => { let c = false; for (let i = 0, j = o.length - 1; i < o.length; j = i++) { const [a, b] = [o[i], o[j]]; if ((a[1] > py) !== (b[1] > py) && px < ((b[0] - a[0]) * (py - a[1])) / (b[1] - a[1]) + a[0]) c = !c; } return c; };
|
|
134
|
+
let best = null, room = [-1]; for (let px = x - 140; px <= x + 140; px += 2) for (let py = y - 140; py <= y + 140; py += 2) { if (!inPoly(px, py)) continue;
|
|
135
|
+
const d = Math.min(...o.map((p) => Math.hypot(p[0] - px, p[1] - py))), off = Math.hypot(px - x, py - y); if (d > room[0]) room = [d, px, py]; if (d >= 59 + r + 4 && (!best || off < best[0])) best = [off, px, py]; }
|
|
136
|
+
if (!best) { console.warn(` no room for an eye near ${x},${y}: ${(room[0] - 37 - r).toFixed(0)} clear at best`); best = room; }
|
|
137
|
+
return circle(best[1], best[2], r); };
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A two-track loop round a stadium centred (cx, cy), `len` long and `wid` across (its ends half circles, so the inner
|
|
141
|
+
* track keeps a radius of wid/2 − 24), turned deg. An ellipse would not do: its ends tighten and pinch the inner track.
|
|
142
|
+
*
|
|
143
|
+
* @param {number} cx The centre, x.
|
|
144
|
+
* @param {number} cy The centre, y.
|
|
145
|
+
* @param {number} len The length.
|
|
146
|
+
* @param {number} wid The width.
|
|
147
|
+
* @param {number} [deg=0] The turn, in degrees.
|
|
148
|
+
* @returns {import('../geometry/types.js').Shape} The loop.
|
|
149
|
+
*/
|
|
150
|
+
export const stadium2 = (cx, cy, len, wid, deg = 0) => { const r = wid / 2, h = len / 2 - r, a = deg * Math.PI / 180;
|
|
151
|
+
const pts = [...sample((t) => polar(r, t, h, 0), -90, 90, 60), ...sample((t) => polar(r, t, -h, 0), 90, 270, 60)];
|
|
152
|
+
return loop2(pts.map(([x, y]) => [cx + x * Math.cos(a) - y * Math.sin(a), cy + x * Math.sin(a) + y * Math.cos(a)])); };
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A cloud loop's centreline (a sheep's fleece, a cloud): a ring of circles [x, y, r] listed clockwise round the middle,
|
|
156
|
+
* each dip between neighbours filleted r, as points (convex arcs of the circles, concave arcs of radius r between them).
|
|
157
|
+
*
|
|
158
|
+
* @param {readonly (readonly number[])[]} cs The circles, [x, y, r], clockwise.
|
|
159
|
+
* @param {number} [r=60] The dips' fillet radius.
|
|
160
|
+
* @returns {import('../geometry/types.js').Point[]} The centreline.
|
|
161
|
+
*/
|
|
162
|
+
export function cloud(cs, r = 60) { const n = cs.length, G = [cs.reduce((s, c) => s + c[0], 0) / n, cs.reduce((s, c) => s + c[1], 0) / n];
|
|
163
|
+
const fil = cs.map((A, i) => { const B = cs[(i + 1) % n], ra = A[2] + r, rb = B[2] + r, dx = B[0] - A[0], dy = B[1] - A[1], d = Math.hypot(dx, dy), a = (ra * ra - rb * rb + d * d) / (2 * d), h = Math.sqrt(ra * ra - a * a);
|
|
164
|
+
const m = [A[0] + (a * dx) / d, A[1] + (a * dy) / d], P = [[m[0] - (h * dy) / d, m[1] + (h * dx) / d], [m[0] + (h * dy) / d, m[1] - (h * dx) / d]];
|
|
165
|
+
return P.sort((p, q) => Math.hypot(q[0] - G[0], q[1] - G[1]) - Math.hypot(p[0] - G[0], p[1] - G[1]))[0]; });
|
|
166
|
+
const ang = (c, p) => Math.atan2(p[1] - c[1], p[0] - c[0]), out = [];
|
|
167
|
+
cs.forEach((A, i) => { const Fin = fil[(i - 1 + n) % n], Fout = fil[i]; let a0 = ang(A, Fin), a1 = ang(A, Fout); while (a1 <= a0) a1 += 2 * Math.PI;
|
|
168
|
+
for (let t = 0; t <= 1; t += 1 / Math.ceil(((a1 - a0) * A[2]) / 6)) out.push([A[0] + A[2] * Math.cos(a0 + (a1 - a0) * t), A[1] + A[2] * Math.sin(a0 + (a1 - a0) * t)]);
|
|
169
|
+
const B = cs[(i + 1) % n]; let b0 = ang(Fout, A), b1 = ang(Fout, B); while (b1 >= b0) b1 -= 2 * Math.PI; if (b0 - b1 > Math.PI) b1 += 2 * Math.PI;
|
|
170
|
+
for (let t = 0; t <= 1; t += 1 / Math.ceil((Math.abs(b0 - b1) * r) / 6)) out.push([Fout[0] + r * Math.cos(b0 + (b1 - b0) * t), Fout[1] + r * Math.sin(b0 + (b1 - b0) * t)]); });
|
|
171
|
+
return out.filter((p, i) => Math.hypot(p[0] - out[(i + 1) % out.length][0], p[1] - out[(i + 1) % out.length][1]) > 1); }
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A smooth closed outline through control points.
|
|
175
|
+
*
|
|
176
|
+
* @param {readonly (readonly number[])[]} pts The control points.
|
|
177
|
+
* @param {number} [n=16] Spline points a span.
|
|
178
|
+
* @returns {import('../geometry/types.js').Polygon} The outline.
|
|
179
|
+
*/
|
|
180
|
+
export const S = (pts, n = 16) => poly(splineClosed(pts, n));
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A tapering stroke along a smooth curve through `pts`, w0 wide at the start to w1 at the end, round ends.
|
|
184
|
+
*
|
|
185
|
+
* @param {readonly (readonly number[])[]} pts The knots.
|
|
186
|
+
* @param {number} w0 The width at the start.
|
|
187
|
+
* @param {number} w1 The width at the end.
|
|
188
|
+
* @param {number} [n=10] Spline points a span.
|
|
189
|
+
* @returns {import('../geometry/types.js').Shape} The stroke.
|
|
190
|
+
*/
|
|
191
|
+
export const taper = (pts, w0, w1, n = 10) => stroke(spline(pts, n), (i, k) => w0 + (w1 - w0) * (i / k), { roundCaps: true, chunk: 6 });
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A cut g wide along a smooth curve through `pts` (a folded wing's edge, a stripe, a whorl), round ends. Run it off the
|
|
195
|
+
* outline or stop it inside; never let it graze an edge at a tangent (that leaves a needle).
|
|
196
|
+
*
|
|
197
|
+
* @param {readonly (readonly number[])[]} pts The knots.
|
|
198
|
+
* @param {number} g The gap.
|
|
199
|
+
* @returns {import('../geometry/types.js').Shape} The cut.
|
|
200
|
+
*/
|
|
201
|
+
export const seam = (pts, g) => stroke(spline(pts, 10), g, { roundCaps: true, chunk: 6 });
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A symmetric outline about x = cx from its right half (top centre round to bottom centre).
|
|
205
|
+
*
|
|
206
|
+
* @param {readonly (readonly number[])[]} half The right half.
|
|
207
|
+
* @param {number} [cx=C] The axis.
|
|
208
|
+
* @returns {import('../geometry/types.js').Point[]} The whole outline.
|
|
209
|
+
*/
|
|
210
|
+
export const mirror = (half, cx = C) => [...half, ...half.slice(1, -1).reverse().map(([x, y]) => [2 * cx - x, y])];
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Points on the circular arc from P to Q bulging h to the left of P→Q (negative h bulges right; 0 is the straight line).
|
|
214
|
+
*
|
|
215
|
+
* @param {readonly number[]} P The start.
|
|
216
|
+
* @param {readonly number[]} Q The end.
|
|
217
|
+
* @param {number} h The bulge.
|
|
218
|
+
* @param {number} [n=40] Steps.
|
|
219
|
+
* @returns {import('../geometry/types.js').Point[]} The arc.
|
|
220
|
+
*/
|
|
221
|
+
export const bow = (P, Q, h, n = 40) => { const L = Math.hypot(Q[0] - P[0], Q[1] - P[1]), u = [(Q[0] - P[0]) / L, (Q[1] - P[1]) / L], nl = [u[1], -u[0]];
|
|
222
|
+
if (Math.abs(h) < 1e-6) return sample((t) => [P[0] + (Q[0] - P[0]) * t, P[1] + (Q[1] - P[1]) * t], 0, 1, n);
|
|
223
|
+
const R = (L * L / 4 + h * h) / (2 * h), m = [(P[0] + Q[0]) / 2, (P[1] + Q[1]) / 2], c = [m[0] - nl[0] * (R - h), m[1] - nl[1] * (R - h)];
|
|
224
|
+
const a0 = Math.atan2(P[1] - c[1], P[0] - c[0]), a1 = Math.atan2(Q[1] - c[1], Q[0] - c[0]); let d = a1 - a0;
|
|
225
|
+
const mid = (t) => [c[0] + Math.abs(R) * Math.cos(a0 + d * t), c[1] + Math.abs(R) * Math.sin(a0 + d * t)];
|
|
226
|
+
while (d > Math.PI) d -= 2 * Math.PI; while (d < -Math.PI) d += 2 * Math.PI;
|
|
227
|
+
const test = mid(0.5); if (Math.hypot(test[0] - (m[0] + nl[0] * h), test[1] - (m[1] + nl[1] * h)) > Math.abs(h) * 0.5 + 1) d = d > 0 ? d - 2 * Math.PI : d + 2 * Math.PI;
|
|
228
|
+
return sample(mid, 0, 1, n); };
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A sickle wing: from the front of its root `p0` along an arc bulging h0 to the tip, back along an arc bulging h1 to the
|
|
232
|
+
* back of its root `p1` (positive bulges to the left of each run).
|
|
233
|
+
*
|
|
234
|
+
* @param {readonly number[]} p0 The root's front.
|
|
235
|
+
* @param {readonly number[]} tip The tip.
|
|
236
|
+
* @param {readonly number[]} p1 The root's back.
|
|
237
|
+
* @param {number} h0 The outward run's bulge.
|
|
238
|
+
* @param {number} h1 The return run's bulge.
|
|
239
|
+
* @returns {import('../geometry/types.js').Polygon} The wing.
|
|
240
|
+
*/
|
|
241
|
+
export const sickle = (p0, tip, p1, h0, h1) => poly([...bow(p0, tip, h0), ...bow(tip, p1, h1).slice(1)]);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The near wing laid over the body and parted from it only by a straight cut g wide across its root, a→b (clipped to the
|
|
245
|
+
* wing, so the body's own outline is never notched).
|
|
246
|
+
*
|
|
247
|
+
* @param {import('../geometry/types.js').Geom} body The body.
|
|
248
|
+
* @param {import('../geometry/types.js').Geom} wing The wing.
|
|
249
|
+
* @param {readonly number[]} a The cut's start.
|
|
250
|
+
* @param {readonly number[]} b The cut's end.
|
|
251
|
+
* @param {number} g The gap.
|
|
252
|
+
* @returns {import('../geometry/types.js').Shape} Body and wing.
|
|
253
|
+
*/
|
|
254
|
+
export const rootCut = (body, wing, a, b, g) => D(U(body, wing), I(bar(a, b, g), wing));
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Cuts g wide straight across a curve through `pts` at each fraction t of its length, reaching `reach` either side (rings
|
|
258
|
+
* round a tail, segments of a body, a stripe across a flank). Square to the curve, so each cut crosses its edges squarely.
|
|
259
|
+
*
|
|
260
|
+
* @param {readonly (readonly number[])[]} pts The curve's knots.
|
|
261
|
+
* @param {readonly number[]} ts Where to cut, as fractions of its length.
|
|
262
|
+
* @param {number} reach How far each cut runs either side.
|
|
263
|
+
* @param {number} g The gap.
|
|
264
|
+
* @returns {import('../geometry/types.js').Shape} The cuts.
|
|
265
|
+
*/
|
|
266
|
+
export const bands = (pts, ts, reach, g) => { const sp = spline(pts, 12), N = sp.length - 1;
|
|
267
|
+
return U(...ts.map((t) => { const i = Math.round(t * N), a = sp[Math.max(0, i - 1)], b = sp[Math.min(N, i + 1)], L = Math.hypot(b[0] - a[0], b[1] - a[1]), n = [-(b[1] - a[1]) / L, (b[0] - a[0]) / L];
|
|
268
|
+
return bar([sp[i][0] - n[0] * reach, sp[i][1] - n[1] * reach], [sp[i][0] + n[0] * reach, sp[i][1] + n[1] * reach], g); })); };
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* A leg from hip [x, y] straight down to the ground at y `gy`, w wide, its foot squared off flat (round heel and toe, r)
|
|
272
|
+
* and pointing the way `dir` says (1 right, −1 left) by `toe` past the leg: legs that stand on a line need flat feet.
|
|
273
|
+
*
|
|
274
|
+
* @param {readonly number[]} hip The hip, [x, y].
|
|
275
|
+
* @param {number} gy The ground.
|
|
276
|
+
* @param {number} [w=54] The leg's width.
|
|
277
|
+
* @param {number} [dir=1] The way the foot points.
|
|
278
|
+
* @param {number} [toe=14] How far it reaches past the leg.
|
|
279
|
+
* @param {number} [r=14] The heel's and toe's radius.
|
|
280
|
+
* @returns {import('../geometry/types.js').Shape} The leg.
|
|
281
|
+
*/
|
|
282
|
+
export const flatLeg = ([x, y], gy, w = 54, dir = 1, toe = 14, r = 14) => U(pill([x, y], [x, gy - w / 2], w),
|
|
283
|
+
rr(Math.min(x - w / 2, x - w / 2 + dir * toe), gy - w / 2 - 2, w + toe, w / 2 + 2, [0, 0, r, r]));
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* A dog's bone L long between its knuckle centres, w across the shaft, each end two knuckles of radius r; turned deg
|
|
287
|
+
* about its centre (cx, cy).
|
|
288
|
+
*
|
|
289
|
+
* @param {number} cx The centre, x.
|
|
290
|
+
* @param {number} cy The centre, y.
|
|
291
|
+
* @param {number} L The length between knuckle centres.
|
|
292
|
+
* @param {number} w The shaft's width.
|
|
293
|
+
* @param {number} r The knuckles' radius.
|
|
294
|
+
* @param {number} [deg=0] The turn, in degrees.
|
|
295
|
+
* @returns {import('../geometry/types.js').Shape} The bone.
|
|
296
|
+
*/
|
|
297
|
+
export const bone = (cx, cy, L, w, r, deg = 0) => { const s = (w / 2 + r) * 0.62;
|
|
298
|
+
return rot(U(rr(cx - L / 2, cy - w / 2, L, w, 0), ...[-1, 1].flatMap((sx) => [-1, 1].map((sy) => circle(cx + sx * L / 2, cy + sy * s, r, 120)))), deg, cx, cy); };
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* A paw print centred (cx, cy), about 300 across at k = 1: the heel pad a rounded triangle, four toe pads fanned over it.
|
|
302
|
+
*
|
|
303
|
+
* @param {number} cx The centre, x.
|
|
304
|
+
* @param {number} cy The centre, y.
|
|
305
|
+
* @param {number} [k=1] The scale.
|
|
306
|
+
* @returns {import('../geometry/types.js').Shape} The print.
|
|
307
|
+
*/
|
|
308
|
+
export const pawPrint = (cx, cy, k = 1) => U(S([[0, 10], [70, 30], [104, 96], [70, 140], [0, 128], [-70, 140], [-104, 96], [-70, 30]].map(([x, y]) => [cx + x * k, cy + y * k])),
|
|
309
|
+
...[[-118, -36, 34, 44, -20], [-46, -100, 36, 48, -6], [46, -100, 36, 48, 6], [118, -36, 34, 44, 20]].map(([x, y, rx, ry, a]) => ellipse(cx + x * k, cy + y * k, rx * k, ry * k, a)));
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* A solid kept a gap clear of a loop's two tracks: the band (37 from the centreline) plus the gap (22).
|
|
313
|
+
*
|
|
314
|
+
* @param {import('../geometry/types.js').Geom} solid The solid.
|
|
315
|
+
* @param {readonly (readonly number[])[]} o The loop's centreline.
|
|
316
|
+
* @returns {import('../geometry/types.js').Shape} What's left of the solid.
|
|
317
|
+
*/
|
|
318
|
+
export const clearOf = (solid, o) => D(solid, grow(poly(o), 59));
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* A shape moved by (dx, dy).
|
|
322
|
+
*
|
|
323
|
+
* @param {import('../geometry/types.js').Shape} sh The shape.
|
|
324
|
+
* @param {number} dx The move, x.
|
|
325
|
+
* @param {number} dy The move, y.
|
|
326
|
+
* @returns {import('../geometry/types.js').Shape} The moved shape.
|
|
327
|
+
*/
|
|
328
|
+
export const moved = (sh, dx, dy) => sh.map((pg) => pg.map((r) => r.map(([x, y]) => [x + dx, y + dy])));
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* A closed polyline resampled to points `step` apart along it (as filletClosed()'s are), so trackLoop()'s normals stay
|
|
332
|
+
* smooth.
|
|
333
|
+
*
|
|
334
|
+
* @param {readonly (readonly number[])[]} pts The closed polyline (open: no repeated first point).
|
|
335
|
+
* @param {number} step The spacing.
|
|
336
|
+
* @returns {import('../geometry/types.js').Point[]} The resampled points.
|
|
337
|
+
*/
|
|
338
|
+
export const evenPts = (pts, step) => { const n = pts.length, L = [0]; for (let i = 0; i < n; i++) { const a = pts[i], b = pts[(i + 1) % n]; L.push(L[i] + Math.hypot(b[0] - a[0], b[1] - a[1])); }
|
|
339
|
+
const out = [], m = Math.round(L[n] / step); let j = 0;
|
|
340
|
+
for (let k = 0; k < m; k++) { const d = (k * L[n]) / m; while (L[j + 1] < d) j++; const a = pts[j], b = pts[(j + 1) % n], t = (d - L[j]) / (L[j + 1] - L[j] || 1); out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]); }
|
|
341
|
+
return out; };
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A ribbon loop's centreline (a squirrel's tail, a rabbit's moon): the spine splined, offset by half the width wf(t)
|
|
345
|
+
* (t 0 → 1 along it) each side, round caps at both ends, resampled 8 apart. Keep the spine's radius over wf/2 + 40.
|
|
346
|
+
*
|
|
347
|
+
* @param {readonly (readonly number[])[]} spine The spine's knots.
|
|
348
|
+
* @param {(t: number) => number} wf The width along it.
|
|
349
|
+
* @param {number} [n=12] Spline points a span.
|
|
350
|
+
* @returns {import('../geometry/types.js').Point[]} The loop's centreline.
|
|
351
|
+
*/
|
|
352
|
+
export const ribbon = (spine, wf, n = 12) => { const sp = spline(spine, n), N = sp.length - 1;
|
|
353
|
+
const hd = sp.map((p, i) => { const a = sp[Math.max(0, i - 1)], b = sp[Math.min(N, i + 1)]; return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI; });
|
|
354
|
+
const side = (s) => sp.map((p, i) => polar(wf(i / N) / 2, hd[i] + s * 90, ...p));
|
|
355
|
+
const cap = (i, from) => sample((t) => polar(wf(i / N) / 2, from - 180 * t, ...sp[i]), 0, 1, 24).slice(1, -1);
|
|
356
|
+
return evenPts([...side(1), ...cap(N, hd[N] + 90), ...side(-1).reverse(), ...cap(0, hd[0] - 90)], 8); };
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Whether a point is inside a shape (even-odd over every ring; a polygon or a list of them).
|
|
360
|
+
*
|
|
361
|
+
* @param {import('../geometry/types.js').Geom} sh The shape.
|
|
362
|
+
* @param {readonly number[]} p The point.
|
|
363
|
+
* @returns {boolean} True inside.
|
|
364
|
+
*/
|
|
365
|
+
export const inShape = (sh, [px, py]) => { let c = false;
|
|
366
|
+
for (const pg of typeof sh[0][0][0] === 'number' ? [sh] : sh) for (const r of pg) for (let i = 0, j = r.length - 1; i < r.length; j = i++) { const [a, b] = [r[i], r[j]];
|
|
367
|
+
if ((a[1] > py) !== (b[1] > py) && px < ((b[0] - a[0]) * (py - a[1])) / (b[1] - a[1]) + a[0]) c = !c; }
|
|
368
|
+
return c; };
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* A head side-on in headphones, layered back to front: `behind`, the band, the head, the near cup and the band's run up
|
|
372
|
+
* from it, the band re-laid whole where it clears the head. `o`: head (a solid or (g) => one, ears in front of the band
|
|
373
|
+
* included), rimOn (the outline the band clears; default head), cupY or cupC, cupIn, cw, ch, tilt, bw, lift (gaps), x1
|
|
374
|
+
* (where the band lands on the forehead: put it under an ear in front of the band, or the band shows as a sliver past the
|
|
375
|
+
* ear), holes (g) => […], parts (g) => […] parted by the gap, behind (g) => […]. With `even`, the band's straight run up
|
|
376
|
+
* from the cup is sampled as finely as its arc (an uneven spline leaves a tooth on the run's back edge), and the run in
|
|
377
|
+
* front of the head is cut level 1.5 gaps over the head's highest point under it.
|
|
378
|
+
*
|
|
379
|
+
* @param {import('./animals.js').SidePhonesOptions} o The head and the phones.
|
|
380
|
+
* @returns {(g: number) => import('../geometry/types.js').Shape} The build, given the gap.
|
|
381
|
+
*/
|
|
382
|
+
export const sidePhones = (o) => (g) => {
|
|
383
|
+
const head = typeof o.head === 'function' ? o.head(g) : o.head, { bw = 38, cw = 88, ch = 156, tilt = 0, lift = 1.6, cupY = 10, cupIn = 18, x1 } = o, base = o.rimOn || head, off = lift * g + bw / 2;
|
|
384
|
+
const scan = (f) => { for (let t = -1200; t < 1200; t += 2) if (inShape(base, f(t))) return t; };
|
|
385
|
+
const P0 = o.cupC || [scan((x) => [x, cupY]) + cupIn, cupY], E = [x1, scan((y) => [x1, y]) + 10], cy = Math.min(E[1], P0[1] - ch / 2);
|
|
386
|
+
const mx = (P0[0] + E[0]) / 2, rx = (E[0] - P0[0]) / 2, N = 40;
|
|
387
|
+
const ell = (ry) => [...Array(N + 1)].map((_, i) => { const t = Math.PI + (Math.PI * i) / N; return [mx + rx * Math.cos(t), cy + ry * Math.sin(t)]; });
|
|
388
|
+
let ry = 30; while (ry < 900 && ell(ry).slice(N / 4, (3 * N) / 4 + 1).some(([x, y]) => inShape(base, [x, y + off]))) ry += 3;
|
|
389
|
+
const arcP = ell(ry), st = Math.hypot(arcP[1][0] - arcP[0][0], arcP[1][1] - arcP[0][1]), n = o.even ? Math.max(1, Math.round((P0[1] - cy) / st)) : 1;
|
|
390
|
+
const path = [...[...Array(n)].map((_, i) => [P0[0], P0[1] + ((cy - P0[1]) * i) / n]), ...arcP, ...(E[1] > cy + 5 ? [E] : [])];
|
|
391
|
+
const bd = stroke(spline(path, 16), bw, { roundCaps: true }), cup = rot(rr(P0[0] - cw / 2, P0[1] - ch / 2, cw, ch, cw / 2), tilt, P0[0], P0[1]);
|
|
392
|
+
const face = (o.parts ? o.parts(g) : []).reduce((f, p) => over(f, p, g), D(head, ...(o.holes ? o.holes(g) : [])));
|
|
393
|
+
const near = D(bd, rect(mx, -3000, 6000, 6000)), tops = [];
|
|
394
|
+
if (o.even) for (let x = P0[0] - bw / 2; x <= P0[0] + bw / 2; x += 2) { const t = scan((y) => [x, y]); if (t !== undefined) tops.push(t); }
|
|
395
|
+
const front = U(cup, o.even ? I(near, rect(-3000, Math.min(...tops) - 1.5 * g, 6000, 6000)) : I(near, grown(base, 1.5 * g)));
|
|
396
|
+
const s = over(over(over(o.behind ? U(...o.behind(g)) : [], bd, g), face, g), front, g);
|
|
397
|
+
return U(s, D(bd, grown(head, g))); };
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* sidePhones() drawn at k, turned deg and moved to (dx, dy), the gap kept g at the mark's scale.
|
|
401
|
+
*
|
|
402
|
+
* @param {import('./animals.js').SidePhonesOptions} o sidePhones()'s options.
|
|
403
|
+
* @param {number} k The scale.
|
|
404
|
+
* @param {number} dx The move, x.
|
|
405
|
+
* @param {number} dy The move, y.
|
|
406
|
+
* @param {number} [deg=0] The turn, in degrees.
|
|
407
|
+
* @returns {(g: number) => import('../geometry/types.js').Shape} The build, given the gap.
|
|
408
|
+
*/
|
|
409
|
+
export const wearPhones = (o, k, dx, dy, deg = 0) => (g) => move(rot(scale(sidePhones(o)(g / k), k, 0, 0), deg, 0, 0), dx, dy);
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Layers back to front, each laid over() the ones before it by the gap.
|
|
413
|
+
*
|
|
414
|
+
* @param {number} g The gap.
|
|
415
|
+
* @param {...import('../geometry/types.js').Geom} layers The layers, back first.
|
|
416
|
+
* @returns {import('../geometry/types.js').Shape} The stack.
|
|
417
|
+
*/
|
|
418
|
+
export const layered = (g, ...layers) => layers.reduce((s, l) => over(s, l, g));
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* A cat's or a fox's head side-on round the crown's centre, facing right: the snout s further forward (0 a cat's), the
|
|
422
|
+
* pointed ear in front of the band (x1 under it), for sidePhones().
|
|
423
|
+
*
|
|
424
|
+
* @param {number} s How far the snout runs forward.
|
|
425
|
+
* @returns {{ head: import('../geometry/types.js').Shape, rimOn: import('../geometry/types.js').Polygon, x1: number }} sidePhones()'s head, rimOn and x1.
|
|
426
|
+
*/
|
|
427
|
+
export const pointyHead = (s) => { const base = S([[-120, 25], [-110, -55], [-45, -100], [45, -98], [110, -55], [148 + 0.4 * s, 5], [150 + s, 30], [130 + s, 52], [92, 82], [10, 96], [-80, 82]]);
|
|
428
|
+
return { head: U(base, roundPoly([[10, -90], [70, -230], [112, -50]], [20, 20, 20])), rimOn: base, x1: 100 }; };
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* ∪ : a shut eye for a whole animal side-on, its radius at least 1.2 strokes: a mark drawn smaller has a wider gap in
|
|
432
|
+
* drawing units, and a tight arc blots into a heart.
|
|
433
|
+
*
|
|
434
|
+
* @param {number} x The eye, x.
|
|
435
|
+
* @param {number} y The eye, y.
|
|
436
|
+
* @param {number} r Its radius.
|
|
437
|
+
* @param {number} w Its stroke.
|
|
438
|
+
* @returns {import('../geometry/types.js').Shape} The eye.
|
|
439
|
+
*/
|
|
440
|
+
export const shut = (x, y, r, w) => arc(x, y, Math.max(r, 1.2 * w), 20, 160, w);
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* A long head side-on round the crown's centre, facing right (a goat's, a donkey's, a kangaroo's): the muzzle s further
|
|
444
|
+
* forward and narrowing.
|
|
445
|
+
*
|
|
446
|
+
* @param {number} [s=0] How far the muzzle runs forward.
|
|
447
|
+
* @returns {import('../geometry/types.js').Polygon} The head.
|
|
448
|
+
*/
|
|
449
|
+
export const longBase = (s = 0) => S([[-100, 20], [-95, -50], [-40, -90], [40, -85], [110, -40], [175 + s, 5], [190 + s, 35], [170 + s, 60], [90, 65], [10, 75], [-60, 62]]);
|
|
450
|
+
|
|
451
|
+
/** A bird's head side-on round the crown's centre, facing right, for a bird in headphones (the beak is the mark's). */
|
|
452
|
+
export const birdHead = S([[-90, 20], [-85, -50], [-30, -90], [40, -80], [95, -30], [110, 20], [70, 60], [-10, 70], [-60, 60]]);
|
|
453
|
+
|
|
454
|
+
/** A leg standing from y0 to the ground at y1, w wide. */
|
|
455
|
+
const standLeg = (x, y0, y1, w) => pill([x, y0], [x, y1], w);
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Four legs standing from y0 to the ground at y1, w wide: the back pair from xb, the front pair from xf, each pair a leg's
|
|
459
|
+
* width plus the gap apart.
|
|
460
|
+
*
|
|
461
|
+
* @param {number} xb The back pair, x.
|
|
462
|
+
* @param {number} xf The front pair, x.
|
|
463
|
+
* @param {number} y0 The legs' top.
|
|
464
|
+
* @param {number} y1 The ground.
|
|
465
|
+
* @param {number} w Their width.
|
|
466
|
+
* @param {number} g The gap.
|
|
467
|
+
* @returns {import('../geometry/types.js').Shape} The legs.
|
|
468
|
+
*/
|
|
469
|
+
export const legs4 = (xb, xf, y0, y1, w, g) => U(standLeg(xb, y0, y1, w), standLeg(xb + w + g, y0, y1, w), standLeg(xf, y0, y1, w), standLeg(xf + w + g, y0, y1, w));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Geom, Point, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** Points along a helix strand down the y axis (s = ±1 picks the strand). */
|
|
4
|
+
export function strandPts(s: number, o?: { H?: number; A?: number; turns?: number; ph?: number; y0?: number; n?: number }): Point[];
|
|
5
|
+
/** A stroke tapering from w0 at the first point to w1 at the last. */
|
|
6
|
+
export function tapered(pts: readonly (readonly number[])[], w0: number, w1: number): Shape;
|
|
7
|
+
/** A comet: a head disc and a soft tapering tail. */
|
|
8
|
+
export function comet(head: readonly number[], r: number, dir: number, len: number, o?: { bend?: number; wave?: number; tip?: number; n?: number }): Shape;
|
|
9
|
+
/** Discs [x, y, r] packed back to front, each cut free by the gap. */
|
|
10
|
+
export function cells(list: readonly (readonly number[])[], g: number): Shape;
|
|
11
|
+
/** A ring w wide round a disc of radius R, with `inner` inside it. */
|
|
12
|
+
export function zona(R: number, w: number, inner: Geom, g: number): Shape;
|
|
13
|
+
/** A chromatid: a bowed sausage L long, w wide. */
|
|
14
|
+
export function chromatid(L: number, w: number, bow: number): Shape;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Biology: embryo cells, a comet (an abstract sperm), helix strands and chromatids, drawn about the origin in canvas units
|
|
3
|
+
* and placed by the mark. From the Logomarks tools (bioparts.mjs, batch 204).
|
|
4
|
+
*/
|
|
5
|
+
import { U, D, circle, stroke, sample, over } from '../geometry/index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Points along a helix strand down the y axis: H tall from y0, amplitude A, `turns` full turns, phase `ph` (s = ±1 picks
|
|
9
|
+
* the strand).
|
|
10
|
+
*
|
|
11
|
+
* @param {number} s The strand: 1 or −1.
|
|
12
|
+
* @param {{ H?: number, A?: number, turns?: number, ph?: number, y0?: number, n?: number }} [o] The helix.
|
|
13
|
+
* @returns {import('../geometry/types.js').Point[]} The strand's points.
|
|
14
|
+
*/
|
|
15
|
+
export const strandPts = (s, { H = 448, A = 130, turns = 1, ph = 0, y0 = -224, n = 200 } = {}) =>
|
|
16
|
+
sample((t) => [s * A * Math.sin(2 * Math.PI * turns * t + ph), y0 + H * t], 0, 1, n);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A stroke whose width runs from w0 at the first point to w1 at the last (a comet's tail, a needle).
|
|
20
|
+
*
|
|
21
|
+
* @param {readonly (readonly number[])[]} pts The line.
|
|
22
|
+
* @param {number} w0 The width at the start.
|
|
23
|
+
* @param {number} w1 The width at the end.
|
|
24
|
+
* @returns {import('../geometry/types.js').Shape} The stroke.
|
|
25
|
+
*/
|
|
26
|
+
export const tapered = (pts, w0, w1) => stroke(pts, (i, n) => w0 + ((w1 - w0) * i) / (n - 1), { chunk: 8 });
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A comet: a head disc of radius r at `head` and a tail running away from it along `dir` (degrees), `len` long, bowing by
|
|
30
|
+
* `bend` (degrees, one arc) and waving by `wave` (degrees, one S), tapering from the head's width to `tip`. The abstract
|
|
31
|
+
* stand-in for a sperm: a dot and a short soft tail, never a realistic one.
|
|
32
|
+
*
|
|
33
|
+
* @param {readonly number[]} head The head's centre.
|
|
34
|
+
* @param {number} r The head's radius.
|
|
35
|
+
* @param {number} dir The tail's direction, in degrees.
|
|
36
|
+
* @param {number} len The tail's length.
|
|
37
|
+
* @param {{ bend?: number, wave?: number, tip?: number, n?: number }} [o] The tail's bow, wave, tip width and steps.
|
|
38
|
+
* @returns {import('../geometry/types.js').Shape} The comet.
|
|
39
|
+
*/
|
|
40
|
+
export const comet = (head, r, dir, len, { bend = 40, wave = 0, tip = 12, n = 40 } = {}) => {
|
|
41
|
+
const pts = [head]; let [x, y] = head, a = dir;
|
|
42
|
+
for (let i = 1; i <= n; i++) { const t = i / n; a = dir + bend * Math.sin(Math.PI * t) + wave * Math.sin(2 * Math.PI * t); x += (len / n) * Math.cos((a * Math.PI) / 180); y += (len / n) * Math.sin((a * Math.PI) / 180); pts.push([x, y]); }
|
|
43
|
+
return U(circle(...head, r, 120), tapered(pts, r * 0.9, tip));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Cells packed back to front: each disc [x, y, r] laid over the ones before it and cut free by the gap (an embryo's
|
|
48
|
+
* blastomeres, a morula).
|
|
49
|
+
*
|
|
50
|
+
* @param {readonly (readonly number[])[]} list The discs, [x, y, r], back first.
|
|
51
|
+
* @param {number} g The gap.
|
|
52
|
+
* @returns {import('../geometry/types.js').Shape} The cells.
|
|
53
|
+
*/
|
|
54
|
+
export const cells = (list, g) => list.reduce((s, [x, y, r]) => over(s, circle(x, y, r, 160), g), []);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A ring w wide round a disc of radius R (the zona round an embryo), with `inner` inside it; keep the inner shape a gap
|
|
58
|
+
* clear of the ring's inner edge.
|
|
59
|
+
*
|
|
60
|
+
* @param {number} R The ring's outer radius.
|
|
61
|
+
* @param {number} w Its width.
|
|
62
|
+
* @param {import('../geometry/types.js').Geom} inner What sits inside.
|
|
63
|
+
* @param {number} g The gap (the caller keeps `inner` this far in).
|
|
64
|
+
* @returns {import('../geometry/types.js').Shape} The ring and its contents.
|
|
65
|
+
*/
|
|
66
|
+
export const zona = (R, w, inner, g) => U(D(circle(0, 0, R, 240), circle(0, 0, R - w, 240)), inner);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A chromatid: a bowed sausage from (0, −L/2) to (0, L/2), w wide with round ends, its middle pushed out by `bow` along x.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} L The length.
|
|
72
|
+
* @param {number} w The width.
|
|
73
|
+
* @param {number} bow The bow at the middle.
|
|
74
|
+
* @returns {import('../geometry/types.js').Shape} The chromatid.
|
|
75
|
+
*/
|
|
76
|
+
export const chromatid = (L, w, bow) => stroke(sample((t) => [bow * Math.sin(Math.PI * t), -L / 2 + L * t], 0, 1, 60), w, { roundCaps: true, chunk: 8 });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** The prompt >_ about (cx, cy) at scale k. */
|
|
4
|
+
export function prompt(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
5
|
+
/** The tag </>. */
|
|
6
|
+
export function code(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
7
|
+
/** Angular curly braces { }, their stems `apart` from the middle. */
|
|
8
|
+
export function braces(cx: number, cy: number, k?: number, apart?: number): Shape;
|
|
9
|
+
/** The hash #. */
|
|
10
|
+
export function hash(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
11
|
+
/** The dollar $: an angular S with a stem through it. */
|
|
12
|
+
export function dollar(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
13
|
+
/** The underscore cursor _. */
|
|
14
|
+
export function under(cx: number, cy: number, k?: number, w?: number, t?: number): Polygon;
|
|
15
|
+
/** The tilde ~ as one wave. */
|
|
16
|
+
export function tilde(cx: number, cy: number, k?: number, t?: number, w?: number, a?: number): Shape;
|
|
17
|
+
/** A slash /. */
|
|
18
|
+
export function slash(cx: number, cy: number, k?: number, t?: number, h?: number, lean?: number): Polygon;
|
|
19
|
+
/** The fat arrow =>. */
|
|
20
|
+
export function fatArrow(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
21
|
+
/** Not equal !=. */
|
|
22
|
+
export function notEqual(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
23
|
+
/** Binary 01. */
|
|
24
|
+
export function binary(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
25
|
+
/** Lambda λ. */
|
|
26
|
+
export function lambda(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
27
|
+
/** A diff + −. */
|
|
28
|
+
export function diff(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
29
|
+
/** A tick ✓. */
|
|
30
|
+
export function tick(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
31
|
+
/** Two slashes //, a comment. */
|
|
32
|
+
export function comment(cx: number, cy: number, k?: number, t?: number): Shape;
|
|
33
|
+
/** A polygon in glyph units, mapped to (cx, cy) at scale k. */
|
|
34
|
+
export function glyphPoly(cx: number, cy: number, k: number, pts: readonly (readonly number[])[]): Polygon;
|