@johnmorrisdotca/kyogijo-stadiums 0.12.1 → 0.13.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.
@@ -1,5 +1,5 @@
1
1
  import { KYOGIJO_STRINGS } from "./strings.ts";
2
- import { type TurnPreset, type TurnView } from "./turn.camera.ts";
2
+ import { type PresetTable, type TurnPreset, type TurnView } from "./turn.camera.ts";
3
3
  import type { Plane } from "./turn.kit.ts";
4
4
  import { type UprightOptions, type Uprights } from "./uprights.ts";
5
5
  import type { VenueSettings } from "./venue.ts";
@@ -29,7 +29,7 @@ export interface TurnSettings {
29
29
  motion?: "auto" | "smooth" | "none";
30
30
  /** The drawing the floor is, in place of `drawKyogijo`: a chart's `draw`, for instance. It is given the options with the view's own changes (the whole surface, lying wide). */
31
31
  floor?: (options: SurfaceOptions | ScoreOptions) => string;
32
- /** How long a lens the eye has, as a multiple of the view's longer side: 1.7 by default, and the larger it is the flatter the picture. */
32
+ /** How long a lens the eye has, as a multiple of the view's longer side: 8 by default (`LENS`), and the larger it is the flatter the picture; a short lens makes the near end of a turning floor swell. */
33
33
  lens?: number;
34
34
  /** The size the first view is worked out for, in pixels, when the markup is made away from a page. 960 by 600 by default. */
35
35
  size?: {
@@ -93,11 +93,25 @@ export interface TurnModel {
93
93
  view: TurnView;
94
94
  /** False when there is nothing to turn: a badge is a strip, not a surface. */
95
95
  turnable: boolean;
96
+ /** The four viewpoints, when the model has its own (a tournament layout's); the sport's when missing. */
97
+ presets?: PresetTable;
98
+ /** The flat drawing to show where the 3D view cannot be drawn, when it is not `drawKyogijo`'s of `options` (a tournament layout's). */
99
+ flat?: string;
96
100
  }
97
101
  /** The options with what makes the floor of a 3D view: the whole surface, lying wide, at its full size. */
98
102
  export declare function floorOptions(options: SurfaceOptions | ScoreOptions): SurfaceOptions | ScoreOptions;
99
103
  /** The view a set of settings opens on. */
100
- export declare const openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string) => TurnView;
104
+ export declare const openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string | PresetTable) => TurnView;
105
+ /**
106
+ * The scene's extent in pixels from its middle: the area it is fitted to (in feet, in the drawing's frame) widened to take in every plane that is not the ground, and as high as the highest of them (or of `lift`, the height
107
+ * of the floor itself).
108
+ */
109
+ export declare function sceneBox(planes: readonly Plane[], area: {
110
+ x0: number;
111
+ y0: number;
112
+ x1: number;
113
+ y1: number;
114
+ }, lift: number, centre: readonly [number, number], scale: number): Box3;
101
115
  /** Work out a 3D view from the options of a drawing. */
102
116
  export declare function buildTurn(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): TurnModel;
103
117
  /** The words of the 3D view in the model's language. */
@@ -33,6 +33,38 @@ function crowdVars(floor) {
33
33
  }
34
34
  /** The view a set of settings opens on. */
35
35
  export const openingView = (settings, sport) => clampView({ ...(presetView(settings.preset, sport) ?? presetView(TURN_DEFAULT, sport)), ...settings.view });
36
+ /**
37
+ * The scene's extent in pixels from its middle: the area it is fitted to (in feet, in the drawing's frame) widened to take in every plane that is not the ground, and as high as the highest of them (or of `lift`, the height
38
+ * of the floor itself).
39
+ */
40
+ export function sceneBox(planes, area, lift, centre, scale) {
41
+ let [x0, y0, x1, y1] = [area.x0, area.y0, area.x1, area.y1];
42
+ let top = lift;
43
+ for (const p of planes) {
44
+ // A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
45
+ const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
46
+ for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
47
+ const point = [p.o[0] + a * p.u[0] + b * p.v[0], p.o[1] + a * p.u[1] + b * p.v[1], p.o[2] + a * p.u[2] + b * p.v[2]];
48
+ if (reach > 0) {
49
+ const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
50
+ x0 = Math.min(x0, middle[0] - reach);
51
+ x1 = Math.max(x1, middle[0] + reach);
52
+ y0 = Math.min(y0, middle[1] - reach);
53
+ y1 = Math.max(y1, middle[1] + reach);
54
+ top = Math.max(top, point[2]);
55
+ continue;
56
+ }
57
+ if (p.kind !== "ground") {
58
+ x0 = Math.min(x0, point[0]);
59
+ x1 = Math.max(x1, point[0]);
60
+ y0 = Math.min(y0, point[1]);
61
+ y1 = Math.max(y1, point[1]);
62
+ }
63
+ top = Math.max(top, point[2]);
64
+ }
65
+ }
66
+ return { x0: (x0 - centre[0]) * scale, x1: (x1 - centre[0]) * scale, y0: (y0 - centre[1]) * scale, y1: (y1 - centre[1]) * scale, z0: 0, z1: top * scale };
67
+ }
36
68
  /** Work out a 3D view from the options of a drawing. */
