@johnmorrisdotca/kyogijo-stadiums 0.12.2 → 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.
- package/CHANGELOG.md +17 -0
- package/README.md +15 -7
- package/dist/multi-entry.d.ts +1 -0
- package/dist/multi-entry.js +1 -0
- package/dist/multi.draw.d.ts +57 -1
- package/dist/multi.draw.js +38 -10
- package/dist/multi.style.js +3 -2
- package/dist/multi.turn.d.ts +32 -0
- package/dist/multi.turn.js +253 -0
- package/dist/strings.d.ts +1 -1
- package/dist/strings.js +2 -2
- package/dist/turn-entry.d.ts +1 -1
- package/dist/turn-entry.js +1 -1
- package/dist/turn.camera.d.ts +6 -4
- package/dist/turn.camera.js +2 -2
- package/dist/turn.dom.d.ts +9 -4
- package/dist/turn.dom.js +13 -5
- package/dist/turn.kit.d.ts +5 -3
- package/dist/turn.kit.js +2 -1
- package/dist/turn.markup.js +2 -2
- package/dist/turn.model.d.ts +16 -2
- package/dist/turn.model.js +33 -26
- package/dist/turn.style.js +4 -0
- package/dist/venue.d.ts +6 -0
- package/dist/venue.js +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.13.0] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Tournament layouts in 3D** (`mountMultiTurn`, `multiTurnMarkup`, `buildMultiTurn` and `multiPresets` of `@johnmorrisdotca/kyogijo-stadiums/multi`). A layout can be turned like any surface: the same four viewpoints (overhead, broadcast, end-on and low corner), turn, tilt, zoom, reset and auto-turn, the same drag, keys and pinch, and the steady camera of 0.12.2 (a fixed scale for the whole turn, ten-decimal matrices, the lens of 8), with the whole floor fitted at every angle. It is the 3D view and not a second renderer: the layout is built as a `TurnModel`, so the camera, the controls and the tests are the ones every surface has (`mountKyogijoTurn` is now `mountTurn` over `buildTurn`, and `mountMultiTurn` is `mountTurn` over `buildMultiTurn`; `TurnModel` has `presets` and `flat` for a model with viewpoints and a flat drawing of its own). Each surface stands as it does on its own page and in its own cell (`uprightsFor`, moved and turned with the cell, and with the hall when the hall turns the layout): the nets and posts of every net sport and a table tennis table as a top 76 cm over the floor on its skirt and legs, with the table's own drawing on it and a soft shadow on the floor, each table's parts drawn in the right order from every side. What only a layout has is stood up from the figures the flat drawing draws: a surround of 75 cm round each area (the ITTF Handbook, 3.2.3.3), in pieces of at most 3 m, that gives way a little only to an eye low on its side (`Plane.fadeAt`); an umpire's chair and a score table as solid blocks; the scoreboard hung over the middle of the floor and facing the eye, and one at each surface where the layout has them. With `stadium` the stands of the hall are raised on the floor's ground (`standsVenueOf`, the same planes `standsVenue` makes for one surface), with a roof on request. The viewpoints are turned to the layout: a net sport's end-on view looks along its surfaces, and a floor taller than it is wide is turned a quarter so that its long way runs across the screen. The heights of the chairs, the tables and the scoreboard are ours (no rule gives them); the table's, the net's and the surround's are the rulebook's.
|
|
15
|
+
- **`halls.html` has the 3D view.** A View choice (`2D` or `3D`, `view=3d` in the address), the viewpoint in the address (`viewpoint=`), the roof of the stadium (`roof=1`) and the 3D view's own controls under the floor; a layout chosen in 3D opens on the viewpoint chosen. Try it: <https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html?preset=tabletennis-4&view=3d>.
|
|
16
|
+
- **Docs and README.** A section "In 3D" on [the layouts page](./docs/multi.md#in-3d) with a picture of a layout in 3D (four table tennis areas, light and dark); the same picture in the README's layouts section.
|
|
17
|
+
- **Japanese**, two phrases for the parts named in a layout's 3D description: `競技エリアのフェンス` (the competition area's fence) and `審判席と記録台` (the umpires' chairs and the score tables).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `multiDrawing(options, raise)` (internal to `multi.draw.ts`) is the drawing of a layout with what was worked out to make it; `drawMulti` is its string. A layout made as the floor of a 3D view leaves out what the 3D view stands up. The drawn picture is unchanged.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- The size-steadiness test (`src/turn.size.test.ts`) and the camera-fit test (`src/turn.camera.test.ts`) now run on every tournament layout, alone and in a stadium, in place of four layouts used as a plain floor; the depth-order and whole-floor tests of 0.12.2 (`src/turn.raised.test.ts`) run on the tables of the table tennis layouts and on the floor and stands of five layouts at every turn and tip, desk and phone; `src/multi.turn.test.ts` holds each surface to its cell (also in a hall that turns the layout), the surrounds, chairs, scoreboards and stands, the viewpoints and the markup; `e2e/halls.demo.mjs` plays the layouts page in 3D in a real browser (the controls, the address, a full turn's size, the legs under each table's top, the stadium and its roof, Japanese, a phone).
|
|
26
|
+
|
|
10
27
|
## [0.12.2] - 2026-10-20
|
|
11
28
|
|
|
12
29
|
### Added
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">Kyogijo Stadiums <sub>競技場</sub></h1>
|
|
2
2
|
|
|
3
3
|
<p align="center"><strong>Sports surfaces as SVG, drawn to scale, with the score on them.</strong><br>
|
|
4
|
-
The NFL, CFL and IFAF football fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field of every MLB ballpark, current and past, and every NPB home park, the rink and court of 167 NHL, NBA, WNBA and PWHL arenas, and the surfaces of curling, athletics, speed, short track and figure skating, swimming, water polo, diving, artistic swimming, 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, handball, judo, karate, kendo, sumo, kyūdō and a dojo of tatami, thirty-nine sports in all, each drawn from its rulebook or its published distances, with each team's name and logo on its side and the score in the middle, a lineup and the situation of the game on them, with stands and a crowd, a pageant of flags and a 3D view. Wide or tall, light or dark, a
|
|
4
|
+
The NFL, CFL and IFAF football fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field of every MLB ballpark, current and past, and every NPB home park, the rink and court of 167 NHL, NBA, WNBA and PWHL arenas, and the surfaces of curling, athletics, speed, short track and figure skating, swimming, water polo, diving, artistic swimming, 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, handball, judo, karate, kendo, sumo, kyūdō and a dojo of tatami, thirty-nine sports in all, each drawn from its rulebook or its published distances, with each team's name and logo on its side and the score in the middle, a lineup and the situation of the game on them, with stands and a crowd, a pageant of flags and a 3D view. Wide or tall, light or dark, a badge for a list, a helper for React, a custom element and a frame for a page that runs no script. One function, no dependencies.</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
7
|
<a href="https://github.com/johnmorrisdotca/kyogijo-stadiums/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/johnmorrisdotca/kyogijo-stadiums/actions/workflows/ci.yml/badge.svg"></a>
|
|
@@ -72,10 +72,10 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
|
|
|
72
72
|
|
|
73
73
|
## Features
|
|
74
74
|
|
|
75
|
-
- **Thirty-nine surfaces, to scale.** The NFL, CFL and IFAF fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field, the curling sheet, the courts of tennis, volleyball (beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball, the pools, the athletics stadium, the skating rinks, the fields of lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the bud
|
|
75
|
+
- **Thirty-nine surfaces, to scale.** The NFL, CFL and IFAF fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field, the curling sheet, the courts of tennis, volleyball (beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball, the pools, the athletics stadium, the skating rinks, the fields of lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budō. Every figure is cited in [sources](./docs/sources.md). [The surfaces](./docs/surfaces.md).
|
|
76
76
|
- **Each league's rulebook.** `league: "fiba"` draws a court as FIBA marks it: 48 variants, from the WNBA, the NCAA and the IIHF to Little League, PONY, softball, Curling Canada, FIVB's beach volleyball, rugby league and the NLL's box lacrosse, each cited. [League variants](./docs/variants.md).
|
|
77
77
|
- **A realistic basketball floor.** `court: { hardwood: true }` draws maple boards that run the length of the court with staggered ends, grain and tone, stained keys, circle and border, a strip behind each baseline in the team's colour, the baskets and stanchions with soft shadows from above, a coaching box and benches, and a satin sheen, in every league and every renderer, with the detail dropping at small sizes. The clean floor stays the default. [Hardwood](./docs/hardwood.md).
|
|
78
|
-
- **The stands and the crowd.** `crowd: true` draws the seating round any surface that has spectators: a bowl, two sides, a horseshoe, an arena's courtside rows, or a ballpark's grandstands and bleachers
|
|
78
|
+
- **The stands and the crowd.** `crowd: true` draws the seating round any surface that has spectators: a bowl, two sides, a horseshoe, an arena's courtside rows, or a ballpark's grandstands and bleachers, with the people as seeded heads and shoulders in the home colours, the visitors at an end, the empty seats showing (`density`, or `attendance` over `capacity`), and a soccer pitch inside a gridiron stadium's footprint, to scale. Blocks at small sizes; the stands as data. [The crowd](./docs/crowd.md).
|
|
79
79
|
- **Curling, from the rule books.** A sheet from World Curling's and Curling Canada's rules, tall by default, with the board and the hammer in a score, `curlingStones` for the stones of an end, scored by the rules, `show: "folded"` for the whole sheet with both houses enlarged beside it in a square, and a heat map and statistics (free guards, hammer, steals, shots) for many ends. [Curling](./docs/curling.md).
|
|
80
80
|
- **Net, racket and court sports, from their rulebooks.** The ITF's court, the FIVB's indoor and beach courts, World ParaVolley's sitting volleyball court, the BWF's, USA Pickleball's, the ITTF's table, the FIP's padel court, World Squash's courts (with the front wall laid flat beside the floor), the IRF's racquetball court, the ISTAF's sepak takraw court, the FITEQ's teqball court and table, the Roundnet Federation's court and World Netball's court, each in its look (hard court, clay, grass, wood, sand), with a board of sets and games and a ball beside the server. [Net, racket and court sports](./docs/courts.md).
|
|
81
81
|
- **Swimming, water polo, diving and artistic swimming, from World Aquatics' regulations.** The competition pool of 50 or 25 m with its lanes, ropes, blocks, touch panels and flags; the water polo field; the diving well with its springboards and platforms; the artistic swimming area; several pools in a natatorium. The lanes of a race carry names, flags, places, times and splits, with a race replay by lane (`raceTimeline`) and a `splitsChart`. [Pools](./docs/pools.md).
|
|
@@ -85,7 +85,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
|
|
|
85
85
|
- **The budō, from the federations' rules.** The IJF's and the All Japan Judo Federation's judo areas, the WKF's karate areas, the kendo shiai-jo, the sumo dohyō, the kyūdōjō, and **a dojo builder**: a floor of tatami laid as real units (no four corners meet), in jō or metres, with the walls, doors, windows, shrine and kamiza. Five famous halls are on the atlas, each with only what a source gives, and the scores are judo's ippon, waza-ari and shido, karate's points, kendo's points and sumo's kimarite. [The budō](./docs/budo.md).
|
|
86
86
|
- **Soccer, to the Laws.** A pitch from Law 1 of the IFAB Laws of the Game of any size, with presets (international, UEFA, MLS, NCAA, the FA's 9-, 7- and 5-a-side, FIFA's futsal), half pitches, five mowing patterns, four goal styles, penalties and extra time in a score. [Soccer](./docs/soccer.md).
|
|
87
87
|
- **Every ballpark.** The 30 current MLB parks and 44 that MLB clubs have left (the Polo Grounds, Ebbets Field, the Astrodome and more), and the 12 home parks of Nippon Professional Baseball with eight other NPB grounds, each with its fence drawn from the distances published for it; a past park whose fence moved is drawn as it stood in a season. [Ballparks](./docs/ballparks.md).
|
|
88
|
-
- **Soccer grounds, arenas and an atlas.** 64 soccer grounds, each pitch at its published size, and 167 NHL, NBA, WNBA and PWHL arenas (`drawKyogijo({ sport: "nhl", arena: "boston-garden" })`), a rink or
|
|
88
|
+
- **Soccer grounds, arenas and an atlas.** 64 soccer grounds, each pitch at its published size, and 167 NHL, NBA, WNBA and PWHL arenas (`drawKyogijo({ sport: "nhl", arena: "boston-garden" })`), a rink or court at the arena's own size where known, and every park, ground, arena and budō hall (330 places) on a map, by sport, league, country and year, each found by any of its names. [The grounds](./docs/grounds.md), [the arenas](./docs/arenas.md), [the atlas](./docs/atlas.md), [names](./docs/ballparks.md#names).
|
|
89
89
|
- **Walls and quirks.** How high the fence stands, stretch by stretch and season by season (the Green Monster at 37 feet 2 inches, the Crawford Boxes, the Baggie), drawn as a band outside the fence whose width and shade say the height, with a key (`walls`); and Tal's Hill, Wrigley Field's ivy and basket, overhangs, poles, bullpens, the backstop and the dugouts (`quirks`), each from the park's article, none guessed: [ballparks](./docs/ballparks.md).
|
|
90
90
|
- **Special games.** A surface set inside another, to scale: an NHL rink on a ballpark's infield or a football stadium's field (the Winter Classic, the Stadium Series, the Heritage Classic), a college basketball court on the deck of a carrier, a baseball field laid over a London stadium's pitch. 18 games, each with its day, its venue and its sources, and the place of the rink cited where a source gives it and marked as a convention where none does. [Special games](./docs/special-games.md).
|
|
91
91
|
- **Surfaces through the years.** `year: 1975` draws a surface as its league's rules marked it that season, each change cited. [Surfaces through the years](./docs/eras.md).
|
|
@@ -103,7 +103,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
|
|
|
103
103
|
- **Small multiples and backgrounds.** `drawGrid` puts many drawings in one SVG, under a title and a subtitle, over a credit line, with a key they share, in a size for a social card, a square post or a phone; `background` lays a quiet pattern (felt, grass, wood, ice, paper, a grid, dots) or a photograph behind a drawing or a grid. [Layouts and backgrounds](./docs/layouts.md).
|
|
104
104
|
- **Tournament layouts.** Several surfaces laid out together for one large event, to scale, on one floor, as one SVG: thirteen presets from table tennis, pickleball, curling, judo, karate, badminton and tennis, each with its rule and sources, whether it fits a hall, and a real stadium round it. [Tournament layouts](./docs/multi.md).
|
|
105
105
|
- **Replay.** Tracking data played on any surface: the players with their numbers, the ball and its trail, and the passes, shots and goals as they happen, with controls, the keyboard and a live region, steps for a reader who prefers reduced motion, and a pause when the page is hidden. [Replay](./docs/replay.md).
|
|
106
|
-
- **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner), and what the rulebook stands on it at its height: nets, antennae, baskets, rink boards and glass, goals, posts, stumps, a ballpark's fence, the walls of padel and squash, a pool's blocks,
|
|
106
|
+
- **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner), and what the rulebook stands on it at its height: nets, antennae, baskets, rink boards and glass, goals, posts, stumps, a ballpark's fence, the walls of padel and squash, a pool's blocks, hurdles; and the venue round it (raked stands, roofs on request, a dojo's walls and gallery). Plain DOM and CSS over the same SVG. [The 3D view](./docs/turn.md).
|
|
107
107
|
- **Renderers.** The same drawing as plain data (a scene), on a canvas (a browser's or a Node canvas), in a d3 selection, as a mark of an Observable Plot, as GeoJSON and as a PNG in Node, each an entry of its own that imports nothing from outside the package. [Renderers](./docs/renderers.md).
|
|
108
108
|
- **Units and frames.** Each surface in its rulebook's unit and any other, and 48 coordinate frames (ten soccer providers, the NHL API, HockeyTech, NBA stats, NFL tracking, MLBAM, Retrosheet, a curling stone's place from the button, a place from the net in each net sport (from the centre of the court in squash, racquetball, roundnet and netball), the centre of each field sport's surface, the middle of the water of each pool, the middle of the athletics stadium and of each skating rink) to move a provider's points onto a surface. [Units](./docs/units.md), [coordinate frames](./docs/frames.md).
|
|
109
109
|
- **A compact badge and a compact card.** One strip for a list, or the surface with that strip under it.
|
|
@@ -494,7 +494,7 @@ console.log(svg.includes('viewBox="0 0 1200 675"'), svg.includes("Three ballpark
|
|
|
494
494
|
|
|
495
495
|
### Several surfaces on one floor
|
|
496
496
|
|
|
497
|
-
|
|
497
|
+
`drawMulti` lays the package's own surfaces out together for one large event, to scale, on one floor, with the officials' chairs, the boards and empty places for the event's words and marks ([tournament layouts](./docs/multi.md)). Thirteen presets with their rules and sources; `multiFit` says whether a layout fits a hall, `stadium: true` sets it inside real stands, and `mountMultiTurn` turns the floor in 3D.
|
|
498
498
|
|
|
499
499
|
```ts
|
|
500
500
|
import { drawMulti, multiLayout } from "@johnmorrisdotca/kyogijo-stadiums/multi";
|
|
@@ -523,7 +523,15 @@ console.log(layout.needs.length, layout.needs.width);
|
|
|
523
523
|
<br><em>Four table tennis areas under one scoreboard.</em>
|
|
524
524
|
</p>
|
|
525
525
|
|
|
526
|
-
|
|
526
|
+
<p align="center">
|
|
527
|
+
<picture>
|
|
528
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/multi-tabletennis-3d-desk-dark.webp">
|
|
529
|
+
<img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/multi-tabletennis-3d-desk-light.webp" alt="Four table tennis areas in 3D from a low corner: tables on legs, a surround round each, a hung scoreboard, and the 3D view's controls." width="640">
|
|
530
|
+
</picture>
|
|
531
|
+
<br><em>The same four in 3D.</em>
|
|
532
|
+
</p>
|
|
533
|
+
|
|
534
|
+
The [demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html) picks a preset, sets it in a hall, switches the stadium on and off, and turns it in 3D.
|
|
527
535
|
|
|
528
536
|
### The element in a page
|
|
529
537
|
|
package/dist/multi-entry.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { drawMulti, multiSetting, multiTable } from "./multi.draw.ts";
|
|
2
|
+
export { SURROUND_HEIGHT, buildMultiTurn, mountMultiTurn, multiPresets, multiTurnMarkup } from "./multi.turn.ts";
|
|
2
3
|
export { MULTI_MAX, fitSentence, multiFit, multiLayout, needsSentence, specOf, venueOf } from "./multi.ts";
|
|
3
4
|
export { MULTI_PRESETS, MULTI_PRESET_IDS, MULTI_SOURCES, MULTI_VENUES, multiPreset, multiSource, multiVenueById } from "./multi.data.ts";
|
|
4
5
|
export { KYOGIJO_MULTI_STYLE } from "./multi.style.ts";
|
package/dist/multi-entry.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// officials' chairs, a scoreboard hung over the middle, the boards and the empty places for the event's own words and marks; the real configurations of events as presets, each with its rule; the halls whose
|
|
3
3
|
// floors are published, and whether a layout fits one; and a real stadium round the floor at the same scale. `multiTable` is the text alternative.
|
|
4
4
|
export { drawMulti, multiSetting, multiTable } from "./multi.draw.js";
|
|
5
|
+
export { SURROUND_HEIGHT, buildMultiTurn, mountMultiTurn, multiPresets, multiTurnMarkup } from "./multi.turn.js";
|
|
5
6
|
export { MULTI_MAX, fitSentence, multiFit, multiLayout, needsSentence, specOf, venueOf } from "./multi.js";
|
|
6
7
|
export { MULTI_PRESETS, MULTI_PRESET_IDS, MULTI_SOURCES, MULTI_VENUES, multiPreset, multiSource, multiVenueById } from "./multi.data.js";
|
|
7
8
|
export { KYOGIJO_MULTI_STYLE } from "./multi.style.js";
|
package/dist/multi.draw.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Crowd, type CrowdStands } from "./crowd.ts";
|
|
2
|
+
import type { MultiCell, MultiFit, MultiLayout, MultiOptions, MultiVenue } from "./multi.types.ts";
|
|
2
3
|
/**
|
|
3
4
|
* A tournament layout as one SVG string: several of the package's surfaces laid out together for one large event, to scale, on one floor. `preset` is one of `MULTI_PRESETS` (`tabletennis-4`, `pickleball-2x4`,
|
|
4
5
|
* `curling-1x4`, `judo-1x3`, `karate-2x3`, `badminton-1x4`, ...), or `layout` is your own; `rows`, `columns` and `stagger` change the grid. The empty places for the event's words and marks are drawn dashed and hold nothing.
|
|
@@ -13,6 +14,61 @@ import type { MultiFit, MultiLayout, MultiOptions, MultiVenue } from "./multi.ty
|
|
|
13
14
|
* ```
|
|
14
15
|
*/
|
|
15
16
|
export declare function drawMulti(options?: MultiOptions): string;
|
|
17
|
+
/** Where the layout stands in the picture, so that something else can be put in the same frame (the 3D view's planes): the hall's size, where the layout is set in it, and the map from the layout's own frame to the picture's. */
|
|
18
|
+
export interface MultiFrame {
|
|
19
|
+
layout: MultiLayout;
|
|
20
|
+
/** The hall's size and the layout's size as set in it (turned a quarter when the hall asks), in feet; the layout's top left corner is at (`ox`, `oy`). */
|
|
21
|
+
hall: {
|
|
22
|
+
w: number;
|
|
23
|
+
h: number;
|
|
24
|
+
};
|
|
25
|
+
lw: number;
|
|
26
|
+
lh: number;
|
|
27
|
+
ox: number;
|
|
28
|
+
oy: number;
|
|
29
|
+
turned: boolean;
|
|
30
|
+
/** A point of the layout's own frame (feet from the top left of its floor) in the picture's frame. */
|
|
31
|
+
to(x: number, y: number): [number, number];
|
|
32
|
+
}
|
|
33
|
+
/** A layout's drawing, and what was worked out to make it. */
|
|
34
|
+
export interface MultiDrawing {
|
|
35
|
+
svg: string;
|
|
36
|
+
frame: MultiFrame;
|
|
37
|
+
fit: MultiFit | undefined;
|
|
38
|
+
/** The stands of the stadium round the hall, when it is on (in the picture's frame), and the crowd they are of. */
|
|
39
|
+
stands?: {
|
|
40
|
+
stands: CrowdStands;
|
|
41
|
+
crowd: Crowd;
|
|
42
|
+
standsLike: string;
|
|
43
|
+
};
|
|
44
|
+
/** For a drawing made to be raised (`raise`): the surfaces that stand over the floor (a table tennis table), each with its cell and its own drawing, which the floor does not carry. */
|
|
45
|
+
lifted: {
|
|
46
|
+
cell: MultiCell;
|
|
47
|
+
body: string;
|
|
48
|
+
box: {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
w: number;
|
|
52
|
+
h: number;
|
|
53
|
+
};
|
|
54
|
+
lift: number;
|
|
55
|
+
}[];
|
|
56
|
+
/** The width and height of the picture and where it starts, in feet (its viewBox). */
|
|
57
|
+
view: {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
w: number;
|
|
61
|
+
h: number;
|
|
62
|
+
};
|
|
63
|
+
title: string;
|
|
64
|
+
description: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The drawing of a layout, with what it was worked out from. `raise` makes the floor of a 3D view instead of the flat picture: the barriers, the officials' chairs, the scoreboard and the stands are left out (the 3D
|
|
68
|
+
* view stands them up), and a surface that stands over the floor (a table tennis table) is left off it too, with a soft shadow in its place. Everything else is the same string. With `uprights` false the 3D view stands
|
|
69
|
+
* nothing but the stands up, so the barriers and the rest stay on the floor.
|
|
70
|
+
*/
|
|
71
|
+
export declare function multiDrawing(options: MultiOptions, raise: boolean, uprights?: boolean): MultiDrawing;
|
|
16
72
|
/**
|
|
17
73
|
* The same thing as the picture, as a table: each playing surface with its row, column, place and size in metres, and each empty place for the event's words and marks, under a caption that gives
|
|
18
74
|
* what the layout needs. For a reader who cannot see the drawing, and for a page that wants the figures.
|
package/dist/multi.draw.js
CHANGED
|
@@ -14,6 +14,7 @@ import { MULTI_WORDS } from "./multi.words.js";
|
|
|
14
14
|
import { outlineOf } from "./outline.js";
|
|
15
15
|
import { fill } from "./strings.js";
|
|
16
16
|
import { textWidth } from "./text.js";
|
|
17
|
+
import { uprightsFor } from "./uprights.js";
|
|
17
18
|
import { VERSION } from "./version.js";
|
|
18
19
|
const metres = (ft) => ft / FEET_PER_METRE;
|
|
19
20
|
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
@@ -46,6 +47,14 @@ const floorClass = (layout) => (layout.spec.kind === "budo" ? "ky-bud-t2" : "ky-
|
|
|
46
47
|
* ```
|
|
47
48
|
*/
|
|
48
49
|
export function drawMulti(options = {}) {
|
|
50
|
+
return multiDrawing(options, false).svg;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The drawing of a layout, with what it was worked out from. `raise` makes the floor of a 3D view instead of the flat picture: the barriers, the officials' chairs, the scoreboard and the stands are left out (the 3D
|
|
54
|
+
* view stands them up), and a surface that stands over the floor (a table tennis table) is left off it too, with a soft shadow in its place. Everything else is the same string. With `uprights` false the 3D view stands
|
|
55
|
+
* nothing but the stands up, so the barriers and the rest stay on the floor.
|
|
56
|
+
*/
|
|
57
|
+
export function multiDrawing(options, raise, uprights = true) {
|
|
49
58
|
const o = options;
|
|
50
59
|
const language = o.language === "ja" ? "ja" : "en";
|
|
51
60
|
const words = MULTI_WORDS[language];
|
|
@@ -80,8 +89,13 @@ export function drawMulti(options = {}) {
|
|
|
80
89
|
let style = "";
|
|
81
90
|
let look;
|
|
82
91
|
let cellsMarkup = "";
|
|
92
|
+
const lifted = [];
|
|
83
93
|
const surfaceBox = outlineOf(spec.surface).box;
|
|
84
94
|
const common = { ...spec.surface, margin: 0, language };
|
|
95
|
+
// A surface that stands over the floor (a table tennis table's top, 76 cm up) is raised by the 3D view as a plane of its own.
|
|
96
|
+
const up = raise && uprights;
|
|
97
|
+
const lift = up && spec.kind === "cells" ? uprightsFor(common).lift : 0;
|
|
98
|
+
const raised = lift > 0;
|
|
85
99
|
if (spec.kind === "budo") {
|
|
86
100
|
const drawn = inner(drawKyogijo({ ...common, id: `${gid}-s`, ...(o.style === "none" ? { style: "none" } : {}) }));
|
|
87
101
|
style = drawn.style;
|
|
@@ -97,6 +111,13 @@ export function drawMulti(options = {}) {
|
|
|
97
111
|
style = drawn.style;
|
|
98
112
|
look = drawn.look;
|
|
99
113
|
}
|
|
114
|
+
if (raised) {
|
|
115
|
+
// The floor has its shadow, soft and a little larger; the top is a plane of the 3D view.
|
|
116
|
+
const grow = 0.8;
|
|
117
|
+
cellsMarkup += el("rect", { class: "ky-mu-shadow", x: num(cell.x - grow / 2), y: num(cell.y - grow / 2), width: num(cell.w + grow), height: num(cell.h + grow), rx: num(grow) });
|
|
118
|
+
lifted.push({ cell, body: drawn.body, box: { x: surfaceBox.x, y: surfaceBox.y, w: surfaceBox.w, h: surfaceBox.h }, lift });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
100
121
|
// A surface turned a quarter stands across the layout: the drawing's own top left goes to the cell's top right, and the drawing is turned.
|
|
101
122
|
const place = cell.turn ? `translate(${num(cell.x + cell.w)} ${num(cell.y)}) rotate(90) translate(${num(-surfaceBox.x)} ${num(-surfaceBox.y)})` : `translate(${num(cell.x - surfaceBox.x)} ${num(cell.y - surfaceBox.y)})`;
|
|
102
123
|
cellsMarkup += el("g", { class: "ky-mu-cell", "data-cell": cell.n, transform: place }, drawn.body);
|
|
@@ -106,10 +127,10 @@ export function drawMulti(options = {}) {
|
|
|
106
127
|
let floorMarkup = "";
|
|
107
128
|
floorMarkup += el("rect", { class: floorClass(layout), x: 0, y: 0, width: num(layout.floor.w), height: num(layout.floor.h) });
|
|
108
129
|
let over = "";
|
|
109
|
-
if (spec.parts.barriers === true)
|
|
130
|
+
if (!up && spec.parts.barriers === true)
|
|
110
131
|
for (const c of layout.cells)
|
|
111
132
|
over += el("rect", { class: "ky-mu-barrier", x: num(c.bay.x + 0.5), y: num(c.bay.y + 0.5), width: num(c.bay.w - 1), height: num(c.bay.h - 1), rx: 0.4 });
|
|
112
|
-
if (spec.parts.officials === true) {
|
|
133
|
+
if (!up && spec.parts.officials === true) {
|
|
113
134
|
for (const c of layout.cells) {
|
|
114
135
|
if (c.turn) {
|
|
115
136
|
// Beside the net, on the right of a surface that stands across the layout.
|
|
@@ -123,10 +144,10 @@ export function drawMulti(options = {}) {
|
|
|
123
144
|
over += el("rect", { class: "ky-mu-desk", x: num(cx - 5), y: num(c.y - 3.1), width: 3.6, height: 1.1, rx: 0.2 });
|
|
124
145
|
}
|
|
125
146
|
}
|
|
126
|
-
if (spec.parts.scoreboard === "each")
|
|
147
|
+
if (!up && spec.parts.scoreboard === "each")
|
|
127
148
|
for (const c of layout.cells)
|
|
128
149
|
over += el("rect", { class: "ky-mu-sb", x: num(c.x + c.w / 2 - 2), y: num(c.y + c.h + 1.2), width: 4, height: 1.4, rx: 0.2 });
|
|
129
|
-
const sb = layout.shared;
|
|
150
|
+
const sb = up ? undefined : layout.shared;
|
|
130
151
|
if (sb !== undefined) {
|
|
131
152
|
over += el("rect", { class: "ky-mu-sb", x: num(sb.x), y: num(sb.y), width: num(sb.w), height: num(sb.h), rx: 0.5 });
|
|
132
153
|
// One scoreboard for all: a quarter of it for each of four areas, or one panel for each when they are fewer or more.
|
|
@@ -186,6 +207,7 @@ export function drawMulti(options = {}) {
|
|
|
186
207
|
let reach = { left: 0, right: 0, top: 0, bottom: 0 };
|
|
187
208
|
let defs = "";
|
|
188
209
|
let crowdStyle = "";
|
|
210
|
+
let standing;
|
|
189
211
|
const wide = { x: 0, y: 0, w: hall.w, h: hall.h };
|
|
190
212
|
const boards = layout.slots.filter((s) => s.kind === "board");
|
|
191
213
|
const boardsMargin = withSlots && boards.length > 0 ? 5 : 0;
|
|
@@ -202,13 +224,18 @@ export function drawMulti(options = {}) {
|
|
|
202
224
|
const standsLike = venue?.standsLike ?? spec.sport;
|
|
203
225
|
const crowd = resolveCrowd({ gaps: false, awayEnd: "none", roof: stadium.roof, shape: stadium.shape, density: stadium.density, detail: stadium.detail === "plain" ? "plain" : "fine" });
|
|
204
226
|
const stands = layoutOf({ sport: standsLike, box: wide, crowd, awaySide: "right", arena: venue?.shape === "octagon" });
|
|
205
|
-
const density = stadium.crowd === false ? 0 : densityOf(crowd, stands.seats, undefined);
|
|
206
|
-
const drawn = drawCrowd(stands, { id: `${gid}-cr`, crowd, density, fine: crowd.detail !== "plain", ground: "ky-mu-apron" });
|
|
207
|
-
defs += drawn.defs;
|
|
208
227
|
const wall = venue?.shape === "octagon" ? el("path", { class: "ky-mu-wall", d: octagonD(hall.w / 2, hall.w / 2, hall.w / 2) }) : el("rect", { class: "ky-mu-wall", x: 0, y: 0, width: num(hall.w), height: num(hall.h), rx: 0.5 });
|
|
209
|
-
|
|
228
|
+
if (raise)
|
|
229
|
+
hallMarkup += wall;
|
|
230
|
+
else {
|
|
231
|
+
const density = stadium.crowd === false ? 0 : densityOf(crowd, stands.seats, undefined);
|
|
232
|
+
const drawn = drawCrowd(stands, { id: `${gid}-cr`, crowd, density, fine: crowd.detail !== "plain", ground: "ky-mu-apron" });
|
|
233
|
+
defs += drawn.defs;
|
|
234
|
+
hallMarkup = drawn.markup + hallMarkup + wall;
|
|
235
|
+
crowdStyle = KYOGIJO_CROWD_STYLE;
|
|
236
|
+
}
|
|
210
237
|
reach = stands.reach;
|
|
211
|
-
|
|
238
|
+
standing = { stands, crowd, standsLike };
|
|
212
239
|
}
|
|
213
240
|
else if (venue !== undefined) {
|
|
214
241
|
hallMarkup += el("rect", { class: "ky-mu-hall", x: 0, y: 0, width: num(hall.w), height: num(hall.h) });
|
|
@@ -267,7 +294,8 @@ export function drawMulti(options = {}) {
|
|
|
267
294
|
"data-theme": o.theme === "light" || o.theme === "dark" ? o.theme : undefined,
|
|
268
295
|
"data-version": VERSION,
|
|
269
296
|
}, `<title id="${titleId}">${esc(title)}</title><desc id="${descId}">${esc(description)}</desc>` + css + (defs === "" ? "" : `<defs>${defs}</defs>`) + el("rect", { class: "ky-mu-apron", x: num(view.x), y: num(view.y), width: num(vw), height: num(vh) }) + hallLayer + group + overhang + slotTexts + labelsMarkup);
|
|
270
|
-
|
|
297
|
+
const frame = { layout, hall, lw, lh, ox, oy, turned, to };
|
|
298
|
+
return { svg: root, frame, fit, ...(standing === undefined ? {} : { stands: standing }), lifted, view: { x: view.x, y: view.y, w: vw, h: vh }, title, description };
|
|
271
299
|
}
|
|
272
300
|
/** Where an empty place is, in words: the surface it belongs to, the side or end it is at, and whether it is on the mats. */
|
|
273
301
|
function slotWhere(slot, layout, words) {
|
package/dist/multi.style.js
CHANGED
|
@@ -6,8 +6,8 @@ const FLOORS = {
|
|
|
6
6
|
red: { light: ["#b3362b", "#fdf3f1"], dark: ["#7a241d", "#f3d8d4"] },
|
|
7
7
|
ice: { light: ["#e4eef5", "#1d2b36"], dark: ["#a9bccb", "#10181f"] },
|
|
8
8
|
};
|
|
9
|
-
const COMMON_LIGHT = "--ky-mu-neutral:#bdb9ad;--ky-mu-barrier:#161a1f;--ky-mu-board:#1d2430;--ky-mu-board-ink:#f4f1ea;--ky-mu-chair:#ece7da;--ky-mu-chair-edge:#4a4f56;--ky-mu-sb:#10151b;--ky-mu-sb-ink:#f6f6f6;--ky-mu-wall:#3a4048;--ky-mu-hall:#d9d5c8;--ky-mu-over:#c0392b";
|
|
10
|
-
const COMMON_DARK = "--ky-mu-neutral:#2b2f2c;--ky-mu-barrier:#05070a;--ky-mu-board:#0d1218;--ky-mu-board-ink:#e8e4da;--ky-mu-chair:#b9b4a6;--ky-mu-chair-edge:#050607;--ky-mu-sb:#05080b;--ky-mu-sb-ink:#e9e9e9;--ky-mu-wall:#0a0c0f;--ky-mu-hall:#1b1f1c;--ky-mu-over:#e0614f";
|
|
9
|
+
const COMMON_LIGHT = "--ky-mu-neutral:#bdb9ad;--ky-mu-barrier:#161a1f;--ky-mu-board:#1d2430;--ky-mu-board-ink:#f4f1ea;--ky-mu-chair:#ece7da;--ky-mu-chair-edge:#4a4f56;--ky-mu-sb:#10151b;--ky-mu-sb-ink:#f6f6f6;--ky-mu-wall:#3a4048;--ky-mu-hall:#d9d5c8;--ky-mu-over:#c0392b;--ky-mu-shadow:rgba(0,0,0,.2)";
|
|
10
|
+
const COMMON_DARK = "--ky-mu-neutral:#2b2f2c;--ky-mu-barrier:#05070a;--ky-mu-board:#0d1218;--ky-mu-board-ink:#e8e4da;--ky-mu-chair:#b9b4a6;--ky-mu-chair-edge:#050607;--ky-mu-sb:#05080b;--ky-mu-sb-ink:#e9e9e9;--ky-mu-wall:#0a0c0f;--ky-mu-hall:#1b1f1c;--ky-mu-over:#e0614f;--ky-mu-shadow:rgba(0,0,0,.45)";
|
|
11
11
|
const hallVars = (which) => Object.entries(FLOORS).map(([name, f]) => `--ky-mu-f-${name}:${f[which][0]}`).join(";");
|
|
12
12
|
const looks = (which) => Object.entries(FLOORS).map(([name, f]) => `.kyogijo.ky-multi[data-floor=${name}]{--ky-mu-floor:${f[which][0]};--ky-mu-slot:${f[which][1]}}`).join("");
|
|
13
13
|
/**
|
|
@@ -27,6 +27,7 @@ ${Object.entries(FLOORS).map(([name, f]) => `html[data-theme=dark] .kyogijo.ky-m
|
|
|
27
27
|
.kyogijo .ky-mu-hallfloor{fill:var(--ky-mu-neutral);stroke:var(--ky-mu-wall);stroke-width:.8}
|
|
28
28
|
${Object.keys(FLOORS).map((name) => `.kyogijo .ky-mu-hf-${name}{fill:var(--ky-mu-f-${name})}`).join("\n")}
|
|
29
29
|
.kyogijo .ky-mu-wall{fill:none;stroke:var(--ky-mu-wall);stroke-width:2.2;stroke-linejoin:round}
|
|
30
|
+
.kyogijo .ky-mu-shadow{fill:var(--ky-mu-shadow)}
|
|
30
31
|
.kyogijo .ky-mu-over{fill:none;stroke:var(--ky-mu-over);stroke-width:1.4;stroke-dasharray:5 3}
|
|
31
32
|
.kyogijo .ky-mu-barrier{fill:none;stroke:var(--ky-mu-barrier);stroke-width:1;stroke-opacity:.9}
|
|
32
33
|
.kyogijo .ky-mu-board{fill:var(--ky-mu-board)}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MultiOptions } from "./multi.types.ts";
|
|
2
|
+
import { type PresetTable } from "./turn.camera.ts";
|
|
3
|
+
import { type KyogijoTurn } from "./turn.dom.ts";
|
|
4
|
+
import { type TurnModel, type TurnSettings } from "./turn.model.ts";
|
|
5
|
+
import type { Sport } from "./types.ts";
|
|
6
|
+
/** How high a surround stands: the ITTF's "about 75 cm" (Handbook 3.2.3.3). A layout of another sport with surrounds has the same (ours: no other rulebook is read for it). */
|
|
7
|
+
export declare const SURROUND_HEIGHT: number;
|
|
8
|
+
/**
|
|
9
|
+
* The viewpoints of a layout. Overhead, broadcast and the low corner are a single surface's (`TURN_PRESETS`); the end-on view of a net sport looks along its surfaces' length, where the nets are seen whole, as `NET_PRESETS` does. All are turned a quarter where
|
|
10
|
+
* the floor or the surfaces stand across the picture, so that the long way of the floor runs across the screen. `floor` is the picture's frame (the hall's, turned if the hall asks) and `lengthAlongX` says whether the surfaces' length runs along its x.
|
|
11
|
+
*/
|
|
12
|
+
export declare function multiPresets(sport: Sport, floor: {
|
|
13
|
+
w: number;
|
|
14
|
+
h: number;
|
|
15
|
+
}, lengthAlongX: boolean): PresetTable;
|
|
16
|
+
/**
|
|
17
|
+
* A tournament layout as a 3D model: the floor is the layout's flat drawing, and on it stand each surface as it stands on its own page, the surrounds, the umpires' chairs and score tables, the scoreboards and, with `stadium`, the stands
|
|
18
|
+
* of the hall. A `TurnModel` like any other: `turnMarkupOf` and `mountMultiTurn` make a view of it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildMultiTurn(options: MultiOptions, settings?: TurnSettings): TurnModel;
|
|
21
|
+
/**
|
|
22
|
+
* Mount a tournament layout as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the layout's floor with each surface standing on it.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts no-run
|
|
25
|
+
* import { mountMultiTurn } from "@johnmorrisdotca/kyogijo-stadiums/multi";
|
|
26
|
+
* const view = mountMultiTurn(document.querySelector("#hall")!, { preset: "tabletennis-4", stadium: true }, { preset: "broadcast" });
|
|
27
|
+
* view.set({ yaw: 40 });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function mountMultiTurn(host: HTMLElement, options: MultiOptions, settings?: TurnSettings): KyogijoTurn<MultiOptions>;
|
|
31
|
+
/** A tournament layout as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it. */
|
|
32
|
+
export declare function multiTurnMarkup(options: MultiOptions, settings?: TurnSettings): string;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// A tournament layout in 3D: the 3D view of `turn.ts`, with a layout's several surfaces on its floor. Nothing here is drawn a second way. Each surface stands as it does on its own page (`uprightsFor`: the nets and posts, a table
|
|
2
|
+
// tennis table's legs and skirt), moved to where the layout puts it, and the stands of the stadium round the hall are the 3D view's own (`standsVenueOf`); what only a layout has (the surrounds between the areas, the umpires'
|
|
3
|
+
// chairs and score tables, the scoreboards) is stood up from the same figures the flat drawing draws, and the floor is the flat drawing itself, `multiDrawing` made for raising. The model it makes is a `TurnModel`, so the
|
|
4
|
+
// camera, the controls, the pointer, the auto-turn and the tests of every other 3D view are this one's.
|
|
5
|
+
import { FEET_PER_METRE } from "./geometry.js";
|
|
6
|
+
import { multiDrawing } from "./multi.draw.js";
|
|
7
|
+
import { outlineOf } from "./outline.js";
|
|
8
|
+
import { hash } from "./format.js";
|
|
9
|
+
import { TURN_PRESETS, NET_PRESETS, wrap } from "./turn.camera.js";
|
|
10
|
+
import { flat, n, plane, shy, thinnest, wall } from "./turn.kit.js";
|
|
11
|
+
import { mountTurn } from "./turn.dom.js";
|
|
12
|
+
import { turnMarkupOf } from "./turn.markup.js";
|
|
13
|
+
import { openingView, sceneBox } from "./turn.model.js";
|
|
14
|
+
import { describeUprights, planesOf, uprightsFor } from "./uprights.js";
|
|
15
|
+
import { standsVenueOf } from "./venue.js";
|
|
16
|
+
import { isNetSport } from "./court.js";
|
|
17
|
+
/** How high a surround stands: the ITTF's "about 75 cm" (Handbook 3.2.3.3). A layout of another sport with surrounds has the same (ours: no other rulebook is read for it). */
|
|
18
|
+
export const SURROUND_HEIGHT = 0.75 * FEET_PER_METRE;
|
|
19
|
+
/** The longest piece a surround is cut into, in feet: a long wall is several planes, so that each is drawn in its place among the surfaces and the chairs and not as one slab sorted by its middle. */
|
|
20
|
+
const SURROUND_PIECE = 10;
|
|
21
|
+
/** An umpire's chair's seat and a score table's top, in feet over the floor (ours: the rule gives no height for either). */
|
|
22
|
+
const CHAIR = 1.5;
|
|
23
|
+
const DESK = 2.5;
|
|
24
|
+
/** Where the scoreboards hang (ours): the one over the middle of the floor has its lower edge 13 feet up and its cables run 8 feet above its top; the one at each surface stands 2.6 feet off the floor. */
|
|
25
|
+
const HUNG = { bottom: 13, cables: 8 };
|
|
26
|
+
const STANDING = { legs: 2.6, tall: 1.4 };
|
|
27
|
+
const mapOf = (to) => {
|
|
28
|
+
const [e, f] = to(0, 0);
|
|
29
|
+
const [px, py] = to(1, 0);
|
|
30
|
+
const [qx, qy] = to(0, 1);
|
|
31
|
+
return { a: px - e, b: qx - e, c: py - f, d: qy - f, e, f };
|
|
32
|
+
};
|
|
33
|
+
const at = (m, x, y) => [m.a * x + m.b * y + m.e, m.c * x + m.d * y + m.f];
|
|
34
|
+
const dir = (m, x, y) => [m.a * x + m.b * y, m.c * x + m.d * y];
|
|
35
|
+
/** A plane moved by a map, under a new id. A plane that stands in a frame turned a quarter turns with it: its corner, its two sides and the way it faces out. */
|
|
36
|
+
function place(p, m, id) {
|
|
37
|
+
const [ox, oy] = at(m, p.o[0], p.o[1]);
|
|
38
|
+
const [ux, uy] = dir(m, p.u[0], p.u[1]);
|
|
39
|
+
const [vx, vy] = dir(m, p.v[0], p.v[1]);
|
|
40
|
+
const moved = { ...p, id, o: [ox, oy, p.o[2]], u: [ux, uy, p.u[2]], v: [vx, vy, p.v[2]] };
|
|
41
|
+
if (p.shy !== undefined)
|
|
42
|
+
moved.shy = dir(m, p.shy[0], p.shy[1]);
|
|
43
|
+
if (p.crop === true) {
|
|
44
|
+
// A picture of the floor over a rectangle keeps its sides along x and y.
|
|
45
|
+
const corners = [at(m, p.o[0], p.o[1]), at(m, p.o[0] + p.u[0], p.o[1] + p.u[1]), at(m, p.o[0] + p.v[0], p.o[1] + p.v[1]), at(m, p.o[0] + p.u[0] + p.v[0], p.o[1] + p.u[1] + p.v[1])];
|
|
46
|
+
const [x0, x1] = [Math.min(...corners.map((c) => c[0])), Math.max(...corners.map((c) => c[0]))];
|
|
47
|
+
const [y0, y1] = [Math.min(...corners.map((c) => c[1])), Math.max(...corners.map((c) => c[1]))];
|
|
48
|
+
moved.o = [x0, y0, p.o[2]];
|
|
49
|
+
moved.u = [x1 - x0, 0, 0];
|
|
50
|
+
moved.v = [0, y1 - y0, 0];
|
|
51
|
+
}
|
|
52
|
+
return moved;
|
|
53
|
+
}
|
|
54
|
+
const rect = (x, y, w, h) => ({ x0: x, y0: y, x1: x + w, y1: y + h });
|
|
55
|
+
/** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
|
|
56
|
+
function box(id, kind, r, z1, cls) {
|
|
57
|
+
const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
|
|
58
|
+
const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, z1, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(z1)}" class="${cls}-side"/>`));
|
|
59
|
+
planes.push(flat(`${id}-top`, kind, r.x0, r.y0, r.x1, r.y1, z1, `<rect width="${n(r.x1 - r.x0)}" height="${n(r.y1 - r.y0)}" class="${cls}"/>`));
|
|
60
|
+
return planes;
|
|
61
|
+
}
|
|
62
|
+
/** The surround of one area: four sides over the rectangle, each in pieces of at most `SURROUND_PIECE`, facing out from the area's middle (it gives way to a nearly level eye, as the glass of a rink does, but only then). */
|
|
63
|
+
function surround(id, r) {
|
|
64
|
+
const centre = [(r.x0 + r.x1) / 2, (r.y0 + r.y1) / 2];
|
|
65
|
+
const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
|
|
66
|
+
const planes = [];
|
|
67
|
+
sides.forEach(([xa, ya, xb, yb], side) => {
|
|
68
|
+
const long = Math.hypot(xb - xa, yb - ya);
|
|
69
|
+
const pieces = Math.max(1, Math.ceil(long / SURROUND_PIECE));
|
|
70
|
+
const rim = Math.min(0.3, SURROUND_HEIGHT * 0.12);
|
|
71
|
+
for (let i = 0; i < pieces; i += 1) {
|
|
72
|
+
const [t0, t1] = [i / pieces, (i + 1) / pieces];
|
|
73
|
+
const run = long / pieces;
|
|
74
|
+
const svg = `<rect width="${n(run)}" height="${n(SURROUND_HEIGHT)}" class="u-surround"/><rect width="${n(run)}" height="${n(rim)}" class="u-surround-rim"/>`;
|
|
75
|
+
const p = wall(`${id}-${side + 1}-${i + 1}`, "surround", xa + (xb - xa) * t0, ya + (yb - ya) * t0, xa + (xb - xa) * t1, ya + (yb - ya) * t1, 0, SURROUND_HEIGHT, svg);
|
|
76
|
+
planes.push({ ...shy(p, centre), fadeTo: 0.35, fadeAt: 0.85 });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return planes;
|
|
80
|
+
}
|
|
81
|
+
/** A scoreboard that faces the eye: its panel (and where it is divided into the panels of the areas), hung on cables from over its top, or standing on legs. */
|
|
82
|
+
function board(id, cx, cy, wide, tall, bottom, extra, hung, cuts) {
|
|
83
|
+
const total = tall + extra;
|
|
84
|
+
const top = hung ? extra : 0;
|
|
85
|
+
const line = Math.max(0.12, wide * 0.008);
|
|
86
|
+
const holds = hung
|
|
87
|
+
? `<path d="M${n(wide * 0.12)} 0V${n(extra)}M${n(wide * 0.88)} 0V${n(extra)}" class="u-sbline" fill="none" stroke-width="${n(line)}"/>`
|
|
88
|
+
: `<path d="M${n(wide * 0.14)} ${n(tall)}V${n(total)}M${n(wide * 0.86)} ${n(tall)}V${n(total)}" class="u-sbline" fill="none" stroke-width="${n(line * 1.6)}"/>`;
|
|
89
|
+
const panel = `<rect x="0" y="${n(top)}" width="${n(wide)}" height="${n(tall)}" rx="${n(wide * 0.02)}" class="u-sbframe"/>${cuts === "" ? "" : `<path d="${cuts}" transform="translate(0 ${n(top)})" class="u-sbline" fill="none" stroke-width="${n(line * 0.7)}"/>`}`;
|
|
90
|
+
return { ...plane(id, "screen", [cx - wide / 2, cy, bottom + total], [wide, 0, 0], [0, 0, -total], `${holds}${panel}`), billboard: true };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The viewpoints of a layout. Overhead, broadcast and the low corner are a single surface's (`TURN_PRESETS`); the end-on view of a net sport looks along its surfaces' length, where the nets are seen whole, as `NET_PRESETS` does. All are turned a quarter where
|
|
94
|
+
* the floor or the surfaces stand across the picture, so that the long way of the floor runs across the screen. `floor` is the picture's frame (the hall's, turned if the hall asks) and `lengthAlongX` says whether the surfaces' length runs along its x.
|
|
95
|
+
*/
|
|
96
|
+
export function multiPresets(sport, floor, lengthAlongX) {
|
|
97
|
+
const portrait = floor.h > floor.w ? 90 : 0;
|
|
98
|
+
const nets = isNetSport(sport);
|
|
99
|
+
const end = nets ? NET_PRESETS.end : TURN_PRESETS.end;
|
|
100
|
+
const along = nets ? (lengthAlongX ? 0 : 90) : portrait;
|
|
101
|
+
return {
|
|
102
|
+
overhead: { ...TURN_PRESETS.overhead, yaw: portrait },
|
|
103
|
+
broadcast: { ...TURN_PRESETS.broadcast, yaw: wrap(TURN_PRESETS.broadcast.yaw + portrait) },
|
|
104
|
+
end: { ...end, yaw: wrap(end.yaw + along) },
|
|
105
|
+
corner: { ...TURN_PRESETS.corner, yaw: wrap(TURN_PRESETS.corner.yaw + portrait) },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A tournament layout as a 3D model: the floor is the layout's flat drawing, and on it stand each surface as it stands on its own page, the surrounds, the umpires' chairs and score tables, the scoreboards and, with `stadium`, the stands
|
|
110
|
+
* of the hall. A `TurnModel` like any other: `turnMarkupOf` and `mountMultiTurn` make a view of it.
|
|
111
|
+
*/
|
|
112
|
+
export function buildMultiTurn(options, settings = {}) {
|
|
113
|
+
const wanted = settings.uprights;
|
|
114
|
+
const upOptions = wanted === false || wanted === true || wanted === undefined ? {} : wanted;
|
|
115
|
+
const on = wanted !== false;
|
|
116
|
+
const stadiumOn = settings.venue !== false && options.stadium !== undefined && options.stadium !== false;
|
|
117
|
+
const drawn = multiDrawing(stadiumOn ? options : { ...options, stadium: undefined }, true, on);
|
|
118
|
+
const { frame, title, description } = drawn;
|
|
119
|
+
const layout = frame.layout;
|
|
120
|
+
const spec = layout.spec;
|
|
121
|
+
const language = options.language === "ja" ? "ja" : "en";
|
|
122
|
+
const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.uprights, settings.preset, settings.venue]))}`);
|
|
123
|
+
const only = upOptions.only;
|
|
124
|
+
const keep = (kind) => only === undefined || only.includes(kind);
|
|
125
|
+
const items = [];
|
|
126
|
+
const toPicture = mapOf(frame.to);
|
|
127
|
+
if (on) {
|
|
128
|
+
// ---- Each surface, as it stands on its own page, moved to its cell. ----
|
|
129
|
+
const common = { ...spec.surface, margin: 0, language };
|
|
130
|
+
if (spec.kind === "cells") {
|
|
131
|
+
const own = uprightsFor(common, { ...upOptions, venue: undefined, score: "floor" });
|
|
132
|
+
const surfaceBox = outlineOf(spec.surface).box;
|
|
133
|
+
for (const cell of layout.cells) {
|
|
134
|
+
const into = mapOf((x, y) => frame.to(...(cell.turn ? [cell.x + cell.w - y, cell.y + x] : [cell.x + x, cell.y + y])));
|
|
135
|
+
for (const item of own.items) {
|
|
136
|
+
// The hall's floor under a table is the layout's own, so the planes that stood in for it are left behind.
|
|
137
|
+
const planes = item.planes.filter((p) => p.kind !== "ground").map((p) => place(p, into, `c${cell.n}-${p.id}`));
|
|
138
|
+
if (planes.length > 0)
|
|
139
|
+
items.push({ ...item, id: `c${cell.n}-${item.id}`, at: at(into, item.at[0], item.at[1]), planes });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// A surface that stands over the floor (a table's top) is a plane of its own, with the surface's own drawing on it.
|
|
143
|
+
for (const lifted of drawn.lifted) {
|
|
144
|
+
const local = flat("table-top", "table", 0, 0, surfaceBox.w, surfaceBox.h, lifted.lift, `<g transform="translate(${n(-lifted.box.x)} ${n(-lifted.box.y)})">${lifted.body}</g>`);
|
|
145
|
+
const into = mapOf((x, y) => frame.to(...(lifted.cell.turn ? [lifted.cell.x + lifted.cell.w - y, lifted.cell.y + x] : [lifted.cell.x + x, lifted.cell.y + y])));
|
|
146
|
+
items.push({ id: `c${lifted.cell.n}-table-top`, kind: "table-legs", at: at(into, surfaceBox.w / 2, surfaceBox.h / 2), height: lifted.lift, planes: [place(local, into, `c${lifted.cell.n}-table-top`)] });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// ---- What only a layout has. ----
|
|
150
|
+
const parts = spec.parts;
|
|
151
|
+
const layoutPlanes = (ps) => ps.map((p) => place(p, toPicture, p.id));
|
|
152
|
+
if (parts.barriers === true && upOptions.boards !== false && keep("surrounds")) {
|
|
153
|
+
const planes = layout.cells.flatMap((c) => surround(`c${c.n}-surround`, { x0: c.bay.x + 0.5, y0: c.bay.y + 0.5, x1: c.bay.x + c.bay.w - 0.5, y1: c.bay.y + c.bay.h - 0.5 }));
|
|
154
|
+
items.push({ id: "surrounds", kind: "surrounds", at: at(toPicture, layout.floor.w / 2, layout.floor.h / 2), height: SURROUND_HEIGHT, planes: layoutPlanes(planes) });
|
|
155
|
+
}
|
|
156
|
+
if (parts.officials === true && keep("officials")) {
|
|
157
|
+
const planes = [];
|
|
158
|
+
for (const c of layout.cells)
|
|
159
|
+
planes.push(...officialsOf(c));
|
|
160
|
+
items.push({ id: "officials", kind: "officials", at: at(toPicture, layout.floor.w / 2, layout.floor.h / 2), height: DESK, planes: layoutPlanes(planes) });
|
|
161
|
+
}
|
|
162
|
+
if (keep("scoreboard")) {
|
|
163
|
+
const boards = [];
|
|
164
|
+
if (parts.scoreboard === "each")
|
|
165
|
+
for (const c of layout.cells)
|
|
166
|
+
boards.push(board(`c${c.n}-scoreboard`, c.x + c.w / 2, c.y + c.h + 1.9, 4, STANDING.tall, 0, STANDING.legs, false, ""));
|
|
167
|
+
const sb = layout.shared;
|
|
168
|
+
if (sb !== undefined) {
|
|
169
|
+
const count = layout.cells.length;
|
|
170
|
+
const cols = count <= 4 ? Math.min(2, count) : count;
|
|
171
|
+
const rows = count <= 4 && count > 2 ? 2 : 1;
|
|
172
|
+
let cuts = "";
|
|
173
|
+
for (let i = 1; i < cols; i += 1)
|
|
174
|
+
cuts += `M${n((sb.w * i) / cols)} 0v${n(sb.h)}`;
|
|
175
|
+
for (let j = 1; j < rows; j += 1)
|
|
176
|
+
cuts += `M0 ${n((sb.h * j) / rows)}h${n(sb.w)}`;
|
|
177
|
+
boards.push(board("scoreboard", sb.x + sb.w / 2, sb.y + sb.h / 2, sb.w, sb.h, HUNG.bottom, HUNG.cables, true, cuts));
|
|
178
|
+
}
|
|
179
|
+
if (boards.length > 0)
|
|
180
|
+
items.push({ id: "scoreboard", kind: "scoreboard", at: at(toPicture, layout.floor.w / 2, layout.floor.h / 2), height: parts.scoreboard === "shared" ? HUNG.bottom : STANDING.legs, planes: layoutPlanes(boards) });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ---- The stands of the stadium round the hall, in the picture's own frame. ----
|
|
184
|
+
let defs = "";
|
|
185
|
+
let crowdStyle = "";
|
|
186
|
+
if (drawn.stands !== undefined && keep("stands")) {
|
|
187
|
+
const roof = typeof settings.venue === "object" && settings.venue.roof !== undefined ? settings.venue.roof : typeof options.stadium === "object" && options.stadium.roof === true;
|
|
188
|
+
const venue = standsVenueOf(drawn.stands.stands, drawn.stands.crowd, { sport: drawn.stands.standsLike }, { ...(typeof settings.venue === "object" ? settings.venue : {}), roof, id }, 0);
|
|
189
|
+
items.push(...venue.items.filter((item) => item.kind !== "roof" || roof));
|
|
190
|
+
defs = venue.defs;
|
|
191
|
+
crowdStyle = "--ky-cr-home:#8b94a3;--ky-cr-away:#8b94a3";
|
|
192
|
+
}
|
|
193
|
+
// ---- The model. ----
|
|
194
|
+
const uprights = { items, lift: 0, thin: thinnest(drawn.view.w, drawn.view.h), area: { x0: drawn.view.x, y0: drawn.view.y, x1: drawn.view.x + drawn.view.w, y1: drawn.view.y + drawn.view.h }, whole: true, ...(defs === "" ? {} : { defs }) };
|
|
195
|
+
const planes = planesOf(uprights);
|
|
196
|
+
const scale = 1400 / Math.max(drawn.view.w, drawn.view.h);
|
|
197
|
+
const centre = [drawn.view.x + drawn.view.w / 2, drawn.view.y + drawn.view.h / 2];
|
|
198
|
+
const box3 = sceneBox(planes, uprights.area, 0, centre, scale);
|
|
199
|
+
const look = /^<svg[^>]* data-look="([^"]*)"/.exec(drawn.svg)?.[1];
|
|
200
|
+
const surfaceLike = { sport: spec.sport, language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.style === undefined ? {} : { style: options.style }) };
|
|
201
|
+
const standing = describeUprights(surfaceLike, uprights, language);
|
|
202
|
+
const first = layout.cells[0];
|
|
203
|
+
const presets = multiPresets(spec.sport, { w: frame.lw, h: frame.lh }, first === undefined || first.turn === frame.turned);
|
|
204
|
+
return {
|
|
205
|
+
id,
|
|
206
|
+
language,
|
|
207
|
+
options: surfaceLike,
|
|
208
|
+
title,
|
|
209
|
+
description: standing === "" ? description : `${description}${language === "ja" ? "" : " "}${standing}`,
|
|
210
|
+
standing,
|
|
211
|
+
floor: drawn.svg,
|
|
212
|
+
viewBox: { x: drawn.view.x, y: drawn.view.y, w: drawn.view.w, h: drawn.view.h },
|
|
213
|
+
scale,
|
|
214
|
+
uprights,
|
|
215
|
+
defs,
|
|
216
|
+
crowdStyle,
|
|
217
|
+
venue: drawn.stands !== undefined,
|
|
218
|
+
look: { sport: spec.sport, look: look ?? "plain" },
|
|
219
|
+
planes,
|
|
220
|
+
box: box3,
|
|
221
|
+
centre,
|
|
222
|
+
view: openingView(settings, presets),
|
|
223
|
+
turnable: true,
|
|
224
|
+
presets,
|
|
225
|
+
flat: multiDrawing(options, false).svg,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** The umpire's chair and the score table beside a surface, where the flat drawing has them (feet, in the layout's frame). */
|
|
229
|
+
function officialsOf(c) {
|
|
230
|
+
if (c.turn) {
|
|
231
|
+
// Beside the net, on the right of a surface that stands across the layout.
|
|
232
|
+
const cy = c.y + c.h / 2;
|
|
233
|
+
return [...box(`c${c.n}-chair`, "chair", rect(c.x + c.w + 1.8, cy - 3.2, 1.6, 1.6), CHAIR, "u-chair"), ...box(`c${c.n}-desk`, "desk", rect(c.x + c.w + 1.5, cy - 0.8, 1.1, 3.6), DESK, "u-chair")];
|
|
234
|
+
}
|
|
235
|
+
const cx = c.x + c.w / 2;
|
|
236
|
+
return [...box(`c${c.n}-chair`, "chair", rect(cx + 1.6, c.y - 3.4, 1.6, 1.6), CHAIR, "u-chair"), ...box(`c${c.n}-desk`, "desk", rect(cx - 5, c.y - 3.1, 3.6, 1.1), DESK, "u-chair")];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Mount a tournament layout as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the layout's floor with each surface standing on it.
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts no-run
|
|
242
|
+
* import { mountMultiTurn } from "@johnmorrisdotca/kyogijo-stadiums/multi";
|
|
243
|
+
* const view = mountMultiTurn(document.querySelector("#hall")!, { preset: "tabletennis-4", stadium: true }, { preset: "broadcast" });
|
|
244
|
+
* view.set({ yaw: 40 });
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export function mountMultiTurn(host, options, settings = {}) {
|
|
248
|
+
return mountTurn(host, options, settings, buildMultiTurn);
|
|
249
|
+
}
|
|
250
|
+
/** A tournament layout as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it. */
|
|
251
|
+
export function multiTurnMarkup(options, settings = {}) {
|
|
252
|
+
return turnMarkupOf(buildMultiTurn(options, settings), settings);
|
|
253
|
+
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -993,7 +993,7 @@ export interface KyogijoWords {
|
|
|
993
993
|
item: string;
|
|
994
994
|
itemSag: string;
|
|
995
995
|
/** The names of what stands on the surface, by kind. */
|
|
996
|
-
parts: Record<"scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge", string>;
|
|
996
|
+
parts: Record<"scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials", string>;
|
|
997
997
|
};
|
|
998
998
|
/** The names of the zones that `zoneAt` answers with (rules.ts), by id. */
|
|
999
999
|
rules: Record<RuleId, string>;
|
package/dist/strings.js
CHANGED
|
@@ -432,7 +432,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
432
432
|
announce: "{name} view", announceAngle: "Turned to {yaw} degrees, seen from {pitch} degrees above the floor.",
|
|
433
433
|
flat: "This browser cannot draw the 3D view, so the flat drawing is shown.",
|
|
434
434
|
standing: "Standing on it, in the 3D view: {list}.", item: "{name} {h}", itemSag: "{name} {h} at the posts, {low} at the middle",
|
|
435
|
-
parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness" },
|
|
435
|
+
parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness", surrounds: "surrounds", officials: "umpires' chairs and score tables" },
|
|
436
436
|
},
|
|
437
437
|
rules: {
|
|
438
438
|
"defensive-zone": "Defensive zone",
|
|
@@ -968,7 +968,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
968
968
|
announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
|
|
969
969
|
flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
|
|
970
970
|
standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
|
|
971
|
-
parts: { scoreboard: "スコアボードの下端", net: "ネット", antennae: "アンテナ", goalposts: "ゴールポスト", netposts: "支柱", hoop: "バスケットゴール", goal: "ゴール", boards: "ボードとガラス", stumps: "ウィケットのスタンプ", pylons: "パイロン", flags: "コーナーフラッグ", fence: "外野フェンス", backstop: "バックネット", "foul-poles": "ファウルポール", "table-legs": "天板", walls: "壁", "starting-blocks": "スタート台", "backstroke-flags": "背泳ぎ用フラッグ", "false-start-rope": "フォルススタートロープ", springboards: "飛板", platforms: "固定台", "judges-stand": "ジャッジ台", hurdles: "ハードル", steeplechase: "障害物", "high-jump": "走高跳の支柱とバー", "pole-vault": "棒高跳の支柱とバー", cage: "円盤投・ハンマー投の囲い", padding: "防護マット", "rink-boards": "フェンス", mound: "土の台", azuchi: "安土", targets: "的", stands: "スタンド", "wall-seats": "壁の上の観客席", roof: "屋根", gallery: "見学席", dais: "上座の台", banner: "垂れ幕", shrine: "神棚", "mat-edge": "畳の厚み" },
|
|
971
|
+
parts: { scoreboard: "スコアボードの下端", net: "ネット", antennae: "アンテナ", goalposts: "ゴールポスト", netposts: "支柱", hoop: "バスケットゴール", goal: "ゴール", boards: "ボードとガラス", stumps: "ウィケットのスタンプ", pylons: "パイロン", flags: "コーナーフラッグ", fence: "外野フェンス", backstop: "バックネット", "foul-poles": "ファウルポール", "table-legs": "天板", walls: "壁", "starting-blocks": "スタート台", "backstroke-flags": "背泳ぎ用フラッグ", "false-start-rope": "フォルススタートロープ", springboards: "飛板", platforms: "固定台", "judges-stand": "ジャッジ台", hurdles: "ハードル", steeplechase: "障害物", "high-jump": "走高跳の支柱とバー", "pole-vault": "棒高跳の支柱とバー", cage: "円盤投・ハンマー投の囲い", padding: "防護マット", "rink-boards": "フェンス", mound: "土の台", azuchi: "安土", targets: "的", stands: "スタンド", "wall-seats": "壁の上の観客席", roof: "屋根", gallery: "見学席", dais: "上座の台", banner: "垂れ幕", shrine: "神棚", "mat-edge": "畳の厚み", surrounds: "競技エリアのフェンス", officials: "審判席と記録台" },
|
|
972
972
|
},
|
|
973
973
|
rules: {
|
|
974
974
|
"defensive-zone": "ディフェンディングゾーン",
|
package/dist/turn-entry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { mountKyogijoTurn, turnSupported } from "./turn.dom.ts";
|
|
1
|
+
export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.ts";
|
|
2
2
|
export type { KyogijoTurn } from "./turn.dom.ts";
|
|
3
3
|
export { buildTurn, floorOptions, openingView } from "./turn.model.ts";
|
|
4
4
|
export type { TurnModel, TurnSettings } from "./turn.model.ts";
|
package/dist/turn-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The 3D view: a surface as a floor that turns, with a net, a basket, a goal or a pair of posts standing on it.
|
|
2
|
-
export { mountKyogijoTurn, turnSupported } from "./turn.dom.js";
|
|
2
|
+
export { mountKyogijoTurn, mountTurn, turnSupported } from "./turn.dom.js";
|
|
3
3
|
export { buildTurn, floorOptions, openingView } from "./turn.model.js";
|
|
4
4
|
export { layoutOf, planeMarkup, planeMatrix, turnMarkup, turnMarkupOf } from "./turn.markup.js";
|
|
5
5
|
export { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
package/dist/turn.camera.d.ts
CHANGED
|
@@ -25,8 +25,10 @@ export declare const NET_PRESETS: Readonly<Record<TurnPreset, TurnView>>;
|
|
|
25
25
|
* stay nearly square on to it, and the end view looks along it.
|
|
26
26
|
*/
|
|
27
27
|
export declare const CURLING_PRESETS: Readonly<Record<TurnPreset, TurnView>>;
|
|
28
|
-
/** The four viewpoints
|
|
29
|
-
export
|
|
28
|
+
/** The four viewpoints as a table. */
|
|
29
|
+
export type PresetTable = Readonly<Record<TurnPreset, TurnView>>;
|
|
30
|
+
/** The four viewpoints of a sport, or the table itself when it is given one (a tournament layout has its own). */
|
|
31
|
+
export declare const presetsFor: (sport?: string | PresetTable) => PresetTable;
|
|
30
32
|
/** The view a mount opens on. */
|
|
31
33
|
export declare const TURN_DEFAULT: TurnPreset;
|
|
32
34
|
export declare const PITCH_MAX = 84;
|
|
@@ -39,9 +41,9 @@ export declare function clampView(view: TurnView, fallback?: TurnView): TurnView
|
|
|
39
41
|
/** The angle equal to `to` that is nearest to `from`, so that a change of view turns the short way round. */
|
|
40
42
|
export declare const nearest: (from: number, to: number) => number;
|
|
41
43
|
/** The view a viewpoint name stands for in a sport, or `undefined` for a name that is not one. */
|
|
42
|
-
export declare const presetView: (name: string | undefined, sport?: string) => TurnView | undefined;
|
|
44
|
+
export declare const presetView: (name: string | undefined, sport?: string | PresetTable) => TurnView | undefined;
|
|
43
45
|
/** The name of the viewpoint a view is in a sport, or `undefined` when it is none of them (it has been turned by hand). */
|
|
44
|
-
export declare function presetOf(view: TurnView, sport?: string): TurnPreset | undefined;
|
|
46
|
+
export declare function presetOf(view: TurnView, sport?: string | PresetTable): TurnPreset | undefined;
|
|
45
47
|
/** The steps of the keys and the buttons, in degrees, and the factor of a zoom step. A held Shift takes the larger. */
|
|
46
48
|
export declare const STEP: {
|
|
47
49
|
readonly yaw: 6;
|
package/dist/turn.camera.js
CHANGED
|
@@ -41,8 +41,8 @@ export const CURLING_PRESETS = {
|
|
|
41
41
|
};
|
|
42
42
|
/** The sports whose net runs across the court and whose viewpoints are `NET_PRESETS`. */
|
|
43
43
|
const NET_SPORTS = ["tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "sepaktakraw", "teqball"];
|
|
44
|
-
/** The four viewpoints of a sport. */
|
|
45
|
-
export const presetsFor = (sport) => (sport === "curling" ? CURLING_PRESETS : sport !== undefined && NET_SPORTS.includes(sport) ? NET_PRESETS : sport !== undefined && LONG_SPORTS.includes(sport) ? LONG_PRESETS : TURN_PRESETS);
|
|
44
|
+
/** The four viewpoints of a sport, or the table itself when it is given one (a tournament layout has its own). */
|
|
45
|
+
export const presetsFor = (sport) => typeof sport === "object" ? sport : (sport === "curling" ? CURLING_PRESETS : sport !== undefined && NET_SPORTS.includes(sport) ? NET_PRESETS : sport !== undefined && LONG_SPORTS.includes(sport) ? LONG_PRESETS : TURN_PRESETS);
|
|
46
46
|
/** The view a mount opens on. */
|
|
47
47
|
export const TURN_DEFAULT = "corner";
|
|
48
48
|
export const PITCH_MAX = 84;
|
package/dist/turn.dom.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type TurnPreset, type TurnView } from "./turn.camera.ts";
|
|
2
|
-
import { type TurnSettings } from "./turn.model.ts";
|
|
2
|
+
import { type TurnModel, type TurnSettings } from "./turn.model.ts";
|
|
3
3
|
import type { ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
4
|
-
/** What `mountKyogijoTurn` gives back. */
|
|
5
|
-
export interface KyogijoTurn {
|
|
4
|
+
/** What `mountKyogijoTurn` gives back. `O` is what `update` takes: the options of a drawing, or (for `mountMultiTurn`) of a tournament layout. */
|
|
5
|
+
export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
6
6
|
/** `3d`, or `flat` where the browser cannot draw the 3D view (or there is nothing to turn): the flat drawing is shown and the rest of this does nothing. */
|
|
7
7
|
readonly mode: "3d" | "flat";
|
|
8
8
|
/** The view now: where it is going if it is gliding. */
|
|
@@ -14,7 +14,7 @@ export interface KyogijoTurn {
|
|
|
14
14
|
/** Back to the opening view. */
|
|
15
15
|
reset(): void;
|
|
16
16
|
/** Draw again with new options (a score that moved, a look that changed), keeping the view. */
|
|
17
|
-
update(options:
|
|
17
|
+
update(options: O): void;
|
|
18
18
|
/** Whether the auto-turn is running. */
|
|
19
19
|
readonly playing: boolean;
|
|
20
20
|
/** Start the auto-turn, a slow turn of the whole view. It gives whether it is running (a surface that cannot turn gives `false`). Nothing turns by itself until this, the Play button or Space starts it, unless `autoPlay` asked. */
|
|
@@ -46,3 +46,8 @@ export declare function turnSupported(win?: {
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
export declare function mountKyogijoTurn(host: HTMLElement, options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): KyogijoTurn;
|
|
49
|
+
/**
|
|
50
|
+
* Mount any 3D model (`mountKyogijoTurn` is this with the model of one surface; `mountMultiTurn` with a tournament layout's): `build` makes the model from what `update` is given, and the rest, the controls, the pointer, the keys
|
|
51
|
+
* and the auto-turn, is the same.
|
|
52
|
+
*/
|
|
53
|
+
export declare function mountTurn<O>(host: HTMLElement, options: O, settings: TurnSettings, build: (options: O, settings: TurnSettings) => TurnModel): KyogijoTurn<O>;
|
package/dist/turn.dom.js
CHANGED
|
@@ -30,16 +30,24 @@ const between = (from, to, t) => ({ yaw: from.yaw + (nearest(from.yaw, to.yaw) -
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export function mountKyogijoTurn(host, options, settings = {}) {
|
|
33
|
+
return mountTurn(host, options, settings, buildTurn);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Mount any 3D model (`mountKyogijoTurn` is this with the model of one surface; `mountMultiTurn` with a tournament layout's): `build` makes the model from what `update` is given, and the rest, the controls, the pointer, the keys
|
|
37
|
+
* and the auto-turn, is the same.
|
|
38
|
+
*/
|
|
39
|
+
export function mountTurn(host, options, settings, build) {
|
|
33
40
|
const doc = host.ownerDocument;
|
|
34
41
|
const win = doc.defaultView;
|
|
35
|
-
let model =
|
|
36
|
-
|
|
42
|
+
let model = build(options, settings);
|
|
43
|
+
/** What names the four viewpoints: the model's own table (a tournament layout has one), else the sport's. */
|
|
44
|
+
const sport = () => model.presets ?? model.options.sport;
|
|
37
45
|
const supported = turnSupported(win ?? undefined) && model.turnable;
|
|
38
46
|
let alive = true;
|
|
39
47
|
if (!supported) {
|
|
40
48
|
// The flat drawing, with a sentence that says why it does not turn when the browser is the reason.
|
|
41
49
|
const flat = (next) => {
|
|
42
|
-
host.innerHTML = `<div class="ky-turn" data-mode="flat"><style>${KYOGIJO_TURN_STYLE}</style>${drawKyogijo(floorOptions(next))}${model.turnable ? `<p class="ky-turn-flat">${esc(turnWords(model).flat)}</p>` : ""}</div>`;
|
|
50
|
+
host.innerHTML = `<div class="ky-turn" data-mode="flat"><style>${KYOGIJO_TURN_STYLE}</style>${model.flat ?? drawKyogijo(floorOptions(next))}${model.turnable ? `<p class="ky-turn-flat">${esc(turnWords(model).flat)}</p>` : ""}</div>`;
|
|
43
51
|
};
|
|
44
52
|
flat(options);
|
|
45
53
|
return {
|
|
@@ -53,7 +61,7 @@ export function mountKyogijoTurn(host, options, settings = {}) {
|
|
|
53
61
|
pause: () => undefined,
|
|
54
62
|
togglePlay: () => false,
|
|
55
63
|
update: (next) => {
|
|
56
|
-
model =
|
|
64
|
+
model = build(next, settings);
|
|
57
65
|
flat(next);
|
|
58
66
|
},
|
|
59
67
|
svg: () => (alive ? host.querySelector("svg.kyogijo") : null),
|
|
@@ -430,7 +438,7 @@ export function mountKyogijoTurn(host, options, settings = {}) {
|
|
|
430
438
|
update(next) {
|
|
431
439
|
if (!alive)
|
|
432
440
|
return;
|
|
433
|
-
model =
|
|
441
|
+
model = build(next, { ...settings, preset: undefined, view });
|
|
434
442
|
view = clampView(view);
|
|
435
443
|
opening = openingView(settings, sport());
|
|
436
444
|
const layout = layoutOf(model, shown, stage.clientWidth || 960, stage.clientHeight || 600, settings.lens);
|
package/dist/turn.kit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** A point or a direction: x, y, z in feet. */
|
|
2
2
|
export type V3 = readonly [number, number, number];
|
|
3
3
|
/** What a plane is a part of: it is the class of its element (`ky-up-<kind>`) and what a test finds it by. */
|
|
4
|
-
export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine";
|
|
4
|
+
export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine" | "surround" | "chair" | "desk";
|
|
5
5
|
/** One flat rectangle. Its own x runs along `u` (its whole length) and its own y along `v`; `o` is the corner both start from. `svg` is what is drawn on it, in feet along `u` and `v`. */
|
|
6
6
|
export interface Plane {
|
|
7
7
|
/** A name that says what it is and where: `net`, `post-left`, `goal-right-back`. */
|
|
@@ -28,9 +28,11 @@ export interface Plane {
|
|
|
28
28
|
crop?: boolean;
|
|
29
29
|
/** How much of a `shy` plane is left when it is cut away altogether: 0.08 when missing. A plane that others lie over on the screen (the steps of a gallery) takes less, so that they do not add up to a veil. */
|
|
30
30
|
fadeTo?: number;
|
|
31
|
+
/** How low the eye must be for a `shy` plane to begin to give way: the share (of the plane's side being towards the eye, times the sine of the pitch) at which the fade starts; 0.1 when missing. A low barrier takes a high one, so that it stays whole until the eye is nearly level with it. */
|
|
32
|
+
fadeAt?: number;
|
|
31
33
|
}
|
|
32
34
|
/** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
|
|
33
|
-
export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge";
|
|
35
|
+
export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials";
|
|
34
36
|
/** A thing that stands on the surface: where, how high, and the planes it is made of. */
|
|
35
37
|
export interface Upright {
|
|
36
38
|
id: string;
|
|
@@ -90,4 +92,4 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
|
|
|
90
92
|
* How much of a wall is left (1 whole, down to 0.08) at a view: none is cut away unless the wall is on the eye's side of the surface, and then more the lower the eye is, because a wall seen
|
|
91
93
|
* from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
|
|
92
94
|
*/
|
|
93
|
-
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo">, yaw: number, pitch: number): number;
|
|
95
|
+
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
|
package/dist/turn.kit.js
CHANGED
|
@@ -105,6 +105,7 @@ export function fadeOf(p, yaw, pitch) {
|
|
|
105
105
|
// The horizontal direction from the surface to the eye (the same sum as the depth of a point).
|
|
106
106
|
const toward = p.shy[0] * Math.sin(a) + p.shy[1] * Math.cos(a);
|
|
107
107
|
const hides = toward * Math.sin((pitch * Math.PI) / 180);
|
|
108
|
-
const
|
|
108
|
+
const from = p.fadeAt ?? 0.1;
|
|
109
|
+
const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
|
|
109
110
|
return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
|
|
110
111
|
}
|
package/dist/turn.markup.js
CHANGED
|
@@ -88,7 +88,7 @@ export function worldMarkup(model, layout) {
|
|
|
88
88
|
/** The buttons under the view. */
|
|
89
89
|
export function controlsMarkup(model, view, playing = false) {
|
|
90
90
|
const words = turnWords(model);
|
|
91
|
-
const here = presetOf(view, model.options.sport);
|
|
91
|
+
const here = presetOf(view, model.presets ?? model.options.sport);
|
|
92
92
|
const presets = TURN_PRESET_NAMES.map((name, i) => `<button type="button" data-preset="${name}" aria-pressed="${here === name}" aria-keyshortcuts="${i + 1}">${esc(words[name])}</button>`).join("");
|
|
93
93
|
const tools = [
|
|
94
94
|
["turn-left", "↺", words.turnLeft],
|
|
@@ -115,7 +115,7 @@ export function turnMarkupOf(model, settings = {}) {
|
|
|
115
115
|
const sheet = model.options.style === "none" ? "" : `<style>${KYOGIJO_TURN_STYLE}</style>`;
|
|
116
116
|
const theme = model.options.theme;
|
|
117
117
|
const sport = model.options.sport;
|
|
118
|
-
return (`<div class="ky-turn" id="${esc(model.id)}" data-sport="${esc(String(sport))}" data-mode="3d" data-touch="${settings.touch === "turn" ? "turn" : "scroll"}" data-preset="${presetOf(view, sport) ?? "custom"}"${theme === "light" || theme === "dark" ? ` data-theme="${theme}"` : ""} lang="${model.language}">` +
|
|
118
|
+
return (`<div class="ky-turn" id="${esc(model.id)}" data-sport="${esc(String(sport))}" data-mode="3d" data-touch="${settings.touch === "turn" ? "turn" : "scroll"}" data-preset="${presetOf(view, model.presets ?? sport) ?? "custom"}"${theme === "light" || theme === "dark" ? ` data-theme="${theme}"` : ""} lang="${model.language}">` +
|
|
119
119
|
sheet +
|
|
120
120
|
`<div class="ky-turn-view" tabindex="0" role="group" aria-roledescription="${esc(words.region)}" aria-label="${esc(model.title)}" aria-describedby="${esc(model.id)}-help" aria-keyshortcuts="ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1 2 3 4 Home Space"><div ${worldAttributes(model)}>${worldMarkup(model, layout)}</div></div>` +
|
|
121
121
|
`<p class="ky-turn-help" id="${esc(model.id)}-help">${esc(keys)}</p>` +
|
package/dist/turn.model.d.ts
CHANGED
|
@@ -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";
|
|
@@ -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. */
|
package/dist/turn.model.js
CHANGED
|
@@ -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);
|
|
@@ -67,32 +99,7 @@ export function buildTurn(options, settings = {}) {
|
|
|
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
101
|
const area = uprights.whole === true || uprights.area === undefined ? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh } : uprights.area;
|
|
70
|
-
|
|
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 };
|
|
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 {
|
package/dist/turn.style.js
CHANGED
|
@@ -99,6 +99,10 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
99
99
|
.ky-turn .u-seatback{fill:var(--ky-up-seatb)}
|
|
100
100
|
.ky-turn .u-railline{stroke:var(--ky-up-railw);stroke-width:.3;stroke-linecap:square}
|
|
101
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}
|
|
102
106
|
.ky-turn-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
|
|
103
107
|
.ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
|
|
104
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}
|
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.
|
|
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.
|
|
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.
|
|
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",
|