@johnmorrisdotca/dougu-sports 0.27.0 → 0.28.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +3 -3
  3. package/dist/emblems/emblem.descriptions.js +15 -4
  4. package/dist/emblems/emblem.ids.d.ts +2 -2
  5. package/dist/emblems/emblem.ids.js +11 -0
  6. package/dist/emblems/emblem.lathe.d.ts +79 -8
  7. package/dist/emblems/emblem.lathe.js +21 -7
  8. package/dist/emblems/emblem.named.js +115 -5
  9. package/dist/emblems/emblem.registry.js +22 -0
  10. package/dist/emblems/lathe.ballrods.d.ts +2 -0
  11. package/dist/emblems/lathe.ballrods.js +155 -0
  12. package/dist/emblems/lathe.conference.d.ts +2 -0
  13. package/dist/emblems/lathe.conference.js +219 -0
  14. package/dist/emblems/lathe.extras.d.ts +3 -1
  15. package/dist/emblems/lathe.extras.js +77 -2
  16. package/dist/emblems/lathe.framed.d.ts +30 -0
  17. package/dist/emblems/lathe.framed.js +98 -0
  18. package/dist/emblems/lathe.sculpt.js +73 -20
  19. package/dist/emblems/nba.parts.d.ts +27 -0
  20. package/dist/emblems/nba.parts.js +34 -0
  21. package/dist/emblems/trophy/bill-russell-trophy.d.ts +1 -1
  22. package/dist/emblems/trophy/bill-russell-trophy.js +17 -13
  23. package/dist/emblems/trophy/bob-cousy-trophy.d.ts +2 -0
  24. package/dist/emblems/trophy/bob-cousy-trophy.js +18 -0
  25. package/dist/emblems/trophy/foundation-player-award.js +2 -0
  26. package/dist/emblems/trophy/george-halas-trophy.d.ts +2 -0
  27. package/dist/emblems/trophy/george-halas-trophy.js +6 -0
  28. package/dist/emblems/trophy/jackie-robinson-award.d.ts +2 -0
  29. package/dist/emblems/trophy/jackie-robinson-award.js +60 -0
  30. package/dist/emblems/trophy/lamar-hunt-trophy.d.ts +1 -1
  31. package/dist/emblems/trophy/lamar-hunt-trophy.js +5 -16
  32. package/dist/emblems/trophy/larry-bird-trophy.d.ts +2 -0
  33. package/dist/emblems/trophy/larry-bird-trophy.js +23 -0
  34. package/dist/emblems/trophy/larry-obrien-trophy-1977.d.ts +2 -0
  35. package/dist/emblems/trophy/larry-obrien-trophy-1977.js +23 -0
  36. package/dist/emblems/trophy/larry-obrien-trophy.d.ts +1 -1
  37. package/dist/emblems/trophy/larry-obrien-trophy.js +18 -11
  38. package/dist/emblems/trophy/magic-johnson-trophy.d.ts +2 -0
  39. package/dist/emblems/trophy/magic-johnson-trophy.js +20 -0
  40. package/dist/emblems/trophy/messier-leadership-award.d.ts +2 -0
  41. package/dist/emblems/trophy/messier-leadership-award.js +79 -0
  42. package/dist/emblems/trophy/nba-eastern-conference-2001.d.ts +2 -0
  43. package/dist/emblems/trophy/nba-eastern-conference-2001.js +18 -0
  44. package/dist/emblems/trophy/nba-western-conference-2001.d.ts +2 -0
  45. package/dist/emblems/trophy/nba-western-conference-2001.js +19 -0
  46. package/dist/emblems/trophy/oscar-robertson-trophy.d.ts +2 -0
  47. package/dist/emblems/trophy/oscar-robertson-trophy.js +19 -0
  48. package/dist/emblems/trophy/willie-oree-award.d.ts +2 -0
  49. package/dist/emblems/trophy/willie-oree-award.js +55 -0
  50. package/dist/emblems/trophy/wnba-championship-trophy.d.ts +1 -1
  51. package/dist/emblems/trophy/wnba-championship-trophy.js +6 -17
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +11 -0
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/package.json +1 -1
@@ -9,7 +9,7 @@ const pt = (x, y) => `${f(x)} ${f(y)}`;
9
9
  export function LATHE_EXTRAS(c, d) {
10
10
  const { p, solid, yaw, where, at } = c;
11
11
  if (d[0] === "mesh") {
12
- const [, y0, y1, n, rows] = d;
12
+ const [, y0, y1, n, rows, name, width = 0.55] = d;
13
13
  let m = "";
14
14
  for (let i = 0; i < n; i++) {
15
15
  for (const way of [1, -1]) {
@@ -27,7 +27,13 @@ export function LATHE_EXTRAS(c, d) {
27
27
  }
28
28
  }
29
29
  }
30
- return m === "" ? "" : `<path d="${m}" stroke="${mix(c.base.rgb, 0.55)}" stroke-width=".55" stroke-opacity=".9"/>`;
30
+ if (m === "")
31
+ return "";
32
+ if (name === undefined)
33
+ return `<path d="${m}" stroke="${mix(c.base.rgb, 0.55)}" stroke-width=".55" stroke-opacity=".9"/>`;
34
+ // An etched net in a tone of its own: a dark line, then the tone laid on it a little to the side.
35
+ const t = c.tone(name);
36
+ return `<path d="${m}" transform="translate(.2 .25)" stroke="${mix(c.base.rgb, -0.6)}" stroke-width="${width}" stroke-opacity=".4"/><path d="${m}" stroke="${mix(t.rgb, 0.05)}" stroke-width="${width}"/>`;
31
37
  }
32
38
  if (d[0] === "patch") {
33
39
  const [, y0, y1, deg0, deg1, name] = d;
@@ -57,3 +63,72 @@ export function LATHE_EXTRAS(c, d) {
57
63
  };
58
64
  return `<path d="M${lerp(y0, u0)}L${lerp(y0, u1)}L${lerp(y1, u1)}L${lerp(y1, u0)}z" fill="${t.fill}" stroke="${t.edge}" stroke-width=".5"/>`;
59
65
  }
66
+ const rotate = (v, lz, lx, yaw) => {
67
+ const [x0, y0, z0] = v;
68
+ const x1 = x0 * Math.cos(lz) - y0 * Math.sin(lz);
69
+ const y1 = x0 * Math.sin(lz) + y0 * Math.cos(lz);
70
+ const y2 = y1 * Math.cos(lx) - z0 * Math.sin(lx);
71
+ const z2 = y1 * Math.sin(lx) + z0 * Math.cos(lx);
72
+ return [x1 * Math.cos(yaw) + z2 * Math.sin(yaw), y2, -x1 * Math.sin(yaw) + z2 * Math.cos(yaw)];
73
+ };
74
+ /** The great circles of a basketball's seams, by their normals: the equator, the meridian, and the two curved seams that run from pole to pole at 60 degrees either side of it. */
75
+ const SEAMS = [[0, 1, 0], [1, 0, 0], [Math.cos(Math.PI / 3), 0, -Math.sin(Math.PI / 3)], [Math.cos(Math.PI / 3), 0, Math.sin(Math.PI / 3)]];
76
+ /** Draws the seams of a basketball as grooves (each the front half of a great circle: one elliptical arc), and the specks of a grained finish. */
77
+ export function LATHE_BALL(c) {
78
+ const { ball, cx, cy } = c;
79
+ const r = ball.r;
80
+ const lz = ((ball.lean?.[0] ?? 0) * Math.PI) / 180;
81
+ const lx = ((ball.lean?.[1] ?? 0) * Math.PI) / 180;
82
+ const yaw = (c.yaw * Math.PI) / 180;
83
+ const e = (c.elevation * Math.PI) / 180;
84
+ const se = Math.sin(e);
85
+ const ce = Math.cos(e);
86
+ const view = [0, se, ce];
87
+ const screen = (v) => [v[0], -v[1] * ce + v[2] * se];
88
+ const t = c.tone(ball.tone);
89
+ let m = "";
90
+ for (const n0 of SEAMS) {
91
+ const n = rotate(n0, lz, lx, yaw);
92
+ const facing = n[0] * view[0] + n[1] * view[1] + n[2] * view[2];
93
+ const limb = [n[1] * view[2] - n[2] * view[1], n[2] * view[0] - n[0] * view[2], n[0] * view[1] - n[1] * view[0]];
94
+ const size = Math.hypot(...limb);
95
+ if (size < 0.02)
96
+ continue;
97
+ const a = screen([limb[0] / size, limb[1] / size, limb[2] / size]);
98
+ const near = [view[0] - facing * n[0], view[1] - facing * n[1], view[2] - facing * n[2]];
99
+ const k = Math.hypot(...near);
100
+ const mid = screen([near[0] / k, near[1] / k, near[2] / k]);
101
+ const sweep = a[0] * mid[1] - a[1] * mid[0] > 0 ? 1 : 0;
102
+ const angle = (Math.atan2(a[1], a[0]) * 180) / Math.PI;
103
+ m += `M${pt(cx + r * a[0], cy + r * a[1])}A${f(r)} ${f(Math.max(0.05, r * Math.abs(facing)))} ${f(angle)} 0 ${sweep} ${pt(cx - r * a[0], cy - r * a[1])}`;
104
+ }
105
+ let out = "";
106
+ if (m !== "") {
107
+ if (c.line)
108
+ out += `<path d="${m}" stroke-width="1.4"/>`;
109
+ else if (c.flat)
110
+ out += `<path d="${m}" stroke="${mix(t.rgb, -0.4)}" stroke-width="1"/>`;
111
+ else {
112
+ const inlay = ball.seam === undefined ? undefined : c.tone(ball.seam);
113
+ out += `<path d="${m}" stroke="${mix(t.rgb, -0.5)}" stroke-width="${inlay === undefined ? 0.75 : 0.85}"/>`;
114
+ out += inlay === undefined ? `<path d="${m}" transform="translate(.35 .4)" stroke="${mix(t.rgb, 0.55)}" stroke-width=".3" stroke-opacity=".8"/>` : `<path d="${m}" stroke="${mix(inlay.rgb, 0.1)}" stroke-width=".42"/>`;
115
+ }
116
+ }
117
+ const grain = ball.grain ?? 0;
118
+ if (grain > 0 && !c.line) {
119
+ let dots = "";
120
+ for (let i = 0; i < grain; i++) {
121
+ const y = 1 - (2 * (i + 0.5)) / grain;
122
+ const ring = Math.sqrt(1 - y * y);
123
+ const theta = i * 2.39996;
124
+ const p = rotate([ring * Math.cos(theta), y, ring * Math.sin(theta)], lz, lx, yaw);
125
+ if (p[0] * view[0] + p[1] * view[1] + p[2] * view[2] < 0.12)
126
+ continue;
127
+ const [sx, sy] = screen(p);
128
+ dots += `M${pt(cx + r * sx, cy + r * sy)}h0`;
129
+ }
130
+ if (dots !== "")
131
+ out += `<path d="${dots}" stroke="${mix(t.rgb, -0.5)}" stroke-width=".36" stroke-opacity=".6"/>`;
132
+ }
133
+ return out;
134
+ }
@@ -0,0 +1,30 @@
1
+ import { type Rgb } from "./emblem.core.ts";
2
+ import type { FramedCtx } from "./emblem.lathe.ts";
3
+ /** A point or a direction: x to the right, y down, z toward the eye at rest, in the lathe's units about the axis x = 0 (drawn at x = 32). */
4
+ export type V3 = readonly [x: number, y: number, z: number];
5
+ /** The turn and the eye of a drawing: rotate a point about the axis, place it on the page, and say how near the eye it is and whether a direction faces the eye. */
6
+ export declare function viewOf(c: Pick<FramedCtx, "yaw" | "s">): {
7
+ rot: (v: V3) => V3;
8
+ at: (r: V3) => readonly [number, number];
9
+ depth: (r: V3) => number;
10
+ facing: (n: V3) => number;
11
+ };
12
+ export declare const sub: (a: V3, b: V3) => V3;
13
+ export declare const cross: (a: V3, b: V3) => V3;
14
+ export declare const unit: (a: V3) => V3;
15
+ /** The normal of a facet from its first, middle and last points (a quadrilateral's diagonals). */
16
+ export declare const normalOf: (p: readonly V3[]) => V3;
17
+ /**
18
+ * How light polished metal is where a facet faces some way: -1 (black) to 1 (white), for a rotated, unit normal and the elevation of the eye `s`. Two sided, because a thin sheet is seen from either side. A polished
19
+ * surface shows what it mirrors, so the shade is read off a small room: a bright sky above, a dark band at the horizon, a mid floor and a bright window to the upper left. A facet that turns from the sky to the floor
20
+ * as a band twists runs from white through black to grey, the way chrome does.
21
+ */
22
+ export declare function chrome(n: V3, s: number): number;
23
+ /** A colour for a shade from `chrome`, kept to a few steps so that neighbouring facets that differ by a hair are one colour. */
24
+ export declare const shadeOf: (rgb: Rgb, v: number, steps?: number) => string;
25
+ /** A smooth curve through a table of `[x, y]` points that never overshoots them (monotone cubic), as a function of x; outside the table it holds the end values. */
26
+ export declare function curve(table: readonly (readonly [number, number])[]): (x: number) => number;
27
+ /** A straight-line table: the value at x between the two points either side of it. */
28
+ export declare function lin(table: readonly (readonly [number, number])[]): (x: number) => number;
29
+ /** The points as a path: a closed polygon, or an open line. */
30
+ export declare const line: (pts: readonly (readonly [number, number])[], closed: boolean, digits?: 1 | 2) => string;
@@ -0,0 +1,98 @@
1
+ // The small 3D that a body of revolution's own `framed` drawer use (`FramedCtx` in emblem.lathe.ts): a point turned by the yaw and placed the way the body's surface is placed, a polished-metal shade for a facet from the way it
2
+ // faces, a smooth curve through a table of measurements, and the paths of polygons. Nothing here knows any one emblem; a body with no framed parts does not take this module. Imports only the core.
3
+ import { mix } from "./emblem.core.js";
4
+ const RAD = Math.PI / 180;
5
+ const one = (n) => String(Math.round(n * 10) / 10 + 0);
6
+ const two = (n) => String(Math.round(n * 100) / 100 + 0);
7
+ /** The turn and the eye of a drawing: rotate a point about the axis, place it on the page, and say how near the eye it is and whether a direction faces the eye. */
8
+ export function viewOf(c) {
9
+ const cy = Math.cos(c.yaw * RAD);
10
+ const sy = Math.sin(c.yaw * RAD);
11
+ const ce = Math.sqrt(1 - c.s * c.s);
12
+ return {
13
+ rot: (v) => [v[0] * cy + v[2] * sy, v[1], v[2] * cy - v[0] * sy],
14
+ at: (r) => [32 + r[0], r[1] + c.s * r[2]],
15
+ depth: (r) => r[2] * ce - r[1] * c.s,
16
+ facing: (n) => n[2] * ce - n[1] * c.s,
17
+ };
18
+ }
19
+ export const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
20
+ export const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
21
+ export const unit = (a) => {
22
+ const m = Math.hypot(a[0], a[1], a[2]) || 1;
23
+ return [a[0] / m, a[1] / m, a[2] / m];
24
+ };
25
+ /** The normal of a facet from its first, middle and last points (a quadrilateral's diagonals). */
26
+ export const normalOf = (p) => unit(cross(sub(p[2], p[0]), sub(p[p.length - 1], p[1])));
27
+ const smooth = (x, a, b) => {
28
+ const t = Math.max(0, Math.min(1, (x - a) / (b - a)));
29
+ return t * t * (3 - 2 * t);
30
+ };
31
+ /**
32
+ * How light polished metal is where a facet faces some way: -1 (black) to 1 (white), for a rotated, unit normal and the elevation of the eye `s`. Two sided, because a thin sheet is seen from either side. A polished
33
+ * surface shows what it mirrors, so the shade is read off a small room: a bright sky above, a dark band at the horizon, a mid floor and a bright window to the upper left. A facet that turns from the sky to the floor
34
+ * as a band twists runs from white through black to grey, the way chrome does.
35
+ */
36
+ export function chrome(n, s) {
37
+ const ce = Math.sqrt(1 - s * s);
38
+ const eye = [0, -s, ce];
39
+ let m = n;
40
+ let d = m[0] * eye[0] + m[1] * eye[1] + m[2] * eye[2];
41
+ if (d < 0) {
42
+ m = [-m[0], -m[1], -m[2]];
43
+ d = -d;
44
+ }
45
+ const r = [2 * d * m[0] - eye[0], 2 * d * m[1] - eye[1], 2 * d * m[2] - eye[2]];
46
+ const up = -r[1];
47
+ const window = r[2] > 0 ? Math.exp(-(((r[0] + 0.5) / 0.34) ** 2 + ((up - 0.4) / 0.3) ** 2)) : 0;
48
+ const v = -0.12 + 0.95 * smooth(up, -0.12, 0.4) - 0.6 * Math.exp(-(((up + 0.02) / 0.14) ** 2)) + 0.75 * window;
49
+ return Math.max(-0.72, Math.min(0.9, v));
50
+ }
51
+ /** A colour for a shade from `chrome`, kept to a few steps so that neighbouring facets that differ by a hair are one colour. */
52
+ export const shadeOf = (rgb, v, steps = 24) => mix(rgb, Math.round(v * steps) / steps);
53
+ /** A smooth curve through a table of `[x, y]` points that never overshoots them (monotone cubic), as a function of x; outside the table it holds the end values. */
54
+ export function curve(table) {
55
+ const n = table.length;
56
+ const dx = table.slice(1).map((p, i) => p[0] - table[i][0]);
57
+ const dy = table.slice(1).map((p, i) => p[1] - table[i][1]);
58
+ const m = dy.map((d, i) => d / dx[i]);
59
+ const t = table.map((_, i) => (i === 0 ? m[0] : i === n - 1 ? m[n - 2] : m[i - 1] * m[i] <= 0 ? 0 : (2 * m[i - 1] * m[i]) / (m[i - 1] + m[i])));
60
+ return (x) => {
61
+ const first = table[0];
62
+ const last = table[n - 1];
63
+ if (x <= first[0])
64
+ return first[1];
65
+ if (x >= last[0])
66
+ return last[1];
67
+ let i = 0;
68
+ while (x > table[i + 1][0])
69
+ i++;
70
+ const a = table[i];
71
+ const w = dx[i];
72
+ const u = (x - a[0]) / w;
73
+ const u2 = u * u;
74
+ const u3 = u2 * u;
75
+ return (2 * u3 - 3 * u2 + 1) * a[1] + (u3 - 2 * u2 + u) * w * t[i] + (-2 * u3 + 3 * u2) * table[i + 1][1] + (u3 - u2) * w * t[i + 1];
76
+ };
77
+ }
78
+ /** A straight-line table: the value at x between the two points either side of it. */
79
+ export function lin(table) {
80
+ return (x) => {
81
+ const first = table[0];
82
+ if (x <= first[0])
83
+ return first[1];
84
+ for (let i = 1; i < table.length; i++) {
85
+ const b = table[i];
86
+ if (x <= b[0]) {
87
+ const a = table[i - 1];
88
+ return a[1] + ((b[1] - a[1]) * (x - a[0])) / (b[0] - a[0]);
89
+ }
90
+ }
91
+ return table[table.length - 1][1];
92
+ };
93
+ }
94
+ /** The points as a path: a closed polygon, or an open line. */
95
+ export const line = (pts, closed, digits = 1) => {
96
+ const w = digits === 1 ? one : two;
97
+ return `M${pts.map((p) => `${w(p[0])} ${w(p[1])}`).join("L")}${closed ? "z" : ""}`;
98
+ };
@@ -3,6 +3,7 @@
3
3
  // as a solid edge, widest when it is edge on. A ribbon is a centre line with a width that turns about it, drawn as a strip of shaded quadrilaterals. A model that wants them sets `sculptor: LATHE_SCULPT`;
4
4
  // one that does not never imports this file, so its bundle carries none of the code. Plain SVG.
5
5
  import { mix } from "./emblem.core.js";
6
+ import { engravingInk, sideText } from "./emblem.engrave.js";
6
7
  const CX = 32;
7
8
  const RAD = Math.PI / 180;
8
9
  const f = (n) => String(Math.round(n * 10) / 10 + 0);
@@ -55,8 +56,9 @@ function polys(d) {
55
56
  }
56
57
  else if (cmd === "Q") {
57
58
  const [x1, y1, x2, y2] = [num(), num(), num(), num()];
58
- for (let k = 1; k <= 4; k++) {
59
- const u = k / 4;
59
+ const qn = Math.max(2, Math.min(5, Math.ceil(Math.hypot(x2 - x, y2 - y) / 3.4)));
60
+ for (let k = 1; k <= qn; k++) {
61
+ const u = k / qn;
60
62
  cur.push([(1 - u) ** 2 * x + 2 * (1 - u) * u * x1 + u * u * x2, (1 - u) ** 2 * y + 2 * (1 - u) * u * y1 + u * u * y2]);
61
63
  }
62
64
  x = x2;
@@ -64,8 +66,9 @@ function polys(d) {
64
66
  }
65
67
  else if (cmd === "C") {
66
68
  const [x1, y1, x2, y2, x3, y3] = [num(), num(), num(), num(), num(), num()];
67
- for (let k = 1; k <= 5; k++) {
68
- const u = k / 5;
69
+ const cn = Math.max(2, Math.min(6, Math.ceil(Math.hypot(x3 - x, y3 - y) / 3.3)));
70
+ for (let k = 1; k <= cn; k++) {
71
+ const u = k / cn;
69
72
  const a = (1 - u) ** 3;
70
73
  const b = 3 * (1 - u) ** 2 * u;
71
74
  const c = 3 * (1 - u) * u * u;
@@ -84,8 +87,8 @@ function polys(d) {
84
87
  CACHE.set(d, out);
85
88
  return out;
86
89
  }
87
- function drawCut(c, stand) {
88
- const { cut, ax, az, dy } = stand;
90
+ function drawCut(c, stands) {
91
+ const { cut, ax, az, dy } = stands[0];
89
92
  const { yaw, s, tone, line, flat } = c;
90
93
  const th = yaw * RAD;
91
94
  const phi = ((cut.rot ?? 0) + yaw) * RAD;
@@ -93,24 +96,30 @@ function drawCut(c, stand) {
93
96
  const sinP = Math.sin(phi);
94
97
  const A = ax * Math.cos(th) + az * Math.sin(th);
95
98
  const B = az * Math.cos(th) - ax * Math.sin(th);
96
- if (cut.decal === true && cosP <= 0.03)
99
+ if ((cut.decal === true && cosP <= 0.03) || (line && cut.fine === true))
97
100
  return undefined;
98
101
  const t = cut.t ?? 1;
99
102
  const sign = cosP >= 0 ? 1 : -1;
100
- const at = (px, py, d) => `${f(CX + A + (px - CX) * cosP + d * sinP)} ${f(py + dy + s * (B - (px - CX) * sinP + d * cosP))}`;
101
103
  const path = (d, depth) => {
102
104
  let out = "";
103
- for (const poly of polys(d))
104
- out += `M${poly.map(([px, py]) => at(px, py, depth)).join("L")}Z`;
105
+ for (const st of stands) {
106
+ const sa = st.ax * Math.cos(th) + st.az * Math.sin(th);
107
+ const sb = st.az * Math.cos(th) - st.ax * Math.sin(th);
108
+ for (const poly of polys(d))
109
+ out += `M${poly.map(([px, py]) => `${f(CX + sa + (px - CX) * cosP + depth * sinP)} ${f(py + st.dy + s * (sb - (px - CX) * sinP + depth * cosP))}`).join("L")}Z`;
110
+ }
105
111
  return out;
106
112
  };
107
113
  const near = (sign * t) / 2;
108
- const first = cut.layers[0];
114
+ const first = cut.layers[0] ?? ["main", ""];
109
115
  const base = tone(first[0]);
110
116
  let svg = "";
111
117
  const reach = t * Math.hypot(sinP, s * cosP);
112
118
  const glass = first[2] === "gl";
113
- if (line) {
119
+ if (first[1] === "") {
120
+ // A text only: nothing is drawn but the engraving.
121
+ }
122
+ else if (line) {
114
123
  if (reach > 1.3 && Math.abs(sinP) > 0.3)
115
124
  svg += `<path d="${path(first[1], -near)}" stroke-opacity=".6"/>`;
116
125
  }
@@ -131,7 +140,7 @@ function drawCut(c, stand) {
131
140
  for (const [name, d, mode] of cut.layers) {
132
141
  const q = tone(name);
133
142
  if (line) {
134
- if (mode === undefined || mode === "gl" || mode === "rf")
143
+ if (mode === undefined || mode === "gl" || mode === "rf" || mode === "fl")
135
144
  svg += `<path d="${path(d, near)}"/>`;
136
145
  else if (mode === "ln")
137
146
  svg += `<path d="${path(d, near)}" stroke-width="1.1"/>`;
@@ -139,7 +148,7 @@ function drawCut(c, stand) {
139
148
  }
140
149
  const p = path(d, near);
141
150
  if (mode === undefined)
142
- svg += `<path d="${p}" fill="${q.fill}" stroke="${q.edge}"/>`;
151
+ svg += `<path d="${p}" fill="${q.fill}" stroke="${q.edge}" stroke-width=".3"/>`;
143
152
  else if (mode === "dk")
144
153
  svg += `<path d="${p}" fill="${mix(q.rgb, -0.55)}" fill-opacity=".5" stroke="none"/>`;
145
154
  else if (mode === "lt")
@@ -148,13 +157,21 @@ function drawCut(c, stand) {
148
157
  svg += `<path d="${p}" stroke="${q.edge}" stroke-width=".5"/>`;
149
158
  else if (mode === "gl")
150
159
  svg += `<path d="${p}" fill="${mix(q.rgb, 0.35)}" fill-opacity=".3" stroke="${q.edge}" stroke-width=".45"/>`;
160
+ else if (mode === "fl")
161
+ svg += `<path d="${p}" fill="${mix(q.rgb, 0.08)}" stroke="${q.edge}" stroke-width=".3"/>`;
151
162
  else if (mode === "rf")
152
- svg += `<path d="${p}" fill="${q.fill}" fill-opacity=".8" stroke="${q.edge}" stroke-width=".4"/>`;
163
+ svg += `<path d="${p}" fill="${q.fill}" fill-opacity=".8" stroke="${q.edge}" stroke-width=".3"/>`;
153
164
  else
154
165
  svg += `<path d="${p}" fill="#fff" fill-opacity=".6" stroke="none"/>`;
155
166
  }
156
- if (sign < 0 && !line)
167
+ if (sign < 0 && !line && first[1] !== "")
157
168
  svg += `<path d="${path(first[1], near)}" fill="#000" fill-opacity=".22" stroke="none"/>`;
169
+ if (cut.plate !== undefined && c.words !== "") {
170
+ const [x0, x1, y0, y1] = cut.plate;
171
+ const y = (y0 + y1) / 2;
172
+ const end = (px) => [CX + A + (px - CX) * cosP + near * sinP, y + dy + s * (B - (px - CX) * sinP + near * cosP)];
173
+ svg += sideText(c.words, x1 - x0, y1 - y0, engravingInk(line ? "line" : flat ? "color" : "metal", base.rgb), end(x0), end(x1), cosP);
174
+ }
158
175
  return { z: B, svg };
159
176
  }
160
177
  /** The convex hull of some points (Andrew's monotone chain), counter-clockwise. */
@@ -225,7 +242,7 @@ function drawRibbon(c, r) {
225
242
  const nz = -ty * wx;
226
243
  const nl = Math.hypot(nx, ny, nz) || 1;
227
244
  const lambert = Math.abs((nx * L[0] + ny * L[1] + nz * L[2]) / (nl * len));
228
- const bucket = Math.round(Math.max(-0.5, Math.min(0.6, (lambert - 0.42) * 1.5)) * 8) / 8;
245
+ const bucket = Math.round(Math.max(-0.45, Math.min(0.5, (lambert - 0.45) * 1.1)) * 10) / 10;
229
246
  const colour = mix(base.rgb, bucket);
230
247
  quads.push({ z: (p.pz + q.pz) / 2, d: `M${pt(p.px - p.wx, p.py, p.pz - p.wz)}L${pt(p.px + p.wx, p.py, p.pz + p.wz)}L${pt(q.px + q.wx, q.py, q.pz + q.wz)}L${pt(q.px - q.wx, q.py, q.pz - q.wz)}Z`, colour });
231
248
  }
@@ -245,23 +262,59 @@ function drawRibbon(c, r) {
245
262
  svg += `<path d="${run}" fill="${colour}" stroke="${colour}" stroke-width=".3"/>`;
246
263
  return { z: turned.reduce((m, p) => m + p.pz, 0) / turned.length, svg };
247
264
  }
265
+ function drawBlock(c, b) {
266
+ const { yaw, s, tone, line } = c;
267
+ const th = yaw * RAD;
268
+ const cos = Math.cos(th);
269
+ const sin = Math.sin(th);
270
+ const t = tone(b.tone);
271
+ const up = tone(b.top ?? b.tone);
272
+ const [x0, x1] = b.x;
273
+ const [y0, y1] = b.y;
274
+ const [z0, z1] = b.z;
275
+ const at = (x, y, z) => `${f(CX + x * cos + z * sin)} ${f(y + s * (z * cos - x * sin))}`;
276
+ let svg = "";
277
+ // The four sides, each by the way it faces: the ones that look toward the eye, lit from the left.
278
+ const faces = [
279
+ [0, 1, [x0, z1], [x1, z1]],
280
+ [1, 0, [x1, z1], [x1, z0]],
281
+ [0, -1, [x1, z0], [x0, z0]],
282
+ [-1, 0, [x0, z0], [x0, z1]],
283
+ ];
284
+ for (const [nx, nz, a, e] of faces) {
285
+ const facing = nz * cos - nx * sin;
286
+ if (facing <= 0.01)
287
+ continue;
288
+ const lit = -(nx * cos + nz * sin);
289
+ svg += `<path d="M${at(a[0], y0, a[1])}L${at(e[0], y0, e[1])}L${at(e[0], y1, e[1])}L${at(a[0], y1, a[1])}Z" fill="${line ? "none" : mix(t.rgb, 0.05 + 0.27 * lit)}" stroke="${line ? "currentColor" : t.edge}"/>`;
290
+ }
291
+ if (s > 0.02)
292
+ svg += `<path d="M${at(x0, y0, z0)}L${at(x1, y0, z0)}L${at(x1, y0, z1)}L${at(x0, y0, z1)}Z" fill="${line ? "none" : mix(up.rgb, 0.3)}" stroke="${line ? "currentColor" : b.rim === undefined ? up.edge : mix(tone(b.rim).rgb, 0.2)}"${b.rim === undefined || line ? "" : ' stroke-width=".8"'}/>`;
293
+ return svg;
294
+ }
248
295
  /** Draws the parts of a model: the ones that are `under` the bodies, and the rest, each group from far to near. */
249
296
  export function LATHE_SCULPT(c, parts) {
250
297
  const under = [];
251
298
  const over = [];
299
+ const boxes = [];
252
300
  for (const part of parts) {
301
+ if (part.kind === "box") {
302
+ boxes.push(drawBlock(c, part));
303
+ continue;
304
+ }
253
305
  if (part.kind === "ribbon") {
254
306
  (part.under === true ? under : over).push(drawRibbon(c, part));
255
307
  continue;
256
308
  }
257
309
  const [x, z] = part.at ?? [0, 0];
258
310
  const stands = [{ cut: part, ax: x, az: z, dy: part.y ?? 0 }, ...(part.copies ?? []).map(([cx, cz, cy]) => ({ cut: part, ax: x + cx, az: z + cz, dy: (part.y ?? 0) + (cy ?? 0) }))];
259
- for (const stand of stands) {
260
- const made = drawCut(c, stand);
311
+ // The copies of a decal are one path (they lie on one face and cannot hide each other); those of a cut-out are drawn one by one, far to near.
312
+ for (const group of part.decal === true ? [stands] : stands.map((st) => [st])) {
313
+ const made = drawCut(c, group);
261
314
  if (made !== undefined)
262
315
  (part.under === true ? under : over).push(made);
263
316
  }
264
317
  }
265
318
  const join = (list) => list.sort((a, b) => a.z - b.z).map((p) => p.svg).join("");
266
- return { under: join(under), over: join(over) };
319
+ return { under: join(under), over: boxes.join("") + join(over) };
267
320
  }
@@ -0,0 +1,27 @@
1
+ import type { Pt } from "./emblem.lathe.ts";
2
+ /** The tones the NBA's postseason trophies share: the ball's silver (and the frosted silver of the earlier conference balls), the black of the rings and feet, and the pale gold of the etched net. */
3
+ export declare const NBA_TONES: {
4
+ readonly silver: "#d3d8df";
5
+ readonly frost: "#e6e9ec";
6
+ readonly dark: "#16171b";
7
+ readonly etch: "#f0cf66";
8
+ };
9
+ /** The trumpet from its flange down to its foot, in the 64 unit box: the flange's rim is at 24.5 and the foot ends at 61.4. */
10
+ export declare const TRUMPET: readonly Pt[];
11
+ /** The ball's ring of the Western trophy: four bands that cross under the ball and rise round its lower half (the real ones are two rings). */
12
+ export declare const WEST_BAND: readonly [readonly [0, 38.2], readonly [4.2, 37.6], readonly [8.4, 36.2], readonly [12, 33.6], readonly [14.6, 30.4], readonly [15.5, 28.8]];
13
+ /** The Western base from the gold dome down: a black ring, a thin band, a flared black drum, a tall flared skirt and a black foot. In the earlier pair the thin band is black and the skirt polished silver. */
14
+ export declare const westBase: (earlier: boolean) => readonly Pt[];
15
+ /** The Eastern base from its plate down: a plate, three black tiers, a tall drum and a black foot. In the earlier pair the plate and the drum are polished silver. */
16
+ export declare const eastBase: (earlier: boolean) => readonly Pt[];
17
+ /** One of the three posts of the Eastern trophy and its stem and dish, as solids (the posts stand at 0, 120 and 240 degrees). */
18
+ export declare const eastTop: () => ({
19
+ at: readonly [12.2, number];
20
+ p: readonly [readonly [33.4, 0.9, 1], readonly [40.6, 0.9, 1]];
21
+ } | {
22
+ p: readonly [readonly [37.6, 1.2, 1], readonly [40.7, 1.4, 1]];
23
+ open?: undefined;
24
+ } | {
25
+ open: boolean;
26
+ p: readonly [readonly [33.2, 10, 1], readonly [34, 9.7], readonly [36, 7.2], readonly [37.6, 3.2], readonly [38.2, 1.3, 1]];
27
+ })[];
@@ -0,0 +1,34 @@
1
+ /** The tones the NBA's postseason trophies share: the ball's silver (and the frosted silver of the earlier conference balls), the black of the rings and feet, and the pale gold of the etched net. */
2
+ export const NBA_TONES = { silver: "#d3d8df", frost: "#e6e9ec", dark: "#16171b", etch: "#f0cf66" };
3
+ /** The trumpet from its flange down to its foot, in the 64 unit box: the flange's rim is at 24.5 and the foot ends at 61.4. */
4
+ export const TRUMPET = [
5
+ [23, 10.3, 1], [23.2, 11.1], [24.3, 11.25, 1], [24.6, 11.2], [24.6, 9.95],
6
+ [26, 9.7], [28, 9.15], [30, 8.65], [32, 8.15], [34, 7.65], [36, 7.1], [38, 6.55], [40, 6.05], [42, 5.6], [44, 5.15], [46, 4.8], [48, 4.5], [49.4, 4.4], [50.6, 4.55], [51.8, 5.15], [52.8, 6.3], [53.7, 7.6], [54.4, 8.4], [54.8, 8.6, 1],
7
+ [54.8, 8.9, 1, "dark"], [56.4, 8.9, 1, "dark"], [56.4, 9.35, 1], [59.8, 9.4, 1], [59.8, 9.5, 1, "dark"], [61.4, 9.5, 1, "dark"],
8
+ ];
9
+ /** The ball's ring of the Western trophy: four bands that cross under the ball and rise round its lower half (the real ones are two rings). */
10
+ export const WEST_BAND = [[0, 38.2], [4.2, 37.6], [8.4, 36.2], [12, 33.6], [14.6, 30.4], [15.5, 28.8]];
11
+ /** The Western base from the gold dome down: a black ring, a thin band, a flared black drum, a tall flared skirt and a black foot. In the earlier pair the thin band is black and the skirt polished silver. */
12
+ export const westBase = (earlier) => [
13
+ [38, 3, 1], [38.5, 8.6], [39.6, 11], [40, 11.4, 1],
14
+ [40, 13.4, 1, "dark"], [42.2, 13.8, 1, "dark"],
15
+ [42.2, 14.3, 1, earlier ? "dark" : "main"], [44.8, 14.6, 1, earlier ? "dark" : "main"],
16
+ [44.8, 15.3, 1, "dark"], [46, 16], [48, 17.6], [50.4, 20, 1, "dark"],
17
+ [50.4, 20.2, 1, earlier ? "silver" : "main"], [53, 21], [56, 22], [60.6, 23, 1, earlier ? "silver" : "main"],
18
+ [60.6, 23.3, 1, "dark"], [62.4, 23.4, 1, "dark"],
19
+ ];
20
+ /** The Eastern base from its plate down: a plate, three black tiers, a tall drum and a black foot. In the earlier pair the plate and the drum are polished silver. */
21
+ export const eastBase = (earlier) => {
22
+ const light = earlier ? "silver" : "main";
23
+ return [
24
+ [40.6, 10.2, 1, light], [42.2, 10.4, 1, light],
25
+ [42.2, 12.2, 1, "dark"], [43.8, 12.2, 1, "dark"], [43.8, 14, 1, "dark"], [45.4, 14, 1, "dark"], [45.4, 15.8, 1, "dark"], [47, 15.8, 1, "dark"],
26
+ [47, 19.2, 1, light], [58.8, 19.5, 1, light],
27
+ [58.8, 20, 1, "dark"], [60.4, 20.6, 1, "dark"], [62.5, 20.9, 1, "dark"],
28
+ ];
29
+ };
30
+ /** One of the three posts of the Eastern trophy and its stem and dish, as solids (the posts stand at 0, 120 and 240 degrees). */
31
+ export const eastTop = () => {
32
+ const post = (deg) => ({ at: [12.2, deg], p: [[33.4, 0.9, 1], [40.6, 0.9, 1]] });
33
+ return [{ p: [[37.6, 1.2, 1], [40.7, 1.4, 1]] }, { open: true, p: [[33.2, 10, 1], [34, 9.7], [36, 7.2], [37.6, 3.2], [38.2, 1.3, 1]] }, post(0), post(120), post(240)];
34
+ };
@@ -1,2 +1,2 @@
1
- /** The Bill Russell NBA Finals Most Valuable Player Trophy: a gold basketball on a flared net goblet over a stepped round foot. */
1
+ /** The Bill Russell NBA Finals Most Valuable Player Trophy: a gold basketball with silver seams in a net-etched gold trumpet over a stepped round foot. */
2
2
  export declare const TROPHY_BILL_RUSSELL_TROPHY: import("../emblem.core.ts").Emblem<import("../emblem.lathe.ts").LatheModel>;
@@ -1,24 +1,28 @@
1
- // A gold basketball seated straight on the flared rim of a tall goblet: the body is a net-patterned cup that narrows in a long concave curve to a slim waist, then flares into a round foot of two stacked tiers, the upper one
2
- // holding the plain band for the engraving. Drawn for this package from a press photograph of the trophy standing beside the championship trophy (docs/likeness.md): the ball is a third of the height, the rim is wider than the ball,
3
- // the waist is under half the width of the rim and the foot is nearly as wide as the rim; the lettering and the etched strands are left out (the net is a diamond net in the model).
1
+ // A gold basketball with grooved silver seams, seated in the mouth of a tall gold trumpet: the flange is wider than the body under it, the body narrows in a long smooth curve to a slim waist a little under half the
2
+ // body's width, flares into a round foot of two tiers, and is etched with a net of silver diamonds. Drawn for this package from photographs of the 2022 trophy standing beside the championship trophy and the conference finals
3
+ // MVP trophies (docs/likeness.md): the heights and radii are measured on a photograph of the whole row, the ball is a little under a third of the height, and the lettering on the foot's band and the ball's valve are left out
4
+ // (the band takes the engraving option).
4
5
  import { defineLathe } from "../emblem.lathe.js";
5
- import { LATHE_EXTRAS } from "../lathe.extras.js";
6
- /** The Bill Russell NBA Finals Most Valuable Player Trophy: a gold basketball on a flared net goblet over a stepped round foot. */
6
+ import { LATHE_BALL, LATHE_EXTRAS } from "../lathe.extras.js";
7
+ /** The Bill Russell NBA Finals Most Valuable Player Trophy: a gold basketball with silver seams in a net-etched gold trumpet over a stepped round foot. */
7
8
  export const TROPHY_BILL_RUSSELL_TROPHY = /* @__PURE__ */ defineLathe("trophy:bill-russell-trophy", "Bill Russell Trophy", "ビル・ラッセル・トロフィー", "ours", {
8
9
  extras: LATHE_EXTRAS,
10
+ ballExtras: LATHE_BALL,
9
11
  metal: "gold",
10
- turnBox: "-3.7 0 71.4 71.4",
11
- tilt: 8,
12
+ tones: { silver: "#cfd3d8" },
13
+ box: "-0.3 0 64.6 64.6",
14
+ turnBox: "-4.6 0 73.1 73.1",
15
+ tilt: 10,
12
16
  solids: [
13
17
  {
14
18
  p: [
15
- [22.3, 5, 1], [22.3, 11.3, 1], [22.9, 11.3, 1], [23.5, 10.4], [24.4, 9.9],
16
- [26.5, 8.7], [28.7, 7.5], [32, 6.6], [35.1, 6.1], [38.5, 5.5], [41.5, 4.9], [44.2, 4.4], [46.4, 4.15], [49.6, 3.9], [50.9, 4.4], [51.9, 6.4], [52.6, 8.6, 1],
17
- [52.7, 9.1, 1], [56.1, 9.4, 1], [56.2, 10, 1], [58.4, 10.3, 1], [60.2, 10.4], [60.8, 9.9, 1],
19
+ [22.8, 11, 1], [23, 12], [23.9, 12, 1], [24, 10.7, 1],
20
+ [25, 10.25], [27, 9.7], [29.3, 9.3], [31.4, 8.65], [33.5, 7.98], [35.7, 7.45], [37.8, 6.9], [39.9, 6.4], [42, 5.9], [44, 5.4], [45.5, 5.05], [47, 4.8], [50.5, 4.8], [51.6, 5.05], [52.7, 5.6], [53.7, 6.9], [54.8, 8.6], [55.9, 9.7, 1],
21
+ [55.9, 9.9, 1], [58, 10.4], [59.2, 10.7, 1], [59.2, 11, 1], [61.3, 11, 1],
18
22
  ],
19
- decor: [["mesh", 25.2, 48.6, 8, 5], ["ring", 54.2], ["ring", 56.2]],
23
+ decor: [["mesh", 25.5, 46, 8, 5, "silver", 0.3], ["ring", 57.2]],
20
24
  },
21
25
  ],
22
- balls: [{ kind: "basket", cy: 12.7, r: 9.4 }],
23
- plate: { y0: 53.4, y1: 55.9, face: 0 },
26
+ balls: [{ kind: "plain", nba: true, cy: 13.3, r: 10.1, seam: "silver", lean: [-16, 22] }],
27
+ plate: { y0: 59.4, y1: 61 },
24
28
  });
@@ -0,0 +1,2 @@
1
+ /** The Bob Cousy Trophy: the Eastern Conference champions' trophy, a silver ball on three gold posts over a round black and gold base. */
2
+ export declare const TROPHY_BOB_COUSY_TROPHY: import("../emblem.core.ts").Emblem<import("../emblem.lathe.ts").LatheModel>;
@@ -0,0 +1,18 @@
1
+ // A large polished silver basketball with grooved seams on three slim gold posts over a small gold dish, on a round stepped base: a gold plate, three black tiers, a tall plain gold drum and a black foot. Drawn for this package
2
+ // from a photograph of the 2022 conference champions' trophies standing side by side (docs/likeness.md): the posts stand at the corners of a triangle a little wider than the dish, and the lettering on the drum and the ball's
3
+ // valve are left out (the drum is a plain band that takes the engraving option). The Western champions' trophy, the Oscar Robertson Trophy, holds the ball in crossed bands.
4
+ import { defineLathe } from "../emblem.lathe.js";
5
+ import { LATHE_BALL } from "../lathe.extras.js";
6
+ import { NBA_TONES, eastBase, eastTop } from "../nba.parts.js";
7
+ /** The Bob Cousy Trophy: the Eastern Conference champions' trophy, a silver ball on three gold posts over a round black and gold base. */
8
+ export const TROPHY_BOB_COUSY_TROPHY = /* @__PURE__ */ defineLathe("trophy:bob-cousy-trophy", "Bob Cousy Trophy", "ボブ・クージー・トロフィー", "ours", {
9
+ ballExtras: LATHE_BALL,
10
+ metal: "gold",
11
+ tones: NBA_TONES,
12
+ box: "-2.3 0 68.6 68.6",
13
+ turnBox: "-10.5 -1.1 85 85",
14
+ tilt: 13,
15
+ solids: [{ p: eastBase(false) }, ...eastTop()],
16
+ balls: [{ kind: "plain", nba: true, cy: 19.2, r: 17.3, tone: "silver", lean: [20, -16] }],
17
+ plate: { y0: 49, y1: 57 },
18
+ });
@@ -25,5 +25,7 @@ export const TROPHY_FOUNDATION_PLAYER_AWARD = /* @__PURE__ */ defineLathe("troph
25
25
  ["glass", "M19.2 61.8L20.6 60.4H43.4L44.8 61.8Z", "sh"],
26
26
  ],
27
27
  },
28
+ // The text the `engrave` option cuts into the clear middle of the glass; plain glass without it.
29
+ { kind: "cut", at: [0, 4.3], t: 0.1, decal: true, plate: [25.4, 38.6, 24, 38], layers: [] },
28
30
  ],
29
31
  });
@@ -0,0 +1,2 @@
1
+ /** The George Halas Trophy: an open football of polished silver, a laced ribbon in front and two curved bands behind, standing on its tip on a round flared base over a black foot. */
2
+ export declare const TROPHY_GEORGE_HALAS_TROPHY: import("../emblem.core.ts").Emblem<import("../emblem.lathe.ts").LatheModel>;
@@ -0,0 +1,6 @@
1
+ // The George Halas Trophy: the trophy of the National Football Conference champion, drawn as one drawing with the Lamar Hunt Trophy (lathe.conference.ts). Drawn for this package from photographs (docs/likeness.md); the
2
+ // shield and the conference letters on the plate and the engraving on the base are left out.
3
+ import { defineLathe } from "../emblem.lathe.js";
4
+ import { NFL_CONFERENCE } from "../lathe.conference.js";
5
+ /** The George Halas Trophy: an open football of polished silver, a laced ribbon in front and two curved bands behind, standing on its tip on a round flared base over a black foot. */
6
+ export const TROPHY_GEORGE_HALAS_TROPHY = /* @__PURE__ */ defineLathe("trophy:george-halas-trophy", "George Halas Trophy", "ハラス・トロフィー", "ours", NFL_CONFERENCE);
@@ -0,0 +1,2 @@
1
+ /** The Jackie Robinson Rookie of the Year Award: a walnut plaque with a black nameplate, a gold emblem, a bronze relief of a slide at the plate, a plate and a green pin. */
2
+ export declare const TROPHY_JACKIE_ROBINSON_AWARD: import("../emblem.core.ts").Emblem<import("../emblem.shapes.ts").FlatModel>;