37
69
  export function buildTurn(options, settings = {}) {
38
70
  const asked = floorOptions(options);
@@ -66,33 +98,8 @@ export function buildTurn(options, settings = {}) {
66
98
  const centre = [vx + vw / 2, vy + vh / 2];
67
99
  // The scene's extent: the playing surface (the whole floor where it has no uprights), and whatever stands beyond it (a net behind a goal line). The ground
68
100
  // round the surface is not counted, so that the view is fitted to what is played on and the ground runs out of the picture.
69
- const area = uprights.area ?? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh };
70
- let [x0, y0, x1, y1] = [area.x0, area.y0, area.x1, area.y1];
71
- let top = uprights.lift;
72
- for (const p of planes) {
73
- // A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
74
- const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
75
- for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
76
- const point = [p.o[0] + a * p.u[0] + b * p.v[0], p.o[1] + a * p.u[1] + b * p.v[1], p.o[2] + a * p.u[2] + b * p.v[2]];
77
- if (reach > 0) {
78
- const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
79
- x0 = Math.min(x0, middle[0] - reach);
80
- x1 = Math.max(x1, middle[0] + reach);
81
- y0 = Math.min(y0, middle[1] - reach);
82
- y1 = Math.max(y1, middle[1] + reach);
83
- top = Math.max(top, point[2]);
84
- continue;
85
- }
86
- if (p.kind !== "ground") {
87
- x0 = Math.min(x0, point[0]);
88
- x1 = Math.max(x1, point[0]);
89
- y0 = Math.min(y0, point[1]);
90
- y1 = Math.max(y1, point[1]);
91
- }
92
- top = Math.max(top, point[2]);
93
- }
94
- }
95
- const box = { x0: (x0 - centre[0]) * scale, x1: (x1 - centre[0]) * scale, y0: (y0 - centre[1]) * scale, y1: (y1 - centre[1]) * scale, z0: 0, z1: top * scale };
101
+ const area = uprights.whole === true || uprights.area === undefined ? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh } : uprights.area;
102
+ const box = sceneBox(planes, area, uprights.lift, centre, scale);
96
103
  const { title, description } = describe(o);
97
104
  const standing = describeUprights(o, uprights, language);
