@logomarks/core 0.1.11 → 0.2.0
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 +22 -0
- package/README.md +7 -0
- package/package.json +47 -4
- 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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bold hooks: capsules whose round end runs on into a curl that wraps a neighbour's round end, the gap exact all round,
|
|
3
|
+
* and their Track build (the same layout drawn as two tracks). Angles are degrees, 0 right, 90 down. From the Logomarks
|
|
4
|
+
* tools (hookparts.mjs, batch 214, "Bold · Hooks", and its editions in batch 220).
|
|
5
|
+
*/
|
|
6
|
+
import { I, U, circle, fillet, pill, poly, rad, soften, stroke, tracks } from '../geometry/index.js';
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('../geometry/types.js').Point} Point */
|
|
9
|
+
/** @typedef {import('../geometry/types.js').Shape} Shape */
|
|
10
|
+
/** @typedef {import('../geometry/types.js').Geom} Geom */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A hook: the end of a piece it grows from, the neighbour's cap centre it wraps, and how far it sweeps.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} Hook
|
|
16
|
+
* @property {0 | 1} [end] The end it grows from: 0 the first point, 1 (the default) the last.
|
|
17
|
+
* @property {readonly number[]} c The neighbour's cap centre.
|
|
18
|
+
* @property {number} [rn] The neighbour's cap radius (HOOK_W / 2).
|
|
19
|
+
* @property {number} [sw] The sweep, degrees; or give `e`.
|
|
20
|
+
* @property {number} [e] The angle the curl should end at (the neighbour's end direction), when `sw` isn't given.
|
|
21
|
+
* @property {number} [past] Degrees past `e`.
|
|
22
|
+
* @property {number} [t0] The curl's width at its root (the piece's width).
|
|
23
|
+
* @property {number} [t1] Its width at the tip (0.44 of the piece's width).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The capsule's width. */
|
|
27
|
+
export const HOOK_W = 150;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The point d from p at angle a.
|
|
31
|
+
*
|
|
32
|
+
* @param {readonly number[]} p The point.
|
|
33
|
+
* @param {number} a The angle, degrees.
|
|
34
|
+
* @param {number} d The distance.
|
|
35
|
+
* @returns {Point} The point reached.
|
|
36
|
+
*/
|
|
37
|
+
export const hookAt = (p, a, d) => [p[0] + d * Math.cos(rad(a)), p[1] + d * Math.sin(rad(a))];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The angle from p to q, degrees.
|
|
41
|
+
*
|
|
42
|
+
* @param {readonly number[]} p From.
|
|
43
|
+
* @param {readonly number[]} q To.
|
|
44
|
+
* @returns {number} The angle.
|
|
45
|
+
*/
|
|
46
|
+
const ang = (p, q) => (Math.atan2(q[1] - p[1], q[0] - p[0]) * 180) / Math.PI;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The distance from p to q.
|
|
50
|
+
*
|
|
51
|
+
* @param {readonly number[]} p From.
|
|
52
|
+
* @param {readonly number[]} q To.
|
|
53
|
+
* @returns {number} The distance.
|
|
54
|
+
*/
|
|
55
|
+
const dist = (p, q) => Math.hypot(q[0] - p[0], q[1] - p[1]);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Where a piece's cap centre sits to hook the neighbour cap at c: at angle a from c, the two caps and the gap apart.
|
|
59
|
+
*
|
|
60
|
+
* @param {readonly number[]} c The neighbour's cap centre.
|
|
61
|
+
* @param {number} a The angle from it, degrees.
|
|
62
|
+
* @param {number} g The gap.
|
|
63
|
+
* @param {number} [r=HOOK_W / 2] This cap's radius.
|
|
64
|
+
* @param {number} [rn=HOOK_W / 2] The neighbour's.
|
|
65
|
+
* @returns {Point} The cap centre.
|
|
66
|
+
*/
|
|
67
|
+
export const kiss = (c, a, g, r = HOOK_W / 2, rn = HOOK_W / 2) => hookAt(c, a, r + rn + g);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The curl: a band whose inner edge is radius ri about c, from angle a0 sweeping sw degrees, tapering t0 → t1, with a
|
|
71
|
+
* round tip.
|
|
72
|
+
*
|
|
73
|
+
* @param {readonly number[]} c The centre.
|
|
74
|
+
* @param {number} ri The inner radius.
|
|
75
|
+
* @param {number} a0 The start angle, degrees.
|
|
76
|
+
* @param {number} sw The sweep, degrees.
|
|
77
|
+
* @param {number} t0 The width at the start.
|
|
78
|
+
* @param {number} t1 The width at the tip.
|
|
79
|
+
* @param {number} [n=120] Steps along it.
|
|
80
|
+
* @returns {Shape} The curl.
|
|
81
|
+
*/
|
|
82
|
+
export const curl = (c, ri, a0, sw, t0, t1, n = 120) => {
|
|
83
|
+
const inner = [], outer = [];
|
|
84
|
+
for (let i = 0; i <= n; i++) { const f = i / n, a = a0 + sw * f, t = t1 + (t0 - t1) * (1 - f) ** 2; inner.push(hookAt(c, a, ri)); outer.push(hookAt(c, a, ri + t)); }
|
|
85
|
+
return U(poly([...inner, ...outer.reverse()]), circle(...hookAt(c, a0 + sw, ri + t1 / 2), t1 / 2, 120));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A piece: a capsule w wide along `pts` (corners filleted by `bend`), with hooks. Each hook names the end it grows from,
|
|
90
|
+
* the neighbour's cap centre and the sweep; it logs when the end isn't a kiss from the neighbour. The root of each curl is
|
|
91
|
+
* filleted (`soft`) where it leaves the round end, and only there, so a same-piece gap under 2·soft (a bowl's tip near its
|
|
92
|
+
* stem) doesn't close up; `whole` fillets every inner corner of a piece with no such gap.
|
|
93
|
+
*
|
|
94
|
+
* @param {readonly Point[]} pts The capsule's centreline.
|
|
95
|
+
* @param {readonly Hook[]} [hooks=[]] Its hooks.
|
|
96
|
+
* @param {{ w?: number, bend?: number, g?: number, soft?: number, whole?: boolean }} [o] Width (HOOK_W), corner fillet
|
|
97
|
+
* (90), gap (22), root fillet (40), and whether to fillet every inner corner.
|
|
98
|
+
* @returns {Shape} The piece.
|
|
99
|
+
*/
|
|
100
|
+
export const piece = (pts, hooks = [], { w = HOOK_W, bend = 90, g = 22, soft = 40, whole = false } = {}) => {
|
|
101
|
+
const body = pts.length === 2 ? pill(pts[0], pts[1], w) : stroke(bend ? fillet(pts, bend) : pts, w, { roundCaps: true, chunk: 8 });
|
|
102
|
+
const curls = hooks.map(({ end = 1, c, rn = HOOK_W / 2, sw, e, past = 0, t0 = w, t1 = 0.44 * w }) => {
|
|
103
|
+
const q = end ? pts.at(-1) : pts[0];
|
|
104
|
+
const d = dist(c, q), want = w / 2 + rn + g; if (Math.abs(d - want) > 0.5) console.log(` hook off by ${(d - want).toFixed(1)}`);
|
|
105
|
+
const a0 = ang(c, q); if (sw == null) { const dd = ((e - a0 + 540) % 360) - 180; sw = dd + Math.sign(dd) * past; }
|
|
106
|
+
return curl(c, rn + g, a0, sw, t0, t1);
|
|
107
|
+
});
|
|
108
|
+
const roots = hooks.map(({ end = 1 }) => circle(...(end ? pts.at(-1) : pts[0]), 0.9 * w, 160));
|
|
109
|
+
if (soft && whole) return soften(U(body, ...curls), soft, 0);
|
|
110
|
+
return soft && roots.length ? U(body, ...curls, I(soften(U(body, ...curls), soft, 0), ...(roots.length > 1 ? [U(...roots)] : roots))) : U(body, ...curls);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The Track band's width for the gap g: 74 at 22, so the Track build lands on the house Track rules once fitted. Pass it
|
|
115
|
+
* as `r` and `rn` to kiss() for a Track layout.
|
|
116
|
+
*
|
|
117
|
+
* @param {number} g The gap.
|
|
118
|
+
* @returns {number} The band width.
|
|
119
|
+
*/
|
|
120
|
+
export const TB = (g) => (74 * g) / 22;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The Track build of a piece: its centreline run on round each hook's neighbour (at the kiss distance, so the curl's inner
|
|
124
|
+
* track clears the neighbour's tracks by g), drawn as two tracks 26 wide at pitch 48, both scaled with g.
|
|
125
|
+
*
|
|
126
|
+
* @param {readonly Point[]} pts The centreline.
|
|
127
|
+
* @param {readonly Hook[]} [hooks=[]] Its hooks (`end`, `c`, `sw` or `e` and `past`).
|
|
128
|
+
* @param {{ g?: number, bend?: number }} [o] The gap (22) and the corner fillet (60).
|
|
129
|
+
* @returns {Shape} The two tracks.
|
|
130
|
+
*/
|
|
131
|
+
export const trackPiece = (pts, hooks = [], { g = 22, bend = 60 } = {}) => {
|
|
132
|
+
let path = pts.slice();
|
|
133
|
+
for (const { end = 1, c, sw: sw0, e, past = 0 } of hooks) {
|
|
134
|
+
if (!end) path = path.slice().reverse(); const q = path.at(-1), a0 = ang(c, q);
|
|
135
|
+
let sw = sw0; if (sw == null) { const dd = ((e - a0 + 540) % 360) - 180; sw = dd + Math.sign(dd) * past; }
|
|
136
|
+
path = [...path, ...arc(c, dist(c, q), a0, a0 + sw, Math.max(8, Math.ceil(Math.abs(sw) / 4))).slice(1)];
|
|
137
|
+
}
|
|
138
|
+
return tracks(bend ? fillet(path, bend) : path, { n: 2, pitch: (48 * g) / 22, w: (26 * g) / 22 });
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Points along an arc about c, radius r, from a0 to a1 degrees.
|
|
143
|
+
*
|
|
144
|
+
* @param {readonly number[]} c The centre.
|
|
145
|
+
* @param {number} r The radius.
|
|
146
|
+
* @param {number} a0 The start angle.
|
|
147
|
+
* @param {number} a1 The end angle.
|
|
148
|
+
* @param {number} [n=60] Steps.
|
|
149
|
+
* @returns {Point[]} n + 1 points.
|
|
150
|
+
*/
|
|
151
|
+
export const arc = (c, r, a0, a1, n = 60) => [...Array(n + 1)].map((_, i) => hookAt(c, a0 + ((a1 - a0) * i) / n, r));
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A neighbour's end, for a hook: its cap centre and the way the end faces.
|
|
155
|
+
*
|
|
156
|
+
* @param {readonly Point[]} pts The neighbour's centreline.
|
|
157
|
+
* @param {0 | 1} [end=1] Which end.
|
|
158
|
+
* @returns {{ c: Point, e: number }} The cap centre and the angle the end faces.
|
|
159
|
+
*/
|
|
160
|
+
export const endOf = (pts, end = 1) => (end ? { c: pts.at(-1), e: ang(pts.at(-2), pts.at(-1)) } : { c: pts[0], e: ang(pts[1], pts[0]) });
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Every point of every ring of a shape.
|
|
164
|
+
*
|
|
165
|
+
* @param {Shape} s The shape.
|
|
166
|
+
* @returns {Point[]} The points.
|
|
167
|
+
*/
|
|
168
|
+
export const pts0 = (s) => s.flatMap((pg) => pg.flatMap((r) => r));
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The least distance between two shapes' outline points.
|
|
172
|
+
*
|
|
173
|
+
* @param {Shape} a One shape.
|
|
174
|
+
* @param {Shape} b The other.
|
|
175
|
+
* @returns {number} The distance.
|
|
176
|
+
*/
|
|
177
|
+
const near = (a, b) => { let m = 1e9; const pa = pts0(a), pb = pts0(b); for (const p of pa) for (const q of pb) { const d = (p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2; if (d < m) m = d; } return Math.sqrt(m); };
|
|
178
|
+
|
|
179
|
+
/** join()'s check: set `on` to have it log any two pieces closer than the gap. */
|
|
180
|
+
export const hookCheck = { on: false };
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The pieces as one shape; with hookCheck.on, it logs any two that come closer than the gap.
|
|
184
|
+
*
|
|
185
|
+
* @param {number} g The gap.
|
|
186
|
+
* @param {...Shape} ps The pieces.
|
|
187
|
+
* @returns {Shape} Their union.
|
|
188
|
+
*/
|
|
189
|
+
export const join = (g, ...ps) => {
|
|
190
|
+
if (hookCheck.on) for (let i = 0; i < ps.length; i++) for (let j = i + 1; j < ps.length; j++) { const d = near(ps[i], ps[j]); if (d < g - 0.6) console.log(` pieces ${i},${j} only ${d.toFixed(1)} apart (g ${g.toFixed(1)})`); }
|
|
191
|
+
return U(...ps);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* For the Track build: a straight run must enter the curl on its tangent (a corner there kinks the outer track), so the
|
|
196
|
+
* piece's end sits a quarter turn round from where the bold build puts it: c + (TB(g) + g) along d ± 90°.
|
|
197
|
+
*
|
|
198
|
+
* @param {readonly number[]} c The neighbour's cap centre.
|
|
199
|
+
* @param {number} d The run's direction, degrees.
|
|
200
|
+
* @param {number} side Which side: 1 or −1.
|
|
201
|
+
* @param {number} g The gap.
|
|
202
|
+
* @returns {Point} The piece's end.
|
|
203
|
+
*/
|
|
204
|
+
export const tq = (c, d, side, g) => hookAt(c, d + side * 90, TB(g) + g);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './geo.js';
|
|
2
|
+
export * from './discs.js';
|
|
3
|
+
export * from './hooks.js';
|
|
4
|
+
export * from './jog.js';
|
|
5
|
+
export * from './tech.js';
|
|
6
|
+
export * from './emblems.js';
|
|
7
|
+
export * from './shatter.js';
|
|
8
|
+
export * from './slats.js';
|
|
9
|
+
export * from './flow.js';
|
|
10
|
+
export * from './folds.js';
|
|
11
|
+
export * from './swash.js';
|
|
12
|
+
export * from './ref.js';
|
|
13
|
+
export * from './tracks.js';
|
|
14
|
+
export * from './lines.js';
|
|
15
|
+
export type { Hook } from './hooks.js';
|
|
16
|
+
export type { JogArm } from './jog.js';
|
|
17
|
+
export type { Rail, Shard, ShardMove } from './shatter.js';
|
|
18
|
+
export type { Slat, SlatRowOptions, SlatEdges } from './slats.js';
|
|
19
|
+
export type { FlowOptions } from './flow.js';
|
|
20
|
+
export type { SwashOptions } from './swash.js';
|
|
21
|
+
export type { StripeEdge } from './ref.js';
|
|
22
|
+
export type { TrackKit } from './tracks.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @logomarks/core/forms
|
|
3
|
+
*
|
|
4
|
+
* The constructions the Bold, Line and Track families are drawn with, on the 512 canvas with the house gap: needle
|
|
5
|
+
* gaps, knobs and split discs (geo), one disc cut so a moved piece keeps its gap (discs), capsules whose ends curl round
|
|
6
|
+
* a neighbour (hooks), jogged bursts and chamfer checkers (jog), sparkles, ribbons and keyed chevrons (tech), the split
|
|
7
|
+
* emblem's horizon and grounds (emblems), a form breaking into shards (shatter), turning slats, flow grids, fold seams
|
|
8
|
+
* and straps, swashes, rotation, stripes, guilloche and speed letters (ref), the Track editions' kit (tracks), and soft
|
|
9
|
+
* blobs, Rail lines, chamfered paths and interlocking overlaps (lines).
|
|
10
|
+
*/
|
|
11
|
+
export * from './geo.js';
|
|
12
|
+
export * from './discs.js';
|
|
13
|
+
export * from './hooks.js';
|
|
14
|
+
export * from './jog.js';
|
|
15
|
+
export * from './tech.js';
|
|
16
|
+
export * from './emblems.js';
|
|
17
|
+
export * from './shatter.js';
|
|
18
|
+
export * from './slats.js';
|
|
19
|
+
export * from './flow.js';
|
|
20
|
+
export * from './folds.js';
|
|
21
|
+
export * from './swash.js';
|
|
22
|
+
export * from './ref.js';
|
|
23
|
+
export * from './tracks.js';
|
|
24
|
+
export * from './lines.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Geom, Point, Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** One jogged arm's options. */
|
|
4
|
+
export interface JogArm {
|
|
5
|
+
r0?: number;
|
|
6
|
+
rb?: number;
|
|
7
|
+
breaks?: readonly number[];
|
|
8
|
+
R: number;
|
|
9
|
+
w: number;
|
|
10
|
+
g: number;
|
|
11
|
+
shift: number;
|
|
12
|
+
scarf?: number;
|
|
13
|
+
innerShift?: number;
|
|
14
|
+
cx?: number;
|
|
15
|
+
cy?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The point `along` the axis at deg from (cx, cy), stepped `side` square to it. */
|
|
19
|
+
export function axisAt(deg: number, along: number, side?: number, cx?: number, cy?: number): Point;
|
|
20
|
+
/** A bar on the axis at deg from a0 to a1, w wide, `side` off the axis, its ends slanted by cut0 / cut1. */
|
|
21
|
+
export function axisBar(deg: number, a0: number, a1: number, w: number, side?: number, o?: { cut0?: number; cut1?: number; cx?: number; cy?: number }): Polygon;
|
|
22
|
+
/** One jogged arm: runs broken by g and stepped sideways. */
|
|
23
|
+
export function jogArm(deg: number, o: JogArm): Shape;
|
|
24
|
+
/** n jogged arms round the centre, the first at deg0. */
|
|
25
|
+
export function jogBurst(n: number, deg0: number, o: JogArm, each?: (i: number) => Partial<JogArm>): Shape;
|
|
26
|
+
/** A polygon with its corners cut at 45° by c (one or one per corner). */
|
|
27
|
+
export function chamfer(pts: readonly Point[], c: number | readonly number[]): Polygon;
|
|
28
|
+
/** A box from its corners, chamfered by c. */
|
|
29
|
+
export function chamferBox(x0: number, y0: number, x1: number, y1: number, c?: number | readonly number[]): Polygon;
|
|
30
|
+
/** A checker's dark cells, corner meetings cut so two squares are parted by exactly g. */
|
|
31
|
+
export function checkerCells(x0: number, y0: number, p: number, cols: number, rows: number, g: number, o?: { parity?: 0 | 1; outer?: number; skip?: (i: number, j: number) => boolean }): Shape;
|
|
32
|
+
/** Throws unless a stands at least g (less 0.5) clear of b. */
|
|
33
|
+
export function assertClear(a: Geom, b: Geom, g: number, what?: string): void;
|
package/src/forms/jog.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jog bursts and chamfer checkers. A jog burst is straight bars radiating from a centre, each broken part way along and
|
|
3
|
+
* its outer run stepped sideways, parallel to itself (a splice jog), so the burst looks mis-registered; the break is a gap
|
|
4
|
+
* g along the arm (square, or a scarf cut at an angle, measured square to the cut), so the two runs are parted by exactly
|
|
5
|
+
* the house gap wherever they face each other. A chamfer checker is a heavy slab with 45° chamfered corners and a small
|
|
6
|
+
* checker of chamfered squares tucked into a corner: two dark squares that meet corner to corner have that corner cut at
|
|
7
|
+
* 45° with legs of g/√2, so they are parted by exactly g and stay full size. Angles are degrees, y down. From the
|
|
8
|
+
* Logomarks tools (jogparts.mjs, batch 244, "Bold · Jog" and "Bold · Chamfer").
|
|
9
|
+
*/
|
|
10
|
+
import { C, U, I, poly, grow } from '../geometry/index.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('../geometry/types.js').Point} Point */
|
|
13
|
+
/** @typedef {import('../geometry/types.js').Polygon} Polygon */
|
|
14
|
+
/** @typedef {import('../geometry/types.js').Shape} Shape */
|
|
15
|
+
/** @typedef {import('../geometry/types.js').Geom} Geom */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The point at distance `along` on the axis at angle `deg` from (cx, cy), stepped `side` square to it.
|
|
19
|
+
*
|
|
20
|
+
* @param {number} deg The axis's angle.
|
|
21
|
+
* @param {number} along The distance along it.
|
|
22
|
+
* @param {number} [side=0] The step square to it.
|
|
23
|
+
* @param {number} [cx=C] The axis's origin.
|
|
24
|
+
* @param {number} [cy=C] The axis's origin.
|
|
25
|
+
* @returns {Point} The point.
|
|
26
|
+
*/
|
|
27
|
+
export const axisAt = (deg, along, side = 0, cx = C, cy = C) => {
|
|
28
|
+
const a = (deg * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a);
|
|
29
|
+
return [cx + c * along - s * side, cy + s * along + c * side];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A bar on the axis at `deg` from `a0` to `a1` along it, `w` wide, centred `side` off the axis. `cut0` / `cut1` slant its
|
|
34
|
+
* ends: the end line turned that many degrees off square (positive leans the end forward on the +side).
|
|
35
|
+
*
|
|
36
|
+
* @param {number} deg The axis's angle.
|
|
37
|
+
* @param {number} a0 Where the bar starts along it.
|
|
38
|
+
* @param {number} a1 Where it ends.
|
|
39
|
+
* @param {number} w Its width.
|
|
40
|
+
* @param {number} [side=0] Its offset from the axis.
|
|
41
|
+
* @param {{ cut0?: number, cut1?: number, cx?: number, cy?: number }} [o] The end slants and the axis's origin.
|
|
42
|
+
* @returns {Polygon} The bar.
|
|
43
|
+
*/
|
|
44
|
+
export const axisBar = (deg, a0, a1, w, side = 0, { cut0 = 0, cut1 = 0, cx = C, cy = C } = {}) => {
|
|
45
|
+
const t0 = Math.tan((cut0 * Math.PI) / 180) * (w / 2), t1 = Math.tan((cut1 * Math.PI) / 180) * (w / 2);
|
|
46
|
+
const P = (al, sd) => axisAt(deg, al, sd, cx, cy);
|
|
47
|
+
return poly([P(a0 - t0, side - w / 2), P(a1 - t1, side - w / 2), P(a1 + t1, side + w / 2), P(a0 + t0, side + w / 2)]);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One arm of a jog burst.
|
|
52
|
+
*
|
|
53
|
+
* @typedef {object} JogArm
|
|
54
|
+
* @property {number} [r0] Where the inner run starts (0: the centre).
|
|
55
|
+
* @property {number} [rb] The break (or give `breaks`).
|
|
56
|
+
* @property {readonly number[]} [breaks] Several breaks, each run stepped `shift` further than the last.
|
|
57
|
+
* @property {number} R Where the outer run ends.
|
|
58
|
+
* @property {number} w The bar's width.
|
|
59
|
+
* @property {number} g The gap at each break.
|
|
60
|
+
* @property {number} shift Each run's step square to the axis (positive is clockwise on screen).
|
|
61
|
+
* @property {number} [scarf] The breaks' slant, degrees off square; the gap stays g square to the cut.
|
|
62
|
+
* @property {number} [innerShift] The inner run's own step.
|
|
63
|
+
* @property {number} [cx] The centre.
|
|
64
|
+
* @property {number} [cy] The centre.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* One jogged arm on the axis at `deg`: an inner run from `r0` to the break at `rb`, a gap g, and an outer run on to `R`
|
|
69
|
+
* stepped `shift` square to the axis. A slanted face stepped sideways slides along the axis by shift·tan, so each later
|
|
70
|
+
* run starts that much further on.
|
|
71
|
+
*
|
|
72
|
+
* @param {number} deg The axis's angle.
|
|
73
|
+
* @param {JogArm} o The arm.
|
|
74
|
+
* @returns {Shape} The arm.
|
|
75
|
+
*/
|
|
76
|
+
export const jogArm = (deg, { r0 = 0, rb, breaks = [rb], R, w, g, shift, scarf = 0, innerShift = 0, cx = C, cy = C }) => {
|
|
77
|
+
const a = (scarf * Math.PI) / 180, half = g / 2 / Math.cos(a), t = Math.tan(a); // the axial half-gap that leaves g square to a slanted cut
|
|
78
|
+
const runs = []; let from = r0, side = innerShift;
|
|
79
|
+
breaks.forEach((b, i) => { runs.push(axisBar(deg, from, b - half, w, side, { cut0: i ? scarf : 0, cut1: scarf, cx, cy })); side += shift; from = b + half + shift * t; });
|
|
80
|
+
runs.push(axisBar(deg, from, R, w, side, { cut0: scarf, cx, cy }));
|
|
81
|
+
return U(...runs);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* n jogged arms round the centre, the first at `deg0`; `each(i)` may override any jogArm option per arm (a shift's sign, a
|
|
86
|
+
* break radius). The inner runs meet at the centre when r0 is 0.
|
|
87
|
+
*
|
|
88
|
+
* @param {number} n The number of arms.
|
|
89
|
+
* @param {number} deg0 The first arm's angle.
|
|
90
|
+
* @param {JogArm} o Every arm's options.
|
|
91
|
+
* @param {(i: number) => Partial<JogArm>} [each] Per-arm overrides.
|
|
92
|
+
* @returns {Shape} The burst.
|
|
93
|
+
*/
|
|
94
|
+
export const jogBurst = (n, deg0, o, each = () => ({})) => U(...[...Array(n)].map((_, i) => jogArm(deg0 + (i * 360) / n, { ...o, ...each(i) })));
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A polygon with each corner cut at 45° (along the line square to its bisector) by `c` along both edges: a number for
|
|
98
|
+
* every corner or one per corner, 0 keeping it sharp. Points run in any order; corners are cut symmetrically.
|
|
99
|
+
*
|
|
100
|
+
* @param {readonly Point[]} pts The corners.
|
|
101
|
+
* @param {number | readonly number[]} c The cut.
|
|
102
|
+
* @returns {Polygon} The chamfered polygon.
|
|
103
|
+
*/
|
|
104
|
+
export const chamfer = (pts, c) => {
|
|
105
|
+
const n = pts.length, cs = Array.isArray(c) ? c : pts.map(() => c), out = [];
|
|
106
|
+
for (let i = 0; i < n; i++) {
|
|
107
|
+
const p = pts[i], a = pts[(i - 1 + n) % n], b = pts[(i + 1) % n], k = cs[i];
|
|
108
|
+
if (!k) { out.push(p); continue; }
|
|
109
|
+
const ua = [(a[0] - p[0]) / Math.hypot(a[0] - p[0], a[1] - p[1]), (a[1] - p[1]) / Math.hypot(a[0] - p[0], a[1] - p[1])];
|
|
110
|
+
const ub = [(b[0] - p[0]) / Math.hypot(b[0] - p[0], b[1] - p[1]), (b[1] - p[1]) / Math.hypot(b[0] - p[0], b[1] - p[1])];
|
|
111
|
+
out.push([p[0] + ua[0] * k, p[1] + ua[1] * k], [p[0] + ub[0] * k, p[1] + ub[1] * k]);
|
|
112
|
+
}
|
|
113
|
+
return poly(out);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A box from its corners, its corners (top-left, top-right, bottom-right, bottom-left) chamfered by c.
|
|
118
|
+
*
|
|
119
|
+
* @param {number} x0 Left.
|
|
120
|
+
* @param {number} y0 Top.
|
|
121
|
+
* @param {number} x1 Right.
|
|
122
|
+
* @param {number} y1 Bottom.
|
|
123
|
+
* @param {number | readonly number[]} [c=0] The chamfer, for every corner or one per corner.
|
|
124
|
+
* @returns {Polygon} The box.
|
|
125
|
+
*/
|
|
126
|
+
export const chamferBox = (x0, y0, x1, y1, c = 0) => chamfer([[x0, y0], [x1, y0], [x1, y1], [x0, y1]], c);
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A checker of `cols` × `rows` cells of size p from (x0, y0): the dark cells (where (i + j) % 2 === parity, less any
|
|
130
|
+
* `skip(i, j)`) as full squares, each corner that meets another dark square cut at 45° with legs of g/√2, so two squares
|
|
131
|
+
* meeting corner to corner are parted by exactly g; `outer` chamfers every other corner too.
|
|
132
|
+
*
|
|
133
|
+
* @param {number} x0 Left.
|
|
134
|
+
* @param {number} y0 Top.
|
|
135
|
+
* @param {number} p The cell size.
|
|
136
|
+
* @param {number} cols Cells across.
|
|
137
|
+
* @param {number} rows Cells down.
|
|
138
|
+
* @param {number} g The gap.
|
|
139
|
+
* @param {{ parity?: 0 | 1, outer?: number, skip?: (i: number, j: number) => boolean }} [o] Which cells are dark, the
|
|
140
|
+
* free corners' chamfer (0: sharp) and cells to leave out.
|
|
141
|
+
* @returns {Shape} The checker.
|
|
142
|
+
*/
|
|
143
|
+
export const checkerCells = (x0, y0, p, cols, rows, g, { parity = 0, outer = 0, skip = () => false } = {}) => {
|
|
144
|
+
const dark = (i, j) => i >= 0 && j >= 0 && i < cols && j < rows && (i + j) % 2 === parity && !skip(i, j), c = g / Math.SQRT2, out = [];
|
|
145
|
+
for (let j = 0; j < rows; j++) for (let i = 0; i < cols; i++) if (dark(i, j)) {
|
|
146
|
+
const cut = [[-1, -1], [1, -1], [1, 1], [-1, 1]].map(([di, dj]) => (dark(i + di, j + dj) ? c : outer));
|
|
147
|
+
out.push(chamferBox(x0 + i * p, y0 + j * p, x0 + (i + 1) * p, y0 + (j + 1) * p, cut));
|
|
148
|
+
}
|
|
149
|
+
return U(...out);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Throws unless every part of `a` stands at least g (less a half-unit tolerance) clear of `b`.
|
|
154
|
+
*
|
|
155
|
+
* @param {Geom} a One shape.
|
|
156
|
+
* @param {Geom} b The other.
|
|
157
|
+
* @param {number} g The gap.
|
|
158
|
+
* @param {string} [what='parts'] What to call them in the error.
|
|
159
|
+
* @returns {void}
|
|
160
|
+
* @throws {Error} When they come closer than the gap.
|
|
161
|
+
*/
|
|
162
|
+
export const assertClear = (a, b, g, what = 'parts') => { if (I(grow(a, g - 0.5), b).length) throw new Error(`${what} closer than the gap`); };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Point, Polygon, Shape, Geom } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** A blob: radius R wobbled by sine terms [k, amplitude, phase]; sy squashes it. */
|
|
4
|
+
export function blob(cx: number, cy: number, R: number, terms?: readonly (readonly number[])[], sy?: number, n?: number): Polygon;
|
|
5
|
+
/** The same blob grown by g all round. */
|
|
6
|
+
export function blobGrown(cx: number, cy: number, R: number, terms: readonly (readonly number[])[], sy?: number, g?: number): Polygon;
|
|
7
|
+
/** The Cairo pentagonal tiling on unit cells round the origin (t = 0.2257: every edge equal). */
|
|
8
|
+
export function cairo(t?: number): Point[][];
|
|
9
|
+
/** The polygons whose centres lie within r of (cx, cy). */
|
|
10
|
+
export function near<T extends readonly (readonly number[])[]>(list: readonly T[], cx: number, cy: number, r: number): T[];
|
|
11
|
+
/** A polyline with every inner corner cut at 45° by c (or one per inner corner). */
|
|
12
|
+
export function chamPath(pts: readonly Point[], c: number | readonly number[]): Point[];
|
|
13
|
+
/** A Rail: two tracks w wide, pitch apart, along an open polyline, corners mitred, ends flat. */
|
|
14
|
+
export function railsOf(pts: readonly Point[], w?: number, pitch?: number): Shape;
|
|
15
|
+
/** A closed Rail round a regular n-gon whose centre path has inradius r. */
|
|
16
|
+
export function railRing(cx: number, cy: number, r: number, n: number, deg?: number, w?: number, pitch?: number): Shape;
|
|
17
|
+
/** A closed Rail along any polygon, corners mitred. */
|
|
18
|
+
export function railLoop(pts: readonly Point[], w?: number, pitch?: number): Shape;
|
|
19
|
+
/** Points from a to b bowed sideways by k times the length at the middle. */
|
|
20
|
+
export function bow(a: readonly number[], b: readonly number[], k: number, n?: number): Point[];
|
|
21
|
+
/** A slot g wide leaving the canvas centre at angle a, from `from` to `to` along it. */
|
|
22
|
+
export function slot(a: number, g: number, from?: number, to?: number): Polygon;
|
|
23
|
+
/** The two half-planes either side of the line p→q: [left, right]. */
|
|
24
|
+
export function halves(p: readonly number[], q: readonly number[]): Polygon[];
|
|
25
|
+
/** Shapes in a chain along p→q, each in front of the next on the left and behind it on the right, gap g. */
|
|
26
|
+
export function lockChain(list: readonly Geom[], p: readonly number[], q: readonly number[], g?: number): Shape;
|
|
27
|
+
/** a and b overlapped: a in front left of p→q, b in front right of it. */
|
|
28
|
+
export function lockAlong(a: Geom, b: Geom, p: readonly number[], q: readonly number[], g?: number): Shape;
|
|
29
|
+
/** The least distance between two shapes' outline points. */
|
|
30
|
+
export function gapBetween(a: Geom, b: Geom): number;
|
|
31
|
+
/** A part slid along deg until it sits exactly g clear of `fixed`. */
|
|
32
|
+
export function slideToGap(fixed: Geom, part: Geom, deg: number, g: number, far?: number): Geom;
|