@johnmorrisdotca/dougu-sports 0.5.2 → 0.7.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 +37 -0
- package/README.md +7 -5
- package/dist/emblems/ball/ball.core.d.ts +14 -0
- package/dist/emblems/ball/ball.family.panelled.d.ts +8 -2
- package/dist/emblems/ball/ball.family.panelled.js +34 -8
- package/dist/emblems/ball/ball.soccer.era.d.ts +1 -1
- package/dist/emblems/ball/ball.soccer.era.js +14 -9
- package/dist/emblems/ball/ball.volleyball.flag.d.ts +25 -0
- package/dist/emblems/ball/ball.volleyball.flag.js +41 -15
- package/dist/emblems/emblem.core.d.ts +3 -0
- package/dist/emblems/emblem.descriptions.js +22 -14
- package/dist/emblems/emblem.ids.d.ts +3 -3
- package/dist/emblems/emblem.ids.js +4 -4
- package/dist/emblems/gear/gear.budo.d.ts +63 -0
- package/dist/emblems/gear/gear.budo.js +175 -34
- package/dist/emblems/gear/gear.budo.steps.d.ts +35 -0
- package/dist/emblems/gear/gear.budo.steps.js +146 -0
- package/dist/emblems/gear/gear.core.js +19 -8
- package/dist/emblems/gear/gear.index.js +3 -2
- package/dist/emblems/sport/judo.js +3 -3
- package/dist/emblems/sport/karate.js +2 -2
- package/dist/emblems-entry.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -39,8 +39,8 @@ export const GEAR_IDS = [
|
|
|
39
39
|
"gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle",
|
|
40
40
|
"gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley",
|
|
41
41
|
"gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles",
|
|
42
|
-
"gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded",
|
|
43
|
-
"gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown",
|
|
42
|
+
"gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back",
|
|
43
|
+
"gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6",
|
|
44
44
|
"gear:yumi", "gear:ya", "gear:karate-gi"
|
|
45
45
|
];
|
|
46
46
|
/** The Japanese sports and budo: sumo, kendo, judo, kyudo and karate, with the trophies and the gear that belong to them. Every one is also in its own group; this list is for showing them together. */
|
|
@@ -48,8 +48,8 @@ export const JAPANESE_IDS = [
|
|
|
48
48
|
"sport:sumo", "trophy:sumo-cup", "gear:gunbai", "gear:mawashi",
|
|
49
49
|
"trophy:championship-banner",
|
|
50
50
|
"sport:kendo", "gear:shinai", "gear:kendo-men",
|
|
51
|
-
"sport:judo", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded",
|
|
52
|
-
"gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown",
|
|
51
|
+
"sport:judo", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back",
|
|
52
|
+
"gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6",
|
|
53
53
|
"sport:kyudo", "gear:yumi", "gear:ya",
|
|
54
54
|
"sport:karate", "gear:karate-gi",
|
|
55
55
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PieceOptions } from "../ball/ball.core.ts";
|
|
2
|
+
import { type Part } from "./gear.core.ts";
|
|
2
3
|
/**
|
|
3
4
|
* The colours of the judo belts, which `tint` takes: the six kyu colours in order, black (1st to 5th dan), `kohaku` (the red of the red-and-white belt of the 6th to 8th dan, whose white panels are the accent) and
|
|
4
5
|
* `red` (9th and 10th dan). The Kodokan's own kyu belts are white and brown only; the colours between are a later, national addition, so a country's order may differ.
|
|
@@ -15,13 +16,75 @@ export declare const OBI_RANKS: {
|
|
|
15
16
|
readonly red: "#c4202e";
|
|
16
17
|
};
|
|
17
18
|
export type ObiRank = keyof typeof OBI_RANKS;
|
|
19
|
+
export type Pt = readonly [number, number];
|
|
18
20
|
/** Judogi colours: the IJF white ("snow white") and blue (between Pantone 285M and 286M). */
|
|
19
21
|
export declare const JUDOGI_WHITE = "#f6f4ee";
|
|
20
22
|
export declare const JUDOGI_BLUE = "#1456b6";
|
|
23
|
+
export declare const f: (n: number) => string;
|
|
24
|
+
export declare const path: (pts: readonly Pt[], close?: boolean) => string;
|
|
25
|
+
/** A rectangle of width `w` along the segment from `a` to `b`, as a polygon. */
|
|
26
|
+
export declare const strip: (a: Pt, b: Pt, w: number) => Pt[];
|
|
27
|
+
export declare const lerp: (a: Pt, b: Pt, t: number) => Pt;
|
|
28
|
+
/** A line parallel to the segment from `a` to `b`, `off` to its side, between the fractions `t0` and `t1` of its length. */
|
|
29
|
+
export declare const along: (a: Pt, b: Pt, off: number, t0: number, t1: number) => string;
|
|
30
|
+
export declare const INK = "#0d1522";
|
|
31
|
+
/** The edge of a black belt: light enough to show on a dark page. */
|
|
32
|
+
export declare const BELT_EDGE = "#5d6672";
|
|
33
|
+
export declare const dark: (d: string, w?: number, op?: number) => Part;
|
|
34
|
+
export declare const light: (d: string, w?: number, op?: number) => Part;
|
|
35
|
+
export interface BeltSpec {
|
|
36
|
+
/** The colour of the belt, and of its second colour (the other half of a split belt, or the white of the kohaku panels). */
|
|
37
|
+
main: string;
|
|
38
|
+
alt?: string;
|
|
39
|
+
/** Centre and ends of the two wraps, the top of the first one, the width of the belt and how far its wraps sag at the middle (the belt is round). */
|
|
40
|
+
cx: number;
|
|
41
|
+
x0: number;
|
|
42
|
+
x1: number;
|
|
43
|
+
y: number;
|
|
44
|
+
w: number;
|
|
45
|
+
sag: number;
|
|
46
|
+
/** The knot's width, the length of the ends and how far they hang apart (degrees). */
|
|
47
|
+
knot: number;
|
|
48
|
+
tail: number;
|
|
49
|
+
splay: number;
|
|
50
|
+
/** The length of the end on the right as the wearer is seen (the left end of the wearer), where it is not `tail`: a knot just tied has one end longer than the other. */
|
|
51
|
+
tail2?: number;
|
|
52
|
+
/** Panels of the red-and-white belt: the x where the panel colour changes along the first and the second wrap, and the colour of the ends. */
|
|
53
|
+
panels?: {
|
|
54
|
+
upper: readonly number[];
|
|
55
|
+
lower: readonly number[];
|
|
56
|
+
tail: string;
|
|
57
|
+
};
|
|
58
|
+
/** A split belt: the second colour fills the lower half of the belt's width, lengthwise. */
|
|
59
|
+
half?: boolean;
|
|
60
|
+
/** The white stripe of a women's black belt, down the middle of the belt. */
|
|
61
|
+
stripe?: boolean;
|
|
62
|
+
/** A bar across each end. */
|
|
63
|
+
bars?: boolean;
|
|
64
|
+
/** The edge of the belt, where a darker tone of the belt itself would vanish (a black belt on a dark page). */
|
|
65
|
+
edge?: string;
|
|
66
|
+
}
|
|
67
|
+
/** A belt tied at the front: two wraps, a flat square knot and two equal ends hanging a little apart. Parts, back to front. */
|
|
68
|
+
export declare function belt(s: BeltSpec): Part[];
|
|
69
|
+
/**
|
|
70
|
+
* The judogi laid flat: jacket (sleeves out to the sides, lapels crossed left over right, thick stitched collar, quilted upper body, vented skirt), the belt tied over it and the trousers below with a drawstring
|
|
71
|
+
* waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
|
|
72
|
+
*/
|
|
73
|
+
export declare function judogiFlat(torso?: boolean): Part[];
|
|
21
74
|
export declare const GEAR_JUDOGI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
22
75
|
export declare const GEAR_JUDOGI_BLUE: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
23
76
|
export declare const GEAR_JUDOGI_FOLDED: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
24
77
|
export declare const GEAR_JUDOGI_BLUE_FOLDED: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
78
|
+
export declare const GEAR_JUDOGI_BACK: import("./gear.index.ts").Piece<{
|
|
79
|
+
main: string;
|
|
80
|
+
accent: "#1f2228";
|
|
81
|
+
parts: Part[];
|
|
82
|
+
}>;
|
|
83
|
+
export declare const GEAR_JUDOGI_BLUE_BACK: import("./gear.index.ts").Piece<{
|
|
84
|
+
main: string;
|
|
85
|
+
accent: "#1f2228";
|
|
86
|
+
parts: Part[];
|
|
87
|
+
}>;
|
|
25
88
|
export declare const GEAR_KARATE_GI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
26
89
|
export declare const GEAR_OBI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
27
90
|
export declare const GEAR_OBI_WHITE: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fitSize, linePath } from "../ball/ball.letters.js";
|
|
2
|
+
import { defineGear, drawGear, drawn, rect } from "./gear.core.js";
|
|
2
3
|
/**
|
|
3
4
|
* The colours of the judo belts, which `tint` takes: the six kyu colours in order, black (1st to 5th dan), `kohaku` (the red of the red-and-white belt of the 6th to 8th dan, whose white panels are the accent) and
|
|
4
5
|
* `red` (9th and 10th dan). The Kodokan's own kyu belts are white and brown only; the colours between are a later, national addition, so a country's order may differ.
|
|
@@ -17,25 +18,55 @@ export const OBI_RANKS = {
|
|
|
17
18
|
/** Judogi colours: the IJF white ("snow white") and blue (between Pantone 285M and 286M). */
|
|
18
19
|
export const JUDOGI_WHITE = "#f6f4ee";
|
|
19
20
|
export const JUDOGI_BLUE = "#1456b6";
|
|
20
|
-
const f = (n) => String(Math.round(n * 10) / 10);
|
|
21
|
-
const path = (pts, close = true) => `M${pts.map((p) => `${f(p[0])} ${f(p[1])}`).join("L")}${close ? "z" : ""}`;
|
|
21
|
+
export const f = (n) => String(Math.round(n * 10) / 10);
|
|
22
|
+
export const path = (pts, close = true) => `M${pts.map((p) => `${f(p[0])} ${f(p[1])}`).join("L")}${close ? "z" : ""}`;
|
|
22
23
|
const mirror = (pts) => pts.map((p) => [64 - p[0], p[1]]);
|
|
23
24
|
/** A rectangle of width `w` along the segment from `a` to `b`, as a polygon. */
|
|
24
|
-
const strip = (a, b, w) => {
|
|
25
|
+
export const strip = (a, b, w) => {
|
|
25
26
|
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
26
27
|
const l = Math.hypot(dx, dy) || 1;
|
|
27
28
|
const nx = (-dy / l) * (w / 2), ny = (dx / l) * (w / 2);
|
|
28
29
|
return [[a[0] + nx, a[1] + ny], [b[0] + nx, b[1] + ny], [b[0] - nx, b[1] - ny], [a[0] - nx, a[1] - ny]];
|
|
29
30
|
};
|
|
30
|
-
const lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
31
|
+
export const lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
31
32
|
/** A line parallel to the segment from `a` to `b`, `off` to its side, between the fractions `t0` and `t1` of its length. */
|
|
32
|
-
const along = (a, b, off, t0, t1) => {
|
|
33
|
+
export const along = (a, b, off, t0, t1) => {
|
|
33
34
|
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
34
35
|
const l = Math.hypot(dx, dy) || 1;
|
|
35
36
|
const nx = (-dy / l) * off, ny = (dx / l) * off;
|
|
36
37
|
const p0 = lerp(a, b, t0), p1 = lerp(a, b, t1);
|
|
37
38
|
return `M${f(p0[0] + nx)} ${f(p0[1] + ny)}L${f(p1[0] + nx)} ${f(p1[1] + ny)}`;
|
|
38
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* A sleeve of the jacket's left side, hanging out and down from the armhole: its axis `deg` below the horizontal, `len` long from the middle of the armhole to the middle of the cuff, and the cuff `cuff` wide. The
|
|
42
|
+
* corners, in order: the top of the shoulder, the outer corner of the cuff, its inner corner and the underarm. Everything is measured from this shape (test/judo.test.ts).
|
|
43
|
+
*/
|
|
44
|
+
function hangingSleeve(top, under, deg, len, cuff) {
|
|
45
|
+
const a = (deg * Math.PI) / 180;
|
|
46
|
+
const mid = lerp(top, under, 0.5);
|
|
47
|
+
const tip = [mid[0] - Math.cos(a) * len, mid[1] + Math.sin(a) * len];
|
|
48
|
+
const hx = (Math.sin(a) * cuff) / 2, hy = (Math.cos(a) * cuff) / 2;
|
|
49
|
+
return [top, [tip[0] - hx, tip[1] - hy], [tip[0] + hx, tip[1] + hy], under];
|
|
50
|
+
}
|
|
51
|
+
/** The judogi's sleeve (IJF C1.10): to the wrist bone, about four fifths as long as the jacket, and wide at the cuff so that the forearm has room all the way. */
|
|
52
|
+
const JUDOGI_SLEEVE = hangingSleeve([19.8, 6.4], [18.6, 22.4], 60, 26, 8.8);
|
|
53
|
+
/** The karate-gi's sleeve (WKF 2.2.1 j): three-quarter length, between half the forearm and the bend of the wrist. */
|
|
54
|
+
const KARATE_SLEEVE = hangingSleeve([20.6, 8], [19.4, 22], 56, 23.6, 8.8);
|
|
55
|
+
/** Rows of quilting across a sleeve, from its top edge to its underside, leaving the ends of each row a little short of the seams. */
|
|
56
|
+
function sleeveRows(pts) {
|
|
57
|
+
let d = "";
|
|
58
|
+
for (let t = 0.1; t <= 0.9; t += 0.08) {
|
|
59
|
+
const a = lerp(pts[0], pts[1], t), b = lerp(pts[3], pts[2], t);
|
|
60
|
+
const a2 = lerp(a, b, 0.07), b2 = lerp(a, b, 0.93);
|
|
61
|
+
d += `M${f(a2[0])} ${f(a2[1])}L${f(b2[0])} ${f(b2[1])}`;
|
|
62
|
+
}
|
|
63
|
+
return d;
|
|
64
|
+
}
|
|
65
|
+
/** The hem of a sleeve's cuff, stitched across at the fraction `t` of the way from the shoulder. */
|
|
66
|
+
const cuffHem = (sleeve, t = 0.9) => {
|
|
67
|
+
const a = lerp(sleeve[0], sleeve[1], t), b = lerp(sleeve[3], sleeve[2], t);
|
|
68
|
+
return `M${f(a[0])} ${f(a[1])}L${f(b[0])} ${f(b[1])}`;
|
|
69
|
+
};
|
|
39
70
|
/** Horizontal rows (every `step`, from `y0` to `y1`) clipped to a convex polygon, as one path. */
|
|
40
71
|
function rows(poly, y0, y1, step, inset = 0) {
|
|
41
72
|
let d = "";
|
|
@@ -55,13 +86,13 @@ function rows(poly, y0, y1, step, inset = 0) {
|
|
|
55
86
|
return d;
|
|
56
87
|
}
|
|
57
88
|
// Cloth lines are laid over the cloth as ink at low opacity (dark rows) or as light rows, so they follow any tint.
|
|
58
|
-
const INK = "#0d1522";
|
|
89
|
+
export const INK = "#0d1522";
|
|
59
90
|
/** The edge of a black belt: light enough to show on a dark page. */
|
|
60
|
-
const BELT_EDGE = "#5d6672";
|
|
61
|
-
const dark = (d, w = 0.3, op = 0.2) => ({ d, fill: "none", stroke: INK, w, op, line: "none" });
|
|
62
|
-
const light = (d, w = 0.3, op = 0.3) => ({ d, fill: "none", stroke: "#ffffff", w, op, line: "none" });
|
|
91
|
+
export const BELT_EDGE = "#5d6672";
|
|
92
|
+
export const dark = (d, w = 0.3, op = 0.2) => ({ d, fill: "none", stroke: INK, w, op, line: "none" });
|
|
93
|
+
export const light = (d, w = 0.3, op = 0.3) => ({ d, fill: "none", stroke: "#ffffff", w, op, line: "none" });
|
|
63
94
|
/** A belt tied at the front: two wraps, a flat square knot and two equal ends hanging a little apart. Parts, back to front. */
|
|
64
|
-
function belt(s) {
|
|
95
|
+
export function belt(s) {
|
|
65
96
|
const parts = [];
|
|
66
97
|
const hw = (s.x1 - s.x0) / 2;
|
|
67
98
|
const w = s.w;
|
|
@@ -109,7 +140,8 @@ function belt(s) {
|
|
|
109
140
|
const tailFill = s.panels?.tail ?? s.main;
|
|
110
141
|
for (const side of [-1, 1]) {
|
|
111
142
|
const a = [s.cx + side * kw * 0.2, yc + hh * 0.2];
|
|
112
|
-
const
|
|
143
|
+
const length = side === 1 ? (s.tail2 ?? s.tail) : s.tail;
|
|
144
|
+
const b = [a[0] + side * Math.sin(rad) * length, a[1] + Math.cos(rad) * length];
|
|
113
145
|
parts.push({ d: path(strip(a, b, tw)), fill: tailFill, stroke: s.edge, cyl: 0.55, shade: 0.25 });
|
|
114
146
|
if (s.half === true) {
|
|
115
147
|
const q = strip(a, b, tw);
|
|
@@ -120,7 +152,7 @@ function belt(s) {
|
|
|
120
152
|
if (s.stripe === true)
|
|
121
153
|
parts.push({ d: path(strip(lerp(a, b, 0.25), lerp(a, b, 1), tw * 0.15)), fill: "#f6f4ee", stroke: "none", line: "none" });
|
|
122
154
|
if (s.bars === true)
|
|
123
|
-
parts.push({ d: path(strip(lerp(a, b, 1 - 3.4 /
|
|
155
|
+
parts.push({ d: path(strip(lerp(a, b, 1 - 3.4 / length), lerp(a, b, 1 - 2.2 / length), tw)), fill: "$accent", stroke: "none", op: 0.9, line: "none" });
|
|
124
156
|
}
|
|
125
157
|
const kfill = s.panels === undefined ? s.main : alt;
|
|
126
158
|
const blob = rect(s.cx - kw / 2, yt, kw, hh * 2, Math.min(2.4, kw * 0.22));
|
|
@@ -141,13 +173,9 @@ function belt(s) {
|
|
|
141
173
|
return parts;
|
|
142
174
|
}
|
|
143
175
|
// ───────────────────────────── the judogi ─────────────────────────────
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
* waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
|
|
147
|
-
*/
|
|
148
|
-
function judogiFlat() {
|
|
176
|
+
/** The judogi's trousers laid flat: straight legs, a waistband with its drawstring, reinforced knees. */
|
|
177
|
+
function judogiTrousers() {
|
|
149
178
|
const parts = [];
|
|
150
|
-
// Trousers, behind everything: straight legs, a waistband with its drawstring, reinforced knees.
|
|
151
179
|
const trousers = [[18.4, 37], [45.6, 37], [46.6, 62], [34.6, 62], [32, 43.6], [29.4, 62], [17.4, 62]];
|
|
152
180
|
parts.push({ d: path(trousers), fill: "$main", shade: 0.3, cyl: 0.25 });
|
|
153
181
|
parts.push({ d: path([[18.4, 37], [45.6, 37], [45.7, 39.8], [18.3, 39.8]]), fill: "$main", shade: 0.2 });
|
|
@@ -159,9 +187,20 @@ function judogiFlat() {
|
|
|
159
187
|
parts.push(dark(path(k) + rows(k, 51.4, 56.4, 1.6, 0.3), 0.28, 0.35));
|
|
160
188
|
}
|
|
161
189
|
parts.push(dark("M17.6 60.2H29.6M34.4 60.2H46.4", 0.3, 0.3));
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
190
|
+
return parts;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The judogi laid flat: jacket (sleeves out to the sides, lapels crossed left over right, thick stitched collar, quilted upper body, vented skirt), the belt tied over it and the trousers below with a drawstring
|
|
194
|
+
* waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
|
|
195
|
+
*/
|
|
196
|
+
export function judogiFlat(torso = false) {
|
|
197
|
+
const parts = [];
|
|
198
|
+
// Trousers, behind everything: straight legs, a waistband with its drawstring, reinforced knees. A torso (the jacket alone, for the close-ups of the belt) has no trousers, no sleeves and no belt.
|
|
199
|
+
if (!torso)
|
|
200
|
+
parts.push(...judogiTrousers());
|
|
201
|
+
// Sleeves: to the wrist, the arms hanging out and down (IJF C1.10: the sleeve covers the wrist bone, at most 5 cm short of it, and is loose all along the forearm).
|
|
202
|
+
const sleeves = torso ? [] : [JUDOGI_SLEEVE, mirror(JUDOGI_SLEEVE)];
|
|
203
|
+
for (const pts of sleeves)
|
|
165
204
|
parts.push({ d: path(pts), fill: "$main", cyl: 0.15, shade: 0.35 });
|
|
166
205
|
const jacket = [[19.4, 6.6], [26.2, 4], [32, 3.2], [37.8, 4], [44.6, 6.6], [45.6, 8], [45.6, 36], [18.4, 36], [18.4, 8]];
|
|
167
206
|
parts.push({ d: path(jacket), fill: "$main", shade: 0.35, cyl: 0.18 });
|
|
@@ -169,15 +208,11 @@ function judogiFlat() {
|
|
|
169
208
|
const body = [[19, 7], [45, 7], [45.6, 26], [18.4, 26]];
|
|
170
209
|
parts.push(dark(rows(body, 8, 25, 1.4, 0.5), 0.28, 0.1));
|
|
171
210
|
parts.push(light(rows(body, 8.5, 25.5, 1.4, 0.5), 0.24, 0.2));
|
|
172
|
-
for (const pts of
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
d += `M${f(a2[0])} ${f(a2[1])}L${f(b2[0])} ${f(b2[1])}`;
|
|
178
|
-
}
|
|
179
|
-
parts.push(dark(d, 0.28, 0.1));
|
|
180
|
-
}
|
|
211
|
+
for (const pts of sleeves)
|
|
212
|
+
parts.push(dark(sleeveRows(pts), 0.28, 0.1));
|
|
213
|
+
// The loose cuff: a hem stitched a little in from the open end, on both sleeves.
|
|
214
|
+
for (const pts of sleeves)
|
|
215
|
+
parts.push(dark(cuffHem(pts), 0.3, 0.38));
|
|
181
216
|
// The neck: the collar goes round the back of the neck, and the opening is a V that ends under 10 cm below the sternum.
|
|
182
217
|
parts.push({ d: path([[26.4, 4.2], [37.6, 4.2], [33.4, 14]]), fill: INK, stroke: INK, w: 0.3, op: 0.62, line: "none" });
|
|
183
218
|
parts.push({ d: "M25.4 5.4Q32 2 38.6 5.4L37.6 8.2Q32 5.4 26.4 8.2Z", fill: "$main", cyl: 0.4, shade: 0.3 });
|
|
@@ -202,7 +237,8 @@ function judogiFlat() {
|
|
|
202
237
|
parts.push(dark("M18.8 34.4H44.8", 0.3, 0.32));
|
|
203
238
|
parts.push(dark("M20.2 31.4V36M43.8 31.4V36", 0.4, 0.45));
|
|
204
239
|
// The belt, over the jacket, wrapped twice.
|
|
205
|
-
|
|
240
|
+
if (!torso)
|
|
241
|
+
parts.push(...belt({ edge: BELT_EDGE, main: "$accent", cx: 32, x0: 18.2, x1: 45.8, y: 25.2, w: 2.5, sag: 0, knot: 6.4, tail: 11.4, splay: 6 }));
|
|
206
242
|
return parts;
|
|
207
243
|
}
|
|
208
244
|
/** The judogi folded into a neat rectangle: collar and lapel stitching on top, the belt tied round it. */
|
|
@@ -241,6 +277,109 @@ export const GEAR_JUDOGI = /* @__PURE__ */ drawn("gear:judogi", "Judo suit", "
|
|
|
241
277
|
export const GEAR_JUDOGI_BLUE = /* @__PURE__ */ drawn("gear:judogi-blue", "Blue judo suit", "青い柔道衣", { main: JUDOGI_BLUE, accent: OBI_RANKS.black, parts: judogiFlat() });
|
|
242
278
|
export const GEAR_JUDOGI_FOLDED = /* @__PURE__ */ drawn("gear:judogi-folded", "Folded judo suit", "畳んだ柔道衣", { main: JUDOGI_WHITE, accent: OBI_RANKS.black, parts: judogiFolded() });
|
|
243
279
|
export const GEAR_JUDOGI_BLUE_FOLDED = /* @__PURE__ */ drawn("gear:judogi-blue-folded", "Folded blue judo suit", "畳んだ青い柔道衣", { main: JUDOGI_BLUE, accent: OBI_RANKS.black, parts: judogiFolded() });
|
|
280
|
+
// ───────────────────────────── the judogi from behind, with its back number ─────────────────────────────
|
|
281
|
+
/** The bands of the back number (IJF C1.8): blue for everyone, gold for an Olympic champion and red for a world champion while the title is held. Each with the ink of the family name and the ink of the country code. */
|
|
282
|
+
const BAND = {
|
|
283
|
+
blue: { band: "#26318f", name: "#fefefe", code: "#26318f" },
|
|
284
|
+
gold: { band: "#d8a31a", name: "#1b2430", code: "#1b2430" },
|
|
285
|
+
red: { band: "#c4202e", name: "#fefefe", code: "#c4202e" },
|
|
286
|
+
};
|
|
287
|
+
/** The white of the patch. */
|
|
288
|
+
const PATCH_WHITE = "#fcfcf9";
|
|
289
|
+
/** The back number's size and place, in this drawing's units: the large one is 38 cm wide and 39 cm high (the manual of the IJF's partner, docs/references.md), and the jacket's 27.2 units across stand for about 60 cm. */
|
|
290
|
+
const BACKNUMBER = { x0: 23.4, x1: 40.6, y0: 8.4, y1: 26.1 };
|
|
291
|
+
const cleanName = (value) => typeof value === "string" ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().replace(/[^A-Z0-9 '-]/g, "").replace(/\s+/g, " ").trim().slice(0, 16) : "";
|
|
292
|
+
const cleanCode = (value) => (typeof value === "string" ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().replace(/[^A-Z]/g, "").slice(0, 3) : "");
|
|
293
|
+
/** A dashed line round the edge of a rectangle, `inset` in from it: the stitching that holds the patch on. */
|
|
294
|
+
function stitches(x0, y0, x1, y1, inset, dash = 0.9, gap = 0.75) {
|
|
295
|
+
let d = "";
|
|
296
|
+
const run = (xa, ya, xb, yb) => {
|
|
297
|
+
const len = Math.hypot(xb - xa, yb - ya);
|
|
298
|
+
for (let t = 0; t < len - 0.2; t += dash + gap) {
|
|
299
|
+
const e = Math.min(t + dash, len);
|
|
300
|
+
d += `M${f(xa + ((xb - xa) * t) / len)} ${f(ya + ((yb - ya) * t) / len)}L${f(xa + ((xb - xa) * e) / len)} ${f(ya + ((yb - ya) * e) / len)}`;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const a = x0 + inset, b = x1 - inset, c = y0 + inset, e = y1 - inset;
|
|
304
|
+
run(a, c, b, c);
|
|
305
|
+
run(b, c, b, e);
|
|
306
|
+
run(b, e, a, e);
|
|
307
|
+
run(a, e, a, c);
|
|
308
|
+
return d;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* The back number as parts: a white patch with the family name in white capitals on a band across the top (the band arched), the three letters of the National Olympic Committee in capitals under it, and the rest
|
|
312
|
+
* of the patch blank, where the event's sponsor goes. A small square at the left edge stands for the label that makes it authentic. No flag (the IJF's patch has none), no logo, no mark of the IJF or of the maker.
|
|
313
|
+
*/
|
|
314
|
+
function backnumberParts(back) {
|
|
315
|
+
const parts = [];
|
|
316
|
+
const { x0, x1, y0, y1 } = BACKNUMBER;
|
|
317
|
+
const w = x1 - x0, cx = (x0 + x1) / 2;
|
|
318
|
+
const colours = BAND[back?.band === "gold" || back?.band === "red" ? back.band : "blue"];
|
|
319
|
+
const name = back?.name === undefined ? "TANAKA" : cleanName(back.name);
|
|
320
|
+
const code = back?.code === undefined ? "JPN" : cleanCode(back.code);
|
|
321
|
+
parts.push({ d: rect(x0, y0, w, y1 - y0, 0.3), fill: PATCH_WHITE, stroke: "#7d8594", w: 0.45, shade: 0.12 });
|
|
322
|
+
parts.push(dark(stitches(x0, y0, x1, y1, 0.5), 0.26, 0.45));
|
|
323
|
+
parts.push({ d: `M${x0} ${f(y0 + 0.9)}Q${f(cx)} ${f(y0 - 0.5)} ${x1} ${f(y0 + 0.9)}L${x1} ${f(y0 + 4.6)}Q${f(cx)} ${f(y0 + 3.4)} ${x0} ${f(y0 + 4.6)}Z`, fill: colours.band, stroke: colours.band, w: 0.3, shade: 0 });
|
|
324
|
+
const letters = { weight: 0.2, wide: 0.78 };
|
|
325
|
+
const nameSize = fitSize(name, letters, w - 3, 2.8);
|
|
326
|
+
if (name !== "")
|
|
327
|
+
parts.push({ d: linePath(name, { ...letters, size: nameSize }, cx, y0 + 2.4), fill: "none", stroke: colours.name, w: letters.weight * nameSize });
|
|
328
|
+
const codeLetters = { weight: 0.24, wide: 1.05 };
|
|
329
|
+
const codeSize = fitSize(code, codeLetters, 10, 3.2);
|
|
330
|
+
if (code !== "")
|
|
331
|
+
parts.push({ d: linePath(code, { ...codeLetters, size: codeSize }, cx, y0 + 8.4), fill: "none", stroke: colours.code, w: codeLetters.weight * codeSize });
|
|
332
|
+
parts.push({ d: rect(x0 + 0.9, y0 + 7.6, 1.3, 1.3), fill: "#1b2430", stroke: "none", shade: 0, line: "ink" });
|
|
333
|
+
return parts;
|
|
334
|
+
}
|
|
335
|
+
/** The judogi laid flat and seen from behind: collar, the back of the jacket with the back number sewn on it (its top 3 cm under the collar), the belt crossed behind the waist and the trousers. `$main` is the cloth, `$accent` the belt. */
|
|
336
|
+
function judogiBack(back) {
|
|
337
|
+
const parts = [];
|
|
338
|
+
const trousers = [[18.4, 37], [45.6, 37], [46.6, 62], [34.6, 62], [32, 43.6], [29.4, 62], [17.4, 62]];
|
|
339
|
+
parts.push({ d: path(trousers), fill: "$main", shade: 0.3, cyl: 0.25 });
|
|
340
|
+
parts.push({ d: path([[18.4, 37], [45.6, 37], [45.7, 39.8], [18.3, 39.8]]), fill: "$main", shade: 0.2 });
|
|
341
|
+
parts.push(dark("M18.6 38.9H45.4", 0.3, 0.35));
|
|
342
|
+
parts.push(dark("M17.6 60.2H29.6M34.4 60.2H46.4M32 39.8V43.4", 0.3, 0.3));
|
|
343
|
+
const sleeve = JUDOGI_SLEEVE;
|
|
344
|
+
for (const pts of [sleeve, mirror(sleeve)])
|
|
345
|
+
parts.push({ d: path(pts), fill: "$main", cyl: 0.15, shade: 0.35 });
|
|
346
|
+
const jacket = [[19.4, 6.6], [26.2, 4], [32, 3.2], [37.8, 4], [44.6, 6.6], [45.6, 8], [45.6, 36], [18.4, 36], [18.4, 8]];
|
|
347
|
+
parts.push({ d: path(jacket), fill: "$main", shade: 0.35, cyl: 0.18 });
|
|
348
|
+
parts.push(dark(rows([[19, 7], [45, 7], [45.6, 35], [18.4, 35]], 9, 34, 1.4, 0.5), 0.28, 0.1));
|
|
349
|
+
parts.push(light(rows([[19, 7], [45, 7], [45.6, 35], [18.4, 35]], 9.5, 34.5, 1.4, 0.5), 0.24, 0.2));
|
|
350
|
+
for (const pts of [sleeve, mirror(sleeve)])
|
|
351
|
+
parts.push(dark(sleeveRows(pts), 0.28, 0.1));
|
|
352
|
+
for (const pts of [sleeve, mirror(sleeve)])
|
|
353
|
+
parts.push(dark(cuffHem(pts), 0.3, 0.38));
|
|
354
|
+
parts.push(dark("M18.8 34.4H44.8", 0.3, 0.32));
|
|
355
|
+
parts.push(dark("M20.2 31.4V36M43.8 31.4V36", 0.4, 0.45));
|
|
356
|
+
parts.push(...backnumberParts(back));
|
|
357
|
+
// The collar goes round the back of the neck: a thick, stitched band.
|
|
358
|
+
parts.push({ d: "M24.6 5.4Q32 2 39.4 5.4L38.8 8.2Q32 5.6 25.2 8.2Z", fill: "$main", cyl: 0.4, shade: 0.3 });
|
|
359
|
+
parts.push(dark("M25.2 6.9Q32 4.2 38.8 6.9", 0.28, 0.4));
|
|
360
|
+
// The belt, crossed behind the waist: two wraps, and the two turns that cross in the middle of the back.
|
|
361
|
+
const edge = { stroke: BELT_EDGE, cyl: 0.5, shade: 0.25 };
|
|
362
|
+
parts.push({ d: path([[18.2, 27.2], [45.8, 27.2], [45.8, 29.7], [18.2, 29.7]]), fill: "$accent", ...edge });
|
|
363
|
+
parts.push({ d: path([[18.2, 29.4], [45.8, 29.4], [45.8, 31.9], [18.2, 31.9]]), fill: "$accent", ...edge });
|
|
364
|
+
parts.push({ d: path(strip([28.4, 27.2], [35.6, 31.9], 2.5)), fill: "$accent", ...edge });
|
|
365
|
+
parts.push({ d: path(strip([35.6, 27.2], [28.4, 31.9], 2.5)), fill: "$accent", ...edge });
|
|
366
|
+
parts.push(dark("M18.6 28.4H27M37 28.4H45.4M18.6 30.6H27M37 30.6H45.4", 0.26, 0.3));
|
|
367
|
+
return parts;
|
|
368
|
+
}
|
|
369
|
+
/** The title of a back view: its name, then (when the call gave a back number) the family name and the country code that are on it. */
|
|
370
|
+
const backTitle = (en, back) => {
|
|
371
|
+
if (back === undefined)
|
|
372
|
+
return en;
|
|
373
|
+
const name = back.name === undefined ? "TANAKA" : cleanName(back.name);
|
|
374
|
+
const code = back.code === undefined ? "JPN" : cleanCode(back.code);
|
|
375
|
+
return [en, `${name} ${code}`.trim()].filter((t) => t !== "").join(", ");
|
|
376
|
+
};
|
|
377
|
+
const backPiece = (id, en, ja, main) => {
|
|
378
|
+
const spec = { main, accent: OBI_RANKS.black, parts: judogiBack(undefined) };
|
|
379
|
+
return defineGear({ id, en, ja, source: "drawn", model: spec, draw: (o) => drawGear(id, backTitle(en, o?.backnumber), { ...spec, parts: o?.backnumber === undefined ? spec.parts : judogiBack(o.backnumber) }, o) });
|
|
380
|
+
};
|
|
381
|
+
export const GEAR_JUDOGI_BACK = /* @__PURE__ */ backPiece("gear:judogi-back", "Judo suit, back view", "柔道衣の背面", JUDOGI_WHITE);
|
|
382
|
+
export const GEAR_JUDOGI_BLUE_BACK = /* @__PURE__ */ backPiece("gear:judogi-blue-back", "Blue judo suit, back view", "青い柔道衣の背面", JUDOGI_BLUE);
|
|
244
383
|
// ───────────────────────────── the karate-gi ─────────────────────────────
|
|
245
384
|
/** The karate-gi laid flat: lighter cloth (no quilting), a plain narrow collar, shorter jacket and sleeves than the judogi, tie strings, ankle-length trousers and the belt. `$main` is the cloth, `$accent` the belt. */
|
|
246
385
|
function karateFlat() {
|
|
@@ -251,10 +390,12 @@ function karateFlat() {
|
|
|
251
390
|
parts.push(dark("M19.6 41.4H44.4", 0.3, 0.3));
|
|
252
391
|
parts.push({ d: "M26 41.8Q24.4 44.6 25.4 47M27.6 41.8Q28.4 44.4 27.4 46.8", fill: "none", stroke: INK, w: 0.35, op: 0.45, line: "none" });
|
|
253
392
|
parts.push(dark("M18.8 60H29.6M34.4 60H45.2", 0.3, 0.25));
|
|
254
|
-
// Sleeves reach the bend of the wrist at most (WKF 2.2.1 j),
|
|
255
|
-
const sleeve =
|
|
393
|
+
// Sleeves reach the bend of the wrist at most and halfway down the forearm at least (WKF 2.2.1 j): three-quarter length, a little shorter than a judogi's, with a wide cuff.
|
|
394
|
+
const sleeve = KARATE_SLEEVE;
|
|
256
395
|
for (const pts of [sleeve, mirror(sleeve)])
|
|
257
396
|
parts.push({ d: path(pts), fill: "$main", shade: 0.3, cyl: 0.12 });
|
|
397
|
+
for (const pts of [sleeve, mirror(sleeve)])
|
|
398
|
+
parts.push(dark(cuffHem(pts, 0.88), 0.3, 0.3));
|
|
258
399
|
const jacket = [[20.2, 8], [26.6, 5.2], [32, 4.4], [37.4, 5.2], [43.8, 8], [44.8, 38.6], [19.2, 38.6]];
|
|
259
400
|
parts.push({ d: path(jacket), fill: "$main", shade: 0.3, cyl: 0.15 });
|
|
260
401
|
// Neck: a wide V that ends lower than the judogi's, a plain thin collar.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Part } from "./gear.core.ts";
|
|
2
|
+
import type { PieceOptions } from "../ball/ball.core.ts";
|
|
3
|
+
export type ObiStep = 1 | 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
export declare const GEAR_OBI_STEP_1: import("./gear.index.ts").Piece<{
|
|
5
|
+
main: string;
|
|
6
|
+
accent: string;
|
|
7
|
+
parts: Part[];
|
|
8
|
+
}>;
|
|
9
|
+
export declare const GEAR_OBI_STEP_2: import("./gear.index.ts").Piece<{
|
|
10
|
+
main: string;
|
|
11
|
+
accent: string;
|
|
12
|
+
parts: Part[];
|
|
13
|
+
}>;
|
|
14
|
+
export declare const GEAR_OBI_STEP_3: import("./gear.index.ts").Piece<{
|
|
15
|
+
main: string;
|
|
16
|
+
accent: string;
|
|
17
|
+
parts: Part[];
|
|
18
|
+
}>;
|
|
19
|
+
export declare const GEAR_OBI_STEP_4: import("./gear.index.ts").Piece<{
|
|
20
|
+
main: string;
|
|
21
|
+
accent: string;
|
|
22
|
+
parts: Part[];
|
|
23
|
+
}>;
|
|
24
|
+
export declare const GEAR_OBI_STEP_5: import("./gear.index.ts").Piece<{
|
|
25
|
+
main: string;
|
|
26
|
+
accent: string;
|
|
27
|
+
parts: Part[];
|
|
28
|
+
}>;
|
|
29
|
+
export declare const GEAR_OBI_STEP_6: import("./gear.index.ts").Piece<{
|
|
30
|
+
main: string;
|
|
31
|
+
accent: string;
|
|
32
|
+
parts: Part[];
|
|
33
|
+
}>;
|
|
34
|
+
/** One step of tying the belt, 1 to 6, with the usual options (`tint` is the jacket's colour, `accent` the belt's). A number that is not a step from 1 to 6 draws the last one. */
|
|
35
|
+
export declare const obiStep: (step: ObiStep, options?: PieceOptions) => string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// How a judo belt is tied, in six pictures: the torso of the judogi (the same jacket as `gear:judogi`, lapels crossed left over right, so the wearer's left panel lies on top and the funeral wrap's opposite is
|
|
2
|
+
// avoided) in close-up, and the belt (`$accent`, black by default) at each stage. The belt is the one `gear:judogi` and the belts are drawn with, so a step's belt takes `accent` like theirs.
|
|
3
|
+
//
|
|
4
|
+
// 1. the middle of the belt against the belly; 2. wrapped twice round the waist, both ends back in front; 3. the ends crossed in front; 4. one end brought up under both layers;
|
|
5
|
+
// 5. the flat square knot (hon-musubi, 本結び); 6. both ends hanging even.
|
|
6
|
+
//
|
|
7
|
+
// The close-up is the jacket alone, drawn 1.55 times as big, so that the belt's width and the knot read at small sizes; a red number says which step it is, and a few small red marks (the middle, the way an end
|
|
8
|
+
// travels, the equal lengths) say what to look at. The marks and the number are not the suit's, so they ignore `tint` and `accent`.
|
|
9
|
+
import { circle, defineGear, drawGear, rect } from "./gear.core.js";
|
|
10
|
+
import { fitSize, linePath } from "../ball/ball.letters.js";
|
|
11
|
+
import { BELT_EDGE, belt, dark, f, judogiFlat, light, lerp, path, strip } from "./gear.budo.js";
|
|
12
|
+
/** The belt's width in the jacket's own units (the judogi's), the two wraps' tops, and the middle of the belly. */
|
|
13
|
+
const W = 2.5;
|
|
14
|
+
const Y_UPPER = 25.2;
|
|
15
|
+
const Y_LOWER = 27.4;
|
|
16
|
+
const X0 = 18.2;
|
|
17
|
+
const X1 = 45.8;
|
|
18
|
+
const MID = 32;
|
|
19
|
+
/** The length of the two ends in these pictures: a little shorter than the judogi's, so that they stay inside the picture. */
|
|
20
|
+
const TAIL = 9.5;
|
|
21
|
+
/** The red of the marks. */
|
|
22
|
+
const MARK = "#d8342c";
|
|
23
|
+
const MARK_EDGE = "#8f1d17";
|
|
24
|
+
/** The close-up: the jacket, 1.55 times as big, with the belt about at the middle of the picture. */
|
|
25
|
+
const ZOOM = "translate(32 32) scale(1.55) translate(-32 -21.6)";
|
|
26
|
+
/** One wrap of the belt across the front, its top at `y`, drawn as the judogi's belt is: the band, two rows of stitching and their highlights. */
|
|
27
|
+
function wrap(y, x0 = X0, x1 = X1) {
|
|
28
|
+
const row = (off) => `M${f(x0 + 0.8)} ${f(y + W * off)}H${f(x1 - 0.8)}`;
|
|
29
|
+
return [
|
|
30
|
+
{ d: rect(x0, y, Math.round((x1 - x0) * 10) / 10, W), fill: "$accent", stroke: BELT_EDGE, cyl: 0.5, shade: 0.25 },
|
|
31
|
+
dark(row(0.3) + row(0.7), 0.28, 0.3),
|
|
32
|
+
light(`M${f(x0 + 0.8)} ${f(y + W * 0.3 + 0.35)}H${f(x1 - 0.8)}M${f(x0 + 0.8)} ${f(y + W * 0.7 + 0.35)}H${f(x1 - 0.8)}`, 0.25, 0.2),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
/** One end of the belt, from `a` to `b`: a strip with its two rows of stitching. */
|
|
36
|
+
function end(a, b, width = W * 1.02) {
|
|
37
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
38
|
+
const l = Math.hypot(dx, dy) || 1;
|
|
39
|
+
const stitch = (off, t0, t1) => {
|
|
40
|
+
const p = lerp(a, b, t0), q = lerp(a, b, t1);
|
|
41
|
+
const nx = (-dy / l) * off, ny = (dx / l) * off;
|
|
42
|
+
return `M${f(p[0] + nx)} ${f(p[1] + ny)}L${f(q[0] + nx)} ${f(q[1] + ny)}`;
|
|
43
|
+
};
|
|
44
|
+
return [
|
|
45
|
+
{ d: path(strip(a, b, width)), fill: "$accent", stroke: BELT_EDGE, cyl: 0.55, shade: 0.25 },
|
|
46
|
+
dark(stitch(-width * 0.18, 0.15, 0.97) + stitch(width * 0.18, 0.15, 0.97), 0.28, 0.3),
|
|
47
|
+
light(stitch(-width * 0.18 + 0.35, 0.15, 0.97) + stitch(width * 0.18 + 0.35, 0.15, 0.97), 0.25, 0.2),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
/** A small red mark: a line or a filled shape in the colour of the marks, drawn as ink in the line style too. */
|
|
51
|
+
const mark = (d, w = 0.6) => ({ d, fill: "none", stroke: MARK, w });
|
|
52
|
+
/** An arrow from `a` to `b`, with its head. */
|
|
53
|
+
function arrow(a, b, head = 1.7) {
|
|
54
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
55
|
+
const l = Math.hypot(dx, dy) || 1;
|
|
56
|
+
const ux = dx / l, uy = dy / l;
|
|
57
|
+
const h = (s) => `${f(b[0] - ux * head + -uy * head * s * 0.6)} ${f(b[1] - uy * head + ux * head * s * 0.6)}`;
|
|
58
|
+
return mark(`M${f(a[0])} ${f(a[1])}L${f(b[0])} ${f(b[1])}M${h(1)}L${f(b[0])} ${f(b[1])}L${h(-1)}`, 0.7);
|
|
59
|
+
}
|
|
60
|
+
/** The belt at each step, in the jacket's units, back to front. */
|
|
61
|
+
function beltAt(step) {
|
|
62
|
+
const ends = (cx) => [cx - 3.2, cx + 3.2];
|
|
63
|
+
switch (step) {
|
|
64
|
+
case 1: {
|
|
65
|
+
// One layer across the belly, a notch marking the middle of the belt, and a red arrow down to it.
|
|
66
|
+
const y = 26.3;
|
|
67
|
+
return [...wrap(y), mark(`M${MID} ${f(y + 0.3)}V${f(y + W - 0.3)}`, 0.5), arrow([MID, y - 5.6], [MID, y - 0.8])];
|
|
68
|
+
}
|
|
69
|
+
case 2: {
|
|
70
|
+
// Two wraps, and the ends that came round the back hanging free in front, apart.
|
|
71
|
+
const [l, r] = ends(MID);
|
|
72
|
+
return [...wrap(Y_LOWER), ...wrap(Y_UPPER), ...end([l, Y_LOWER + 0.8], [l - 0.4, Y_LOWER + 0.8 + TAIL]), ...end([r, Y_LOWER + 0.8], [r + 0.4, Y_LOWER + 0.8 + TAIL])];
|
|
73
|
+
}
|
|
74
|
+
case 3: {
|
|
75
|
+
// The ends cross: the one on the wearer's left (the right of the picture) lies over the other.
|
|
76
|
+
const top = Y_LOWER + 0.8;
|
|
77
|
+
return [...wrap(Y_LOWER), ...wrap(Y_UPPER), ...end([MID - 2.4, top], [MID + 3.6, top + TAIL - 0.6]), ...end([MID + 2.4, top], [MID - 3.6, top + TAIL - 0.6])];
|
|
78
|
+
}
|
|
79
|
+
case 4: {
|
|
80
|
+
// One end goes up under both wraps: it shows below the belt and again above it, the wraps hiding the middle of it, and a red arrow follows its way.
|
|
81
|
+
const x = MID - 1.8;
|
|
82
|
+
const under = end([x, Y_UPPER - 4.2], [x, Y_LOWER + 6.8]);
|
|
83
|
+
const hanging = end([MID + 3.4, Y_LOWER + 0.8], [MID + 3.8, Y_LOWER + 0.8 + TAIL]);
|
|
84
|
+
return [...under, ...wrap(Y_LOWER), ...wrap(Y_UPPER), ...hanging, arrow([MID - 5.8, Y_LOWER + 8.4], [MID - 5.8, Y_UPPER - 5.2])];
|
|
85
|
+
}
|
|
86
|
+
case 5: {
|
|
87
|
+
// The knot: two half knots, the second laid the other way (the flat square knot), so the ends are not yet even, and a red turn over it for each half.
|
|
88
|
+
const y = Y_UPPER + (Y_LOWER + W - Y_UPPER) / 2;
|
|
89
|
+
return [
|
|
90
|
+
...belt({ edge: BELT_EDGE, main: "$accent", cx: MID, x0: X0, x1: X1, y: Y_UPPER, w: W, sag: 0, knot: 6.4, tail: TAIL + 1.2, tail2: TAIL - 2.6, splay: 14 }),
|
|
91
|
+
mark(`M${MID - 4.4} ${f(y - 4.2)}Q${MID} ${f(y - 7.4)} ${MID + 4.4} ${f(y - 4.2)}`, 0.6),
|
|
92
|
+
arrow([MID + 3.6, y - 6.4], [MID + 4.6, y - 4]),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
case 6: {
|
|
96
|
+
// Tied: both ends hang even, and a red mark under them shows their tips on one line.
|
|
97
|
+
const yc = (Y_UPPER + Y_LOWER - 0.3 + W) / 2 + 0.2 * 3;
|
|
98
|
+
const tip = yc + Math.cos((6 * Math.PI) / 180) * TAIL;
|
|
99
|
+
const y = tip + 2;
|
|
100
|
+
return [
|
|
101
|
+
...belt({ edge: BELT_EDGE, main: "$accent", cx: MID, x0: X0, x1: X1, y: Y_UPPER, w: W, sag: 0, knot: 6.4, tail: TAIL, splay: 6 }),
|
|
102
|
+
mark(`M${MID - 4.6} ${f(y)}H${MID + 4.6}M${MID - 4.6} ${f(y - 1)}V${f(y + 1)}M${MID + 4.6} ${f(y - 1)}V${f(y + 1)}`, 0.5),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** The step's number in a red disc at the top left, in the picture's own units (not zoomed). */
|
|
108
|
+
function badge(step) {
|
|
109
|
+
const text = String(step);
|
|
110
|
+
const letters = { weight: 0.2, wide: 1 };
|
|
111
|
+
const size = fitSize(text, letters, 5, 5.2);
|
|
112
|
+
return [
|
|
113
|
+
{ d: circle(7.4, 7.4, 5), fill: MARK, stroke: MARK_EDGE, w: 0.7, shade: 0.4 },
|
|
114
|
+
{ d: linePath(text, { ...letters, size }, 7.4, 7.4), fill: "none", stroke: "#ffffff", w: letters.weight * size, line: "stroke" },
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
/** The parts of a step: the jacket in close-up, the belt at that stage and the number. */
|
|
118
|
+
function stepParts(step) {
|
|
119
|
+
const zoomed = [...judogiFlat(true), ...beltAt(step)].map((p) => ({ ...p, t: ZOOM }));
|
|
120
|
+
return [...zoomed, ...badge(step)];
|
|
121
|
+
}
|
|
122
|
+
const STEPS = [
|
|
123
|
+
{ en: "Belt step 1: centre", ja: "帯の結び方(1)中心" },
|
|
124
|
+
{ en: "Belt step 2: two wraps", ja: "帯の結び方(2)二重巻き" },
|
|
125
|
+
{ en: "Belt step 3: cross", ja: "帯の結び方(3)交差" },
|
|
126
|
+
{ en: "Belt step 4: tuck under", ja: "帯の結び方(4)下に通す" },
|
|
127
|
+
{ en: "Belt step 5: square knot", ja: "帯の結び方(5)本結び" },
|
|
128
|
+
{ en: "Belt step 6: even ends", ja: "帯の結び方(6)仕上げ" },
|
|
129
|
+
];
|
|
130
|
+
const SUIT = "#f6f4ee";
|
|
131
|
+
const BLACK = "#1f2228";
|
|
132
|
+
const stepPiece = (step) => {
|
|
133
|
+
const id = `gear:obi-step-${step}`;
|
|
134
|
+
const { en, ja } = STEPS[step - 1];
|
|
135
|
+
const spec = { main: SUIT, accent: BLACK, parts: stepParts(step) };
|
|
136
|
+
return defineGear({ id, en, ja, source: "drawn", model: spec, draw: (o) => drawGear(id, en, spec, o) });
|
|
137
|
+
};
|
|
138
|
+
export const GEAR_OBI_STEP_1 = /* @__PURE__ */ stepPiece(1);
|
|
139
|
+
export const GEAR_OBI_STEP_2 = /* @__PURE__ */ stepPiece(2);
|
|
140
|
+
export const GEAR_OBI_STEP_3 = /* @__PURE__ */ stepPiece(3);
|
|
141
|
+
export const GEAR_OBI_STEP_4 = /* @__PURE__ */ stepPiece(4);
|
|
142
|
+
export const GEAR_OBI_STEP_5 = /* @__PURE__ */ stepPiece(5);
|
|
143
|
+
export const GEAR_OBI_STEP_6 = /* @__PURE__ */ stepPiece(6);
|
|
144
|
+
const BY_STEP = [GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6];
|
|
145
|
+
/** One step of tying the belt, 1 to 6, with the usual options (`tint` is the jacket's colour, `accent` the belt's). A number that is not a step from 1 to 6 draws the last one. */
|
|
146
|
+
export const obiStep = (step, options) => BY_STEP[Number.isInteger(step) && step >= 1 && step <= 6 ? step - 1 : 5].svg(options);
|