98
105
  return {
@@ -15,6 +15,7 @@ export const KYOGIJO_TURN_STYLE = `
15
15
  .ky-turn-floor,.ky-up{position:absolute;left:0;top:0;transform-origin:0 0;pointer-events:none}
16
16
  .ky-turn-floor>svg{display:block;width:100%;height:auto}
17
17
  .ky-up>svg{display:block;overflow:visible}
18
+ .ky-up[data-crop]>svg{width:100%;height:100%;overflow:hidden}
18
19
  @media (max-width:560px){.ky-turn-view{--ky-turn-ratio:5/4}.ky-turn-controls button{padding:0 10px;font-size:.85rem}}
19
20
  .ky-turn .u-sbframe{fill:var(--ky-up-sbframe);stroke:var(--ky-up-sbedge);stroke-width:.25}
20
21
  .ky-turn .u-sbline{stroke:var(--ky-up-sbline)}
@@ -29,8 +30,10 @@ export const KYOGIJO_TURN_STYLE = `
29
30
  .ky-turn .u-table{fill:var(--ky-up-table)}
30
31
  .ky-turn .u-foot{fill:var(--ky-up-foot)}
31
32
  .ky-turn .u-ground{fill:var(--ky-up-ground)}
33
+ .ky-turn .u-shadow{fill:#000;fill-opacity:.16}
32
34
  .ky-turn .u-wall{fill:var(--ky-up-wall)}
33
35
  .ky-turn .u-clay{fill:var(--ky-up-clay)}
36
+ .ky-turn .u-clay-side{fill:color-mix(in srgb,var(--ky-dirt,var(--ky-up-clay)) 74%,#000)}
34
37
  .ky-turn .u-cap{fill:var(--ky-up-cap)}
35
38
  .ky-turn .u-board{fill:var(--ky-up-board)}
36
39
  .ky-turn .u-glass{fill:var(--ky-up-glass)}
@@ -96,6 +99,10 @@ export const KYOGIJO_TURN_STYLE = `
96
99
  .ky-turn .u-seatback{fill:var(--ky-up-seatb)}
97
100
  .ky-turn .u-railline{stroke:var(--ky-up-railw);stroke-width:.3;stroke-linecap:square}
98
101
  .ky-turn .u-matedge{fill:var(--ky-up-matedge)}
102
+ .ky-turn .u-surround{fill:var(--ky-mu-barrier,#161a1f);fill-opacity:.92}
103
+ .ky-turn .u-surround-rim{fill:var(--ky-mu-chair,#ece7da);fill-opacity:.55}
104
+ .ky-turn .u-chair{fill:var(--ky-mu-chair,#ece7da);stroke:var(--ky-mu-chair-edge,#4a4f56);stroke-width:.06}
105
+ .ky-turn .u-chair-side{fill:color-mix(in srgb,var(--ky-mu-chair,#ece7da) 62%,#000);stroke:var(--ky-mu-chair-edge,#4a4f56);stroke-width:.04}
99
106
  .ky-turn-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
100
107
  .ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
101
108
  .ky-turn-controls button{font:inherit;font-size:.9rem;font-weight:600;min-width:44px;min-height:44px;padding:0 12px;border-radius:999px;border:1px solid var(--ky-turn-rule);background:var(--ky-turn-chip);color:var(--ky-turn-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
@@ -10,6 +10,8 @@ export interface BudoUprights {
10
10
  x1: number;
11
11
  y1: number;
12
12
  };
13
+ /** The view is fitted to the whole floor (a platform stands in it), not to `area`. */
14
+ whole: boolean;
13
15
  }
14
16
  /** The walls of a dojo, 3 m high (ours). */
15
17
  export declare const DOJO_WALL = 3;
@@ -3,11 +3,15 @@
3
3
  // (a judo platform's height is not in the rules: "it depends on the sightlines"). The plans are in metres in the frame of their own origin, and the drawing's frame is feet from the corner of the plan's
4
4
  // box, so every point goes through `at`. The mound's height is the option's; the azuchi's (1.5 m or 1.8 m) and a dojo wall's (3 m) are ours: no rule gives them.
5
5
  import { budoPlan } from "./budo/plan.js";
6
- import { n, plane, shy, wall } from "./turn.kit.js";
6
+ import { flat, n, plane, shy, wall } from "./turn.kit.js";
7
7
  const K = 1 / 0.3048;
8
8
  /** The walls of a dojo, 3 m high (ours). */
9
9
  export const DOJO_WALL = 3;
10
- /** The planes of the dohyō's clay mound: its four sides, from the floor of the hall up to the clay. */
10
+ /**
11
+ * The planes of the dohyō's clay mound, a solid block: its four sides, from the floor of the hall up to the clay, and its flat top, which is the drawing's own clay with the ring of straw bales on it, lifted to the
12
+ * mound's height (the floor itself lies on the hall's floor, so that the ground round the mound stays low). The sides are one tone darker than the top, so that top and sides are lit the same way; whichever way
13
+ * the view is turned, the sides on the far side are drawn first and the top over them (see `layoutOf`), so the mound never looks hollow.
14
+ */
11
15
  function moundPlanes(plan, at, height) {
12
16
  const L = plan.sumo;
13
17
  if (L === undefined)
@@ -17,8 +21,10 @@ function moundPlanes(plan, at, height) {
17
21
  const planes = [];
18
22
  corners.forEach((a, i) => {
19
23
  const c = corners[(i + 1) % 4];
20
- planes.push(wall(`mound-${i + 1}`, "wall", a[0], a[1], c[0], c[1], 0, height, `<rect width="${n(Math.hypot(c[0] - a[0], c[1] - a[1]))}" height="${n(height)}" class="u-clay"/>`));
24
+ planes.push(wall(`mound-${i + 1}`, "wall", a[0], a[1], c[0], c[1], 0, height, `<rect width="${n(Math.hypot(c[0] - a[0], c[1] - a[1]))}" height="${n(height)}" class="u-clay u-clay-side"/>`));
21
25
  });
26
+ const [a, c] = [corners[0], corners[2]];
27
+ planes.push({ ...flat("mound-top", "platform", a[0], a[1], c[0], c[1], height, ""), crop: true });
22
28
  return planes;
23
29
  }
24
30
  /** The azuchi's face, a slope from the foot of the mound up to its top, and the target of each shooter standing against it. */
@@ -54,10 +60,11 @@ export function budoUprights(o, thin) {
54
60
  const at = (x, y) => [(x - ox) * K, (y - oy) * K];
55
61
  const area = { x0: 0, y0: 0, x1: (plan.box.x1 - ox) * K, y1: (plan.box.y1 - oy) * K };
56
62
  const items = [];
57
- let lift = 0;
63
+ // The floor lies on the hall's floor (`lift` is 0): the mound is a block standing on it, with its own top.
64
+ const lift = 0;
58
65
  if (plan.sumo !== undefined) {
59
- lift = plan.sumo.height * K;
60
- items.push({ id: "mound", kind: "mound", at: at(plan.sumo.centre[0], plan.sumo.centre[1]), height: lift, planes: moundPlanes(plan, at, lift) });
66
+ const height = plan.sumo.height * K;
67
+ items.push({ id: "mound", kind: "mound", at: at(plan.sumo.centre[0], plan.sumo.centre[1]), height, planes: moundPlanes(plan, at, height) });
61
68
  }
62
69
  if (plan.kyudo !== undefined) {
63
70
  const built = azuchiPlanes(plan, at);
@@ -83,5 +90,5 @@ export function budoUprights(o, thin) {
83
90
  items.push({ id: "walls", kind: "walls", at: centre, height: h, planes });
84
91
  }
85
92
  void thin;
86
- return { items, lift, area };
93
+ return { items, lift, area, whole: plan.sumo !== undefined };
87
94
  }
@@ -29,6 +29,8 @@ export interface Uprights {
29
29
  x1: number;
30
30
  y1: number;
31
31
  };
32
+ /** True when the view is fitted to the whole floor drawing, not to `area` (which is then only where the score board goes): a raised platform stands in a floor that must not be cut at any angle. */
33
+ whole?: boolean;
32
34
  /** The patterns the venue's planes are drawn with (the contents of an SVG `defs`), when it has any. */
33
35
  defs?: string;
34
36
  /** How far the venue reaches beyond the surface, in feet, when it has one: the ground a floor needs for it to stand on. */
package/dist/uprights.js CHANGED
@@ -41,6 +41,7 @@ export function uprightsFor(o, settings = {}) {
41
41
  let lift = 0;
42
42
  let thin = 0.2;
43
43
  let area;
44
+ let whole = false;
44
45
  if (sport === "nfl" || sport === "cfl" || sport === "ifaf") {
45
46
  const spec = league?.family === "football" ? fieldOf(league, o.year) : footballIn(sport, o.year);
46
47
  const shape = league?.family === "football" || sport === "ifaf" ? "slingshot" : rulesFor(sport, o.year).rules.postShape;
@@ -112,6 +113,7 @@ export function uprightsFor(o, settings = {}) {
112
113
  thin = thinnest(area.x1, area.y1);
113
114
  items = budoUprights({ ...o, sport }, thin).items;
114
115
  lift = built.lift;
116
+ whole = built.whole;
115
117
  }
116
118
  else if (isFieldSport(sport)) {
117
119
  const plan = fieldPlan({ ...o, sport });
@@ -145,7 +147,7 @@ export function uprightsFor(o, settings = {}) {
145
147
  }
146
148
  if (settings.only !== undefined)
147
149
  items = items.filter((item) => settings.only.includes(item.kind));
148
- return { items, lift, thin, area, ...(defs === "" ? {} : { defs }), ...(reach === 0 ? {} : { reach }) };
150
+ return { items, lift, thin, area, ...(whole ? { whole } : {}), ...(defs === "" ? {} : { defs }), ...(reach === 0 ? {} : { reach }) };
149
151
  }
150
152
  /** A length in feet as a rulebook would print it in `unit`: metres to the centimetre, feet to a tenth. */
151
153
  function said(feet, unit, language) {
@@ -100,7 +100,18 @@ export function netUprights(plan, geo, settings, thin) {
100
100
  items.push({ id: "table", kind: "table-legs", at: [L / 2, W / 2], height: lift, planes: tableBody(0, 0, L, W, lift, thin, false) });
101
101
  // The floor of the hall, under the table.
102
102
  const room = Math.max(L, W) * 5;
103
- items.push({ id: "ground", kind: "table-legs", at: [L / 2, W / 2], height: 0, planes: [flat("ground", "ground", -room, -room, L + room, W + room, 0, `<rect width="${n(L + 2 * room)}" height="${n(W + 2 * room)}" class="u-ground"/>`)] });
103
+ // Under the table lies its shadow on that floor, so that the legs stand on something.
104
+ const grow = Math.max(convertLength(0.25, "m", "ft"), thin * 4);
105
+ items.push({
106
+ id: "ground",
107
+ kind: "table-legs",
108
+ at: [L / 2, W / 2],
109
+ height: 0,
110
+ planes: [
111
+ flat("ground", "ground", -room, -room, L + room, W + room, 0, `<rect width="${n(L + 2 * room)}" height="${n(W + 2 * room)}" class="u-ground"/>`),
112
+ flat("table-shadow", "ground", -grow / 2, -grow / 2, L + grow / 2, W + grow / 2, 0.005, `<rect width="${n(L + grow)}" height="${n(W + grow)}" rx="${n(grow)}" class="u-shadow"/>`),
113
+ ],
114
+ });
104
115
  return { items, lift };
105
116
  }
106
117
  default:
package/dist/venue.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Crowd, type CrowdStands } from "./crowd.ts";
1
2
  import { type Cut } from "./venue.geom.ts";
2
3
  import { type Upright } from "./turn.kit.ts";
3
4
  import type { SurfaceOptions } from "./types.ts";
@@ -29,3 +30,8 @@ export declare const ROOF: {
29
30
  };
30
31
  /** The stands of a surface as planes: `undefined`-free, an empty venue when the options have no crowd (or the crowd is a hall's gallery, which `venue.room.ts` raises). */
31
32
  export declare function standsVenue(o: SurfaceOptions, v: VenueBuild, lift: number): Venue;
33
+ /**
34
+ * The stands the model says (`crowdStands`, or the stands of a tournament layout's hall) as planes: what `standsVenue` makes of a surface's, for stands that were worked out some other way. `o` is the little of the
35
+ * drawing's options it reads: the sport (whether a roof is a lid), the detail (`simple` is the plain crowd) and the ground (whose capacity sets the density).
36
+ */
37
+ export declare function standsVenueOf(stands: CrowdStands, crowd: Crowd, o: Pick<SurfaceOptions, "sport" | "detail" | "ground">, v: VenueBuild, lift: number): Venue;
package/dist/venue.js CHANGED
@@ -33,6 +33,13 @@ export function standsVenue(o, v, lift) {
33
33
  const crowd = resolveCrowd(o.crowd);
34
34
  if (stands === undefined || crowd === undefined || stands.gallery === true)
35
35
  return NO_VENUE;
36
+ return standsVenueOf(stands, crowd, { sport: o.sport, detail: o.detail, ground: o.ground }, v, lift);
37
+ }
38
+ /**
39
+ * The stands the model says (`crowdStands`, or the stands of a tournament layout's hall) as planes: what `standsVenue` makes of a surface's, for stands that were worked out some other way. `o` is the little of the
40
+ * drawing's options it reads: the sport (whether a roof is a lid), the detail (`simple` is the plain crowd) and the ground (whose capacity sets the density).
41
+ */
42
+ export function standsVenueOf(stands, crowd, o, v, lift) {
36
43
  const cut = v.detail ?? "auto";
37
44
  const fine = crowd.detail !== "plain" && o.detail !== "simple";
38
45
  const density = densityOf(crowd, stands.seats, groundById(o.ground)?.capacity);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export declare const VERSION = "0.12.1";
2
+ export declare const VERSION = "0.13.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.12.1";
2
+ export const VERSION = "0.13.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Kyogijo Stadiums 競技場, 39 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",