@johnmorrisdotca/kyogijo-stadiums 0.29.0 → 0.31.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 +30 -0
- package/NOTICE.md +4 -2
- package/README.md +21 -1
- package/dist/crowd.d.ts +20 -2
- package/dist/crowd.park.d.ts +6 -0
- package/dist/crowd.park.js +3 -3
- package/dist/district.d.ts +2 -2
- package/dist/district.js +13 -12
- package/dist/draw.js +25 -10
- package/dist/flythrough-entry.d.ts +1 -0
- package/dist/flythrough-entry.js +1 -0
- package/dist/flythrough.dom.js +52 -6
- package/dist/flythrough.game.d.ts +36 -0
- package/dist/flythrough.game.js +145 -0
- package/dist/flythrough.markup.js +1 -0
- package/dist/flythrough.path.d.ts +6 -4
- package/dist/flythrough.path.js +26 -22
- package/dist/flythrough.style.d.ts +1 -1
- package/dist/flythrough.style.js +3 -0
- package/dist/flythrough.words.d.ts +2 -0
- package/dist/flythrough.words.js +6 -2
- package/dist/stadiums-entry.d.ts +2 -2
- package/dist/stadiums-entry.js +2 -2
- package/dist/stadiums.d.ts +109 -28
- package/dist/stadiums.data.js +25 -0
- package/dist/stadiums.js +159 -30
- package/dist/stadiums.osm.data.js +114 -0
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +2 -2
- package/dist/venue.js +3 -3
- package/dist/venue.real.d.ts +2 -0
- package/dist/venue.real.js +120 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.31.0] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Seattle's stadiums** ([the real stadiums](./docs/stadiums.md), `stadium: "lumen-field"` and `park: "t-mobile-park"` with `crowd: { real: true }`): **Lumen Field** for football (`sport: "nfl"`, 68,740 seats) and soccer (37,722), and **T-Mobile Park** for baseball (`sport: "mlb"`, 47,368), each in the outline of its building as OpenStreetMap maps it, with the roof and a plausible bowl of the seats the sources give. A soccer drawing whose `ground` is `lumen-field` needs no `stadium`; any other sport, a `league` of your own and `crowd: { real: false }` are drawn as before.
|
|
15
|
+
- **Lumen Field.** OpenStreetMap maps both its pitches inside the building, lying north and south, so the field's orientation is mapped and not inferred. One bowl of three levels holds the 68,740 seats; a configuration opens its seats from the lowest bowl up (`bowlSeats`, `StadiumPlan.open`), so the soccer pitch closes the upper bowl and part of the middle one, drawn empty. Its roof is the article's canopy over 70 percent of the seats, drawn from the two roof parts OpenStreetMap maps (heights 36 metres, rise 10): strips of triangles from the low edge over the field's side to the high edge over the back of the stands, the bowl lowered to stand under it, the walls to just over the top row, the field and the ends open (`RealShell.roof: "canopy"`, `canopies`).
|
|
16
|
+
- **T-Mobile Park, a ballpark.** The usual ballpark stands (`parkLayout`) with each band as deep as its seats need (`planPark`): the Main Level's 20,521 seats the lower bowl, the Club, Suite and View levels' 22,075 the upper, the bleachers' 3,666, all from the article's layout, held to within two percent of 47,368. OpenStreetMap maps the infield but not home plate, so the way out to centre field is the one of the four the infield allows where the park's fence and the stands behind it lie inside the building's outline: 49.4 degrees, north-east (the build refuses to choose where the outline does not decide). The roof is the article's retractable one in four sections, 217 to 269 feet over the field, far over the 21-metre walls: closed, a band to a crown with the fabric across it; open (`roofOpen`), the sections rest over the east side, over the railway where the article says they rest (`RealShell.roof: "panels"`, `panels`, `wall`).
|
|
17
|
+
- **The district takes any of them** (`districtMember`, `planDistrict`): a ballpark's key is a venue (`t-mobile-park`, its one configuration `mlb`), its drawing turned about home plate by the heading of the way out to centre field (`stadiumFrameHeading`), and a stadium with several configurations takes the one named (`lumen-field:soccer`). Seattle is two presets of the demo's district page: `["t-mobile-park", "lumen-field"]` as neighbours (459 metres apart, Lumen Field to the north) and, with `mode: "city"`, the same two and `climate-pledge-arena` (3.4 to 3.8 kilometres off) at their real distances, with a grid of kilometres and a name beside each.
|
|
18
|
+
- **Data, kept apart as before.** `pnpm stadiums:fetch` now takes the building, the pitches, the infield and the roof parts a stadium names (`osm` in `scripts/stadiums.data.json`) where OpenStreetMap does not tag them with the Wikidata item, joins the outer ways of a relation into one ring, and puts a stadium added later beside the ones already archived; `pnpm stadiums` makes the data files and refuses a figure the sources do not give (a tier's seats not in the article, pitches that do not lie the same way, an outline that does not choose the way out to centre field). The raw answers are in `data-sources/stadiums/2026-10-20/`.
|
|
19
|
+
- **The demo's stadiums page** has the three stadiums as tabs and each one's configurations, with the facts of its roof; its district page has the Seattle presets. **The docs** have a section for each stadium, a Seattle section in [the district page](./docs/district.md) and items in [the gallery](https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/gallery-stadiums.html).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- The 3D view gave two bands of a ballpark's bleachers (the left and the right) the same plane ids (`bleachers-front-0` for both); a second strip's planes now have an id of their own.
|
|
24
|
+
|
|
25
|
+
## [0.30.1] - 2026-10-20
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **A picture of the fly-through** in the README and in [its page](./docs/flythrough.md): the whole run to Climate Pledge Arena, sped up, as an animated WebP in a light and a dark variant (`turning-flythrough-desk-<light|dark>.webp`, 40 frames, about 300 KB each; `pnpm screenshots:flythrough` makes it from the demo's page by `seek`, without waiting on a clock).
|
|
30
|
+
|
|
31
|
+
## [0.30.0] - 2026-10-20
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **The fly-through ends on a game** (`game`, the sixth stage, after `rink`): a short play comes onto the ice while the rink goes on turning from the seat, with a label on the picture that says `Illustrative play`. It is a [replay](./docs/replay.md) timeline of the package's own (`ILLUSTRATIVE_PLAY`, entry `@johnmorrisdotca/kyogijo-stadiums/flythrough`): 101 frames a tenth of a second apart, five skaters and a goalie a side (shirt numbers only, no name, team or league), a puck, two passes, a shot and a goal, and the dots, numbers, trail and events are drawn by the replay's own layers (`replayLayers` through `renderLayers`). It is made up for the picture and says so on the screen, in the stage's words (English and Japanese) and in the docs; it is no real game's data.
|
|
36
|
+
- **The play lies on the floor of the 3D arena**: a plane that takes the floor's own transform after each paint and stands in the floor's layer of the stack, so the glass, the boards and the stands in front of the ice hide it as they hide the ice. It runs ten seconds, holds one and a half, fades out and starts again (the dots never jump back in view); its moment is worked out from the clock and not from the turn's folded time, so it does not jump when the turn comes round. A reader who prefers reduced motion gets the moment after the shot as a still.
|
|
37
|
+
- **The camera has one more channel** (`game`, how much of the play shows), still one path with no jumps; `playMarkup(moment, { id })`, `playAlpha`, `playMoment` and `PLAY_LOOK` are the rest of the play's API, pure and the same every time. `cameraAt` is a good deal faster (the path's tables are made once).
|
|
38
|
+
- Tests for the play (thirteen objects a frame, all on the ice, no skater faster than 40 feet a second and none that jumps, the puck on the stick of whoever has it, the events in order, the same markup for the same moment, no label of a team or a league, said to be made up in both languages) and, in a real browser, that the play is on the ice from the game stage on only, lies where the floor lies at every frame, and its dots move.
|
|
39
|
+
|
|
10
40
|
## [0.29.0] - 2026-10-20
|
|
11
41
|
|
|
12
42
|
### Added
|
package/NOTICE.md
CHANGED
|
@@ -45,7 +45,7 @@ picture or code of the sources is copied; each source is named here so that the
|
|
|
45
45
|
| `arenas`: each arena's names, years, city, seats, rink sizes, Wikidata item and coordinates | The infobox and the text of the English Wikipedia article of each arena, at the revision cited below (the names with the years the article dates them, the year it opened, closed and was pulled down, the seats for ice hockey and for basketball, a rink's length and width where the article gives them in a sentence of its own); the name in Japanese from the title of its Japanese article or written for the package; the Wikidata item (CC0) that each article is linked to and its coordinates (P625) | Facts. The articles are CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); none of their text, tables or images is used |
|
|
46
46
|
| `venues`: the outline of each NHL arena's building, how its rink lies in it and the building's height (`VENUE_OSM`, `src/venues.osm.data.ts`) | OpenStreetMap, through the Overpass API: the closed way (or the relation of a building drawn in two ways) tagged with the venue's Wikidata item (and the ice, where it is mapped), read on 2026-10-20, with the raw answers kept in `data-sources/venues/2026-10-20/`; the outline is simplified to a metre, and the way the rink lies is worked out from it (`coreOf`, `src/venues.geo.ts`) | ODbL 1.0, (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright): a derived database, kept in its own file and offered under the ODbL; a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
|
|
47
47
|
| `venues`: each venue's seats for ice hockey, number of seating tiers and kind of roof (`VENUE_DATA`, `src/venues.data.ts`) | The item of Wikidata (the capacity P1083 and the sport it applies to P641) and the English Wikipedia article of each arena, at the revision cited below, in `data-sources/venues/2026-10-20/` as read; the facts are numbers and words of our own, with what was read in a few words | Wikidata's data is CC0; the facts read from the articles are facts, with the articles credited here (CC BY-SA 4.0) |
|
|
48
|
-
| `stadiums`: the outline of each stadium's building, the roof OpenStreetMap maps over it (its heights
|
|
48
|
+
| `stadiums`: the outline of each stadium's building, the roof OpenStreetMap maps over it (its heights, the opening in it or its canopies), the pitches or the infield, and how the field lies, worked out from the opening, the pitches or the infield and the outline (`STADIUM_OSM`, `src/stadiums.osm.data.ts`) | OpenStreetMap, through the Overpass API: the closed way tagged with the stadium's Wikidata item and the multipolygon of its roof (BC Place), or the relation of the building, the roof parts and the pitches or the infield named in `scripts/stadiums.data.json` (Lumen Field, T-Mobile Park), read on 2026-10-20, with the raw answers kept in `data-sources/stadiums/2026-10-20/`; the outline is simplified to a metre, and the way the field lies is worked out from the opening, the pitches or the infield (`smallestBox`, `src/venues.geo.ts`; a ballpark's way out to centre field is the one of four where its fence lies inside the outline) | ODbL 1.0, (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright): a derived database, kept in its own file and offered under the ODbL; a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
|
|
49
49
|
| `stadiums`: each stadium's seats for each configuration, number of seating bowls, kind of roof and the size of its opening (`STADIUM_DATA`, `src/stadiums.data.ts`) | The item of Wikidata (the capacity P1083) and the English Wikipedia article of each stadium, at the revision cited below, in `data-sources/stadiums/2026-10-20/` as read; the facts are numbers and words of our own, with what was read in a few words | Wikidata's data is CC0; the facts read from the article are facts, with the article credited here (CC BY-SA 4.0) |
|
|
50
50
|
| `atlas`: the clubs that played in each park, ground and arena (`tenants`: the club, its Wikidata item, its league, the years) | The occupants (P466) and home venues (P115) Wikidata lists for each place, with the start (P580) and end (P582) times of each statement, and the clubs' labels in English and Japanese, sports (P641) and leagues (P118), read with the Wikidata API and query service by `scripts/fetch-tenants.mjs` into `scripts/tenants.wikidata.json`; and the `tenants` line of the infobox of each place's English Wikipedia article, at the revision that `source.wikipedia` names for each club (155 articles, read on 2026-10-13) | Facts. Wikidata's data is CC0 (https://creativecommons.org/publicdomain/zero/1.0/), and the articles are CC BY-SA 4.0 as above; the names of clubs and the years are facts, no text, table or image of an article is used. Which clubs are tenants, and the league each is filed under, are our reading, in docs/atlas.md ("Tenants") |
|
|
51
51
|
| `parks`: the walls, quirks, backstop, warning track, dugouts and extra points of a ballpark (`walls`, `quirks`, `backstop_ft`, `track_ft`, `dugouts`, `labels`) | The infobox and the text of the same English article of each park, at the same revision as its fence (the Japanese article for the heights of the walls of the parks of Japan): the height of the wall along a stretch of the fence and the seasons it stood, the hill, overhang, poles, bullpens, ivy and basket the text describes with figures, the infobox's backstop distance, the warning track's width, the sides of the dugouts, and the further distances the text gives (Wrigley Field's wells, Sportsman's Park's and Milwaukee County Stadium's markers). The stretch each figure covers and the angle of each extra point are our reading of the article's words | Facts. The articles are CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); none of their text, tables or images is used |
|
|
@@ -436,11 +436,13 @@ The arenas of the NHL's Pacific and Central Divisions, read on 2026-10-20 (`pnpm
|
|
|
436
436
|
|
|
437
437
|
## The stadiums' sources
|
|
438
438
|
|
|
439
|
-
BC Place, read on 2026-10-20 (`pnpm stadiums:fetch`; the files are in `data-sources/stadiums/2026-10-20/`: `osm-<stadium>.json` as Overpass returned it, `wikidata.json` and `wikipedia.json`). The outline of the building, its roof and the opening in it are OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, and live in `src/stadiums.osm.data.ts` alone. The seats of each configuration, the bowls, the roof's kind and the size of its opening are in `src/stadiums.data.ts`, with what each cites. No seat map of a club, a ticket seller or a federation is read or copied, and no mark of a club, a league or a tournament is drawn.
|
|
439
|
+
BC Place, Lumen Field and T-Mobile Park, read on 2026-10-20 (`pnpm stadiums:fetch`; the files are in `data-sources/stadiums/2026-10-20/`: `osm-<stadium>.json` as Overpass returned it, `wikidata.json` and `wikipedia.json`). The outline of the building, its roof and the opening in it are OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, and live in `src/stadiums.osm.data.ts` alone. The seats of each configuration, the bowls, the roof's kind and the size of its opening are in `src/stadiums.data.ts`, with what each cites. No seat map of a club, a ticket seller or a federation is read or copied, and no mark of a club, a league or a tournament is drawn.
|
|
440
440
|
|
|
441
441
|
| Stadium | Wikidata item | OpenStreetMap way | English article and revision |
|
|
442
442
|
| --- | --- | --- | --- |
|
|
443
443
|
| BC Place | Q612227 | way/24705904 | BC Place, 1372065649 |
|
|
444
|
+
| Lumen Field | Q612736 | relation/2305831 | Lumen Field, 1373998493 |
|
|
445
|
+
| T-Mobile Park | Q1193117 | relation/3008332 | T-Mobile Park, 1379419810 |
|
|
444
446
|
|
|
445
447
|
## The World Cups' articles
|
|
446
448
|
|
package/README.md
CHANGED
|
@@ -604,7 +604,7 @@ console.log(svg.includes("ky-cr-shell"), planVenue(venueById("climate-pledge-are
|
|
|
604
604
|
|
|
605
605
|
### A real stadium, and a district
|
|
606
606
|
|
|
607
|
-
`stadium: "bc-place"` draws BC Place for football or soccer under its cable roof; `drawDistrict({ venues: ["bc-place", "rogers-arena"] })` puts venues at their real positions, to turn together in 3D ([districts](./docs/district.md)).
|
|
607
|
+
`stadium: "bc-place"` draws BC Place for football or soccer under its cable roof, and Lumen Field and T-Mobile Park (`park`) likewise ([stadiums](./docs/stadiums.md)); `drawDistrict({ venues: ["bc-place", "rogers-arena"] })` puts venues at their real positions, tight as neighbours or spread as a city, to turn together in 3D ([districts](./docs/district.md)).
|
|
608
608
|
|
|
609
609
|
<p align="center">
|
|
610
610
|
<picture>
|
|
@@ -622,6 +622,26 @@ console.log(svg.includes("ky-cr-shell"), planVenue(venueById("climate-pledge-are
|
|
|
622
622
|
<br><em>BC Place and Rogers Arena.</em>
|
|
623
623
|
</p>
|
|
624
624
|
|
|
625
|
+
<p align="center">
|
|
626
|
+
<picture>
|
|
627
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/district-seattle-3d-desk-dark.webp">
|
|
628
|
+
<img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/district-seattle-3d-desk-light.webp" alt="Lumen Field and T-Mobile Park in 3D on one ground: a football stadium under canopies and a ballpark under its roof." width="640">
|
|
629
|
+
</picture>
|
|
630
|
+
<br><em>Seattle's SoDo.</em>
|
|
631
|
+
</p>
|
|
632
|
+
|
|
633
|
+
### A fly-through
|
|
634
|
+
|
|
635
|
+
`mountFlythrough` flies one camera from a map to a real arena: the map falls to the city and the building, which rises into 3D, the roof lifts away, and the camera comes in to a seat, where a made-up play runs on the ice ([the fly-through](./docs/flythrough.md)).
|
|
636
|
+
|
|
637
|
+
<p align="center">
|
|
638
|
+
<picture>
|
|
639
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-flythrough-desk-dark.webp">
|
|
640
|
+
<img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/turning-flythrough-desk-light.webp" alt="The fly-through, sped up: a map of North America with a pin on Seattle, the building's outline in the city, Climate Pledge Arena rising into 3D and turning, the roof lifting away, a seat's view across the rink and a play on the ice." width="640">
|
|
641
|
+
</picture>
|
|
642
|
+
<br><em>The fly-through to Climate Pledge Arena, sped up.</em>
|
|
643
|
+
</p>
|
|
644
|
+
|
|
625
645
|
### Who judges where
|
|
626
646
|
|
|
627
647
|
`drawJudging` puts each judo mat's officials by seat in the picture and in a chart ([judging](./docs/judging.md)).
|
package/dist/crowd.d.ts
CHANGED
|
@@ -110,8 +110,26 @@ export interface RealShell {
|
|
|
110
110
|
}>;
|
|
111
111
|
/** The way north points on the page, in degrees clockwise from the top; absent where the rink's real orientation is not known. */
|
|
112
112
|
north?: number;
|
|
113
|
-
/**
|
|
114
|
-
|
|
113
|
+
/**
|
|
114
|
+
* `fixed` is a lid; `saddle` a roof that rises at the two ends and falls at the two sides; `cable` a cable-supported roof with an opening over the surface, which a retractable fabric closes (BC Place's);
|
|
115
|
+
* `canopy` roofs over some of the stands and leaves the field open (Lumen Field's, `canopies`); `panels` a retractable roof of sections over a ballpark's field and stands, far above its walls (T-Mobile Park's, `panels`).
|
|
116
|
+
*/
|
|
117
|
+
roof: "fixed" | "saddle" | "cable" | "canopy" | "panels";
|
|
118
|
+
/** The height of the walls, in feet, where it is not the roof's rim (a ballpark's walls stand far below its roof). */
|
|
119
|
+
wall?: number;
|
|
120
|
+
/** A `canopy` roof's pieces: the outline of each in the drawing's frame, the height of its low edge (over the field's side) and how far it rises to its high edge (over the back of the stands), in feet. */
|
|
121
|
+
canopies?: {
|
|
122
|
+
ring: Pt[];
|
|
123
|
+
low: number;
|
|
124
|
+
rise: number;
|
|
125
|
+
}[];
|
|
126
|
+
/** A `panels` roof: the outline it covers when closed, its bottom and its top above the field in feet (rim and crown), and where its sections rest when it is open. */
|
|
127
|
+
panels?: {
|
|
128
|
+
cover: Pt[];
|
|
129
|
+
bottom: number;
|
|
130
|
+
top: number;
|
|
131
|
+
parked: Pt[];
|
|
132
|
+
};
|
|
115
133
|
/** A `cable` roof: how far its crown (the edge of the opening) stands over its rim, in feet, and the opening, a polygon in the drawing's frame over the surface. */
|
|
116
134
|
crown?: {
|
|
117
135
|
rise: number;
|
package/dist/crowd.park.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface ParkInput {
|
|
|
14
14
|
crowd: Crowd;
|
|
15
15
|
/** The walls that have seats on top of them. */
|
|
16
16
|
seatsOnWalls?: readonly WallSeats[];
|
|
17
|
+
/** How deep each band of stands is, in feet: the conventions of a drawing (60, 56 and 54) unless a real ballpark's seats set them (`planPark`). */
|
|
18
|
+
depths?: {
|
|
19
|
+
lower: number;
|
|
20
|
+
upper: number;
|
|
21
|
+
bleachers: number;
|
|
22
|
+
};
|
|
17
23
|
}
|
|
18
24
|
/** The stands of a ballpark, as data. */
|
|
19
25
|
export declare function parkLayout(input: ParkInput): CrowdStands;
|
package/dist/crowd.park.js
CHANGED
|
@@ -91,16 +91,16 @@ export function parkLayout(input) {
|
|
|
91
91
|
aisles: pieces.flatMap((points) => aislesOf(points)),
|
|
92
92
|
});
|
|
93
93
|
};
|
|
94
|
-
const lowerDepth = 60;
|
|
94
|
+
const lowerDepth = input.depths?.lower ?? 60;
|
|
95
95
|
const walk = 7;
|
|
96
|
-
const upperDepth = 56;
|
|
96
|
+
const upperDepth = input.depths?.upper ?? 56;
|
|
97
97
|
add("lower", "lower", [band(inner, nI, 0, lowerDepth)], 0, lowerDepth, [4, 40], 1);
|
|
98
98
|
add("upper", "upper", [band(inner, nI, lowerDepth + walk, lowerDepth + walk + upperDepth)], lowerDepth + walk, lowerDepth + walk + upperDepth, [52, 104], 0.88);
|
|
99
99
|
const concourses = [{ region: { kind: "poly", points: band(inner, nI, lowerDepth - 0.5, lowerDepth + walk + 0.5) }, hole: null }];
|
|
100
100
|
// The outfield: bleachers right behind the fence, radially from home plate, but for a gap at the batter's eye in the horseshoe; and, where the park has seats on top of a wall,
|
|
101
101
|
// a band of them along the wall at its height.
|
|
102
102
|
const wall = 12;
|
|
103
|
-
const depth = 54;
|
|
103
|
+
const depth = input.depths?.bleachers ?? 54;
|
|
104
104
|
if (shape !== "grandstand") {
|
|
105
105
|
const nF = fence.map((p) => ({ n: [p[0] / (dist(p) || 1), p[1] / (dist(p) || 1)], k: 1 }));
|
|
106
106
|
const eye = (i) => Math.abs(fence[i][0]) < pole * 0.07 && shape === "horseshoe";
|
package/dist/district.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface DistrictMember {
|
|
|
33
33
|
/** The key of the venue and the configuration, unique in a district: `rogers-arena`, `bc-place:soccer`. */
|
|
34
34
|
key: string;
|
|
35
35
|
id: string;
|
|
36
|
-
kind: "arena" | "stadium";
|
|
36
|
+
kind: "arena" | "stadium" | "park";
|
|
37
37
|
config?: StadiumConfigId;
|
|
38
38
|
/** The name in both languages. */
|
|
39
39
|
name: {
|
|
@@ -132,7 +132,7 @@ export declare function nestedDrawing(svg: string): {
|
|
|
132
132
|
};
|
|
133
133
|
styles: string[];
|
|
134
134
|
};
|
|
135
|
-
/** The
|
|
135
|
+
/** The point of a venue's own drawing the district puts at the venue's place on the ground and turns it about: the middle of its surface (the box the stands are laid round), or home plate, the origin of a ballpark's frame. */
|
|
136
136
|
export declare function surfaceCentre(options: SurfaceOptions): Pt;
|
|
137
137
|
/** A length in metres worth drawing as a scale: the largest of 50, 100, 200, 500, 1000, 2000, 5000 that is not more than a fifth of the plan's width. */
|
|
138
138
|
export declare function scaleOf(view: {
|
package/dist/district.js
CHANGED
|
@@ -8,8 +8,7 @@ import { crowdStands, drawKyogijo } from "./draw.js";
|
|
|
8
8
|
import { FEET_PER_METRE } from "./geometry.js";
|
|
9
9
|
import { KYOGIJO_STRINGS } from "./strings.js";
|
|
10
10
|
import { esc, hash } from "./format.js";
|
|
11
|
-
import {
|
|
12
|
-
import { STADIUM_IDS, groundOfStadium, stadiumById, stadiumSeats } from "./stadiums.js";
|
|
11
|
+
import { STADIUM_IDS, stadiumById, stadiumConfigs, stadiumFrameHeading, stadiumKind, stadiumNames, stadiumSeats } from "./stadiums.js";
|
|
13
12
|
import { VENUE_ATTRIBUTION, VENUE_IDS, venueById } from "./venues.js";
|
|
14
13
|
import { hullOf, metresFrom, middleOf, smallestBox } from "./venues.geo.js";
|
|
15
14
|
export const DISTRICT_MODES = ["neighbours", "city"];
|
|
@@ -31,19 +30,19 @@ const venueMember = (venue, language) => {
|
|
|
31
30
|
};
|
|
32
31
|
};
|
|
33
32
|
const stadiumMember = (stadium, config, language) => {
|
|
34
|
-
const
|
|
33
|
+
const park = stadiumKind(stadium) === "park";
|
|
35
34
|
return {
|
|
36
35
|
key: `${stadium.id}:${config}`,
|
|
37
36
|
id: stadium.id,
|
|
38
|
-
kind: "stadium",
|
|
37
|
+
kind: park ? "park" : "stadium",
|
|
39
38
|
config,
|
|
40
|
-
name:
|
|
39
|
+
name: stadiumNames(stadium),
|
|
41
40
|
ring: stadium.osm.ring,
|
|
42
41
|
centre: stadium.osm.axis.centre,
|
|
43
|
-
heading: stadium
|
|
42
|
+
heading: stadiumFrameHeading(stadium),
|
|
44
43
|
basis: stadium.osm.axis.basis,
|
|
45
44
|
seats: stadiumSeats(stadium, config),
|
|
46
|
-
options: { sport: config, stadium: stadium.id, crowd: { real: true }, language },
|
|
45
|
+
options: park ? { sport: "mlb", park: stadium.id, crowd: { real: true }, language } : { sport: config, stadium: stadium.id, crowd: { real: true }, language },
|
|
47
46
|
};
|
|
48
47
|
};
|
|
49
48
|
/** Every key a district can be asked for: the arenas of `VENUES` and the stadiums of `STADIUMS`. */
|
|
@@ -70,9 +69,9 @@ export function districtMember(input, language = "en") {
|
|
|
70
69
|
}
|
|
71
70
|
const stadium = stadiumById(id);
|
|
72
71
|
if (stadium !== undefined) {
|
|
73
|
-
const config = (named ??
|
|
72
|
+
const config = (named ?? stadiumConfigs(stadium)[0]);
|
|
74
73
|
if (stadium.configs[config] === undefined)
|
|
75
|
-
throw new Error(`${id} has no configuration ${String(named)}: ${
|
|
74
|
+
throw new Error(`${id} has no configuration ${String(named)}: ${stadiumConfigs(stadium).join(", ")}`);
|
|
76
75
|
return stadiumMember(stadium, config, language);
|
|
77
76
|
}
|
|
78
77
|
throw new Error(`no venue ${String(id)}: the venues are ${DISTRICT_IDS.join(", ")}`);
|
|
@@ -150,8 +149,8 @@ export const KYOGIJO_DISTRICT_STYLE = `
|
|
|
150
149
|
.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}`;
|
|
151
150
|
const ROOT = /^<svg([^>]*)>/;
|
|
152
151
|
const VIEWBOX = /viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"/;
|
|
153
|
-
/** The north arrow of a venue's drawing and the apron under it: a district has one of each of its own. */
|
|
154
|
-
const withoutOwn = (svg) => svg.replace(/<rect class="ky-apron"[^>]*\/>/, "").replace(/<path class="ky-cr-north"[^>]*\/>/, "").replace(/<path class="ky-cr-north-n"[^>]*\/>/, "");
|
|
152
|
+
/** The north arrow of a venue's drawing and the apron (a ballpark's foul ground) under it: a district has one of each of its own. */
|
|
153
|
+
const withoutOwn = (svg) => svg.replace(/<rect class="ky-(?:apron|foul)"[^>]*\/>/, "").replace(/<path class="ky-cr-north"[^>]*\/>/, "").replace(/<path class="ky-cr-north-n"[^>]*\/>/, "");
|
|
155
154
|
/**
|
|
156
155
|
* A venue's drawing as a part of the district's: its root made a nested `svg` that keeps the class, the sport and the colours the drawing's stylesheet reads, its box laid in its own frame (so that the group's turn
|
|
157
156
|
* and shift put it in place), and its title, description, apron, arrow and stylesheet taken out (the stylesheet is given back in `styles`).
|
|
@@ -175,8 +174,10 @@ export function nestedDrawing(svg) {
|
|
|
175
174
|
return { markup: `<svg${keep} x="${x}" y="${y}" width="${w}" height="${h}" viewBox="${x} ${y} ${w} ${h}" aria-hidden="true" focusable="false">${body}</svg>`, box: { x, y, w, h }, styles };
|
|
176
175
|
}
|
|
177
176
|
const num = (value) => String(Math.round(value * 100) / 100);
|
|
178
|
-
/** The
|
|
177
|
+
/** The point of a venue's own drawing the district puts at the venue's place on the ground and turns it about: the middle of its surface (the box the stands are laid round), or home plate, the origin of a ballpark's frame. */
|
|
179
178
|
export function surfaceCentre(options) {
|
|
179
|
+
if (options.sport === "mlb")
|
|
180
|
+
return [0, 0];
|
|
180
181
|
const stands = crowdStands(options);
|
|
181
182
|
if (stands === undefined)
|
|
182
183
|
throw new Error("a district's venue has a crowd");
|
package/dist/draw.js
CHANGED
|
@@ -11,7 +11,7 @@ import { FOOTBALL } from "./geometry.js";
|
|
|
11
11
|
import { renderLayers } from "./layers.js";
|
|
12
12
|
import { arenaById, arenaCourtIn, arenaNameIn, arenaOfficialName, arenaRinkIn } from "./arenas.js";
|
|
13
13
|
import { venueById, venueBowl } from "./venues.js";
|
|
14
|
-
import {
|
|
14
|
+
import { planPark, stadiumBowl, stadiumById, stadiumConfigOf, stadiumKind, stadiumNames, stadiumSeats } from "./stadiums.js";
|
|
15
15
|
import { groundById, groundNameIn, groundOfficialName } from "./grounds.js";
|
|
16
16
|
import { BOARD, courtFor, hasSetsBoard, isNetLeague, isNetSport } from "./court.js";
|
|
17
17
|
import { cricketBoardMarkup } from "./cricket.board.js";
|
|
@@ -419,6 +419,17 @@ function parkFence(o) {
|
|
|
419
419
|
}
|
|
420
420
|
return { points: fenceFor(parkById(o.park), feetFence(o.fence, unit), o.year), backstop: MLB_DIAMOND.backstop };
|
|
421
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* The real stadium (and the configuration) the options draw, where `crowd: { real: true }` asks for one: a football field (`cfl`, `nfl`) or a soccer pitch in the stadium named by `stadium` or by the `ground`, a baseball field in
|
|
424
|
+
* the park named by `park`; none for a league of the caller's own, a stadium that has no such configuration, or a sport with none.
|
|
425
|
+
*/
|
|
426
|
+
function realStadiumOf(o, sport, real) {
|
|
427
|
+
const config = real ? stadiumConfigOf(sport) : undefined;
|
|
428
|
+
if (config === undefined || chooseLeague(o).league !== undefined)
|
|
429
|
+
return undefined;
|
|
430
|
+
const stadium = stadiumById(config === "mlb" ? o.park : o.stadium ?? o.ground);
|
|
431
|
+
return stadium !== undefined && stadium.configs[config] !== undefined && (stadiumKind(stadium) === "park") === (config === "mlb") ? { stadium, config } : undefined;
|
|
432
|
+
}
|
|
422
433
|
/** The stands round a surface, laid out from its box (or, for a ballpark, from its fence). */
|
|
423
434
|
function standsFor(o, surface, crowd) {
|
|
424
435
|
const { sport } = chooseLeague(o);
|
|
@@ -428,7 +439,12 @@ function standsFor(o, surface, crowd) {
|
|
|
428
439
|
// The walls that have seats on top of them: the ones the package knows (Fenway Park's Green Monster), or, asked for, every wall of 25 feet or more.
|
|
429
440
|
const park = parkById(o.park);
|
|
430
441
|
const walls = park === undefined || crowd.wallSeats === false ? [] : parkWallsIn(park, o.year).filter((w) => (crowd.wallSeats === true ? w.height_ft >= 25 : w.label !== undefined && WALL_SEATS[park.id] === w.label));
|
|
431
|
-
|
|
442
|
+
const input = { fence: fenceOutline(points), backstop, crowd, seatsOnWalls: walls.map((w) => ({ from: w.from, to: w.to, height: w.height_ft })) };
|
|
443
|
+
// A real ballpark: the building it stands in, the way home plate faces and a roof, with its stands as deep as its seats need (no visitors' block: no source places one).
|
|
444
|
+
const real = realStadiumOf(o, sport, crowd.real);
|
|
445
|
+
if (real !== undefined)
|
|
446
|
+
return planPark(real.stadium, { ...input, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd }).stands;
|
|
447
|
+
return parkLayout(input);
|
|
432
448
|
}
|
|
433
449
|
// An arena's real venue: the building it stands in and a bowl fitted to it, where the package has one for the arena and no league of the caller's own sets the surface.
|
|
434
450
|
const box = surface.whole ?? surface.box ?? { x: 0, y: 0, w: 100, h: 100 };
|
|
@@ -437,10 +453,9 @@ function standsFor(o, surface, crowd) {
|
|
|
437
453
|
if (venue !== undefined)
|
|
438
454
|
return crowdLayout({ sport, box, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: venueBowl(venue, box) });
|
|
439
455
|
// A football field or a soccer pitch in a real stadium: its building and a bowl of the configuration's seats.
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return crowdLayout({ sport, box, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: stadiumBowl(stadium, config, box) });
|
|
456
|
+
const real = realStadiumOf(o, sport, crowd.real);
|
|
457
|
+
if (real !== undefined)
|
|
458
|
+
return crowdLayout({ sport, box, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: stadiumBowl(real.stadium, real.config, box) });
|
|
444
459
|
// A dojo's crowd sits in its gallery, from the room's balcony data (in feet here); the arenas (the Budokan, the Kokugikan) keep their bowls.
|
|
445
460
|
const hall = sport === "dojo" ? dojoGallery(o) : undefined;
|
|
446
461
|
const gallery = hall === undefined ? undefined : { offset: hall.offset / 0.3048, depth: hall.depth / 0.3048, height: [hall.height[0] / 0.3048, hall.height[1] / 0.3048], rows: hall.rows, aisles: hall.aisles };
|
|
@@ -733,10 +748,10 @@ export function describe(o) {
|
|
|
733
748
|
const realCrowd = o.crowd === undefined || o.crowd === false ? undefined : resolveCrowd(o.crowd);
|
|
734
749
|
const venue = sport === "nhl" && league === undefined && realCrowd?.real === true ? venueById(o.arena) : undefined;
|
|
735
750
|
const realLine = venue === undefined || arenaName === undefined ? "" : `${gap}${fill(words.said.real, { arena: arenaName, seats: String(venue.seats.hockey), tiers: String(venue.tiers.count) })}${gap}${words.said[venue.osm.axis.basis === "mapped" ? "realMapped" : venue.osm.axis.basis === "building" ? "realBuilding" : "realNone"]}`;
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
const stadiumLine =
|
|
739
|
-
const parkLine = (parkName === undefined ? `${groundLine}${halfLine}${arenaLine}${realLine}${stadiumLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}`) + (special === undefined ? "" : `${gap}${special.text}`);
|
|
751
|
+
const real = realStadiumOf(o, sport, realCrowd?.real === true);
|
|
752
|
+
const stadiumKey = (basis) => (basis === "opening" ? "stadiumOpening" : basis === "pitch" ? "stadiumPitch" : basis === "infield" ? "stadiumInfield" : basis === "building" ? "stadiumBuilding" : "stadiumNone");
|
|
753
|
+
const stadiumLine = real === undefined ? "" : `${gap}${fill(real.config === "mlb" ? words.said.stadiumPark : words.said.stadium, { stadium: stadiumNames(real.stadium)[language], seats: String(stadiumSeats(real.stadium, real.config)), tiers: String(real.stadium.tiers.count) })}${gap}${words.said[stadiumKey(real.stadium.osm.axis.basis)]}`;
|
|
754
|
+
const parkLine = (parkName === undefined ? `${groundLine}${halfLine}${arenaLine}${realLine}${stadiumLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}${stadiumLine}`) + (special === undefined ? "" : `${gap}${special.text}`);
|
|
740
755
|
// A drawing of a past season says which: the markings are those of that season.
|
|
741
756
|
const dated = typeof o.year === "number" && Number.isFinite(o.year);
|
|
742
757
|
const eraLine = dated && league !== undefined
|
|
@@ -3,6 +3,7 @@ export type { Flythrough, FlythroughOptions, FlythroughSettings, FlythroughStart
|
|
|
3
3
|
export { CHANNELS, PACE, RAMP, STAGE_IDS, cameraAt, flightPlan, foldTime, glide, glideSeconds, mainView, mapBox, mapFrame, monotoneSlopes, nextRate, stageAt, startOf, stagesOf } from "./flythrough.path.ts";
|
|
4
4
|
export type { Camera, Channel, Flight, FlightInput, Keyframe, MapFrame, StageId } from "./flythrough.path.ts";
|
|
5
5
|
export { flythroughMarkup, graticuleMarkup, mapMarkup } from "./flythrough.markup.ts";
|
|
6
|
+
export { ILLUSTRATIVE_PLAY, PLAY_LOOK, PLAY_PERIOD, PLAY_SECONDS, PLAY_STILL, playAlpha, playMarkup, playMoment } from "./flythrough.game.ts";
|
|
6
7
|
export { FLYTHROUGH_STYLE } from "./flythrough.style.ts";
|
|
7
8
|
export { FLYTHROUGH_WORDS } from "./flythrough.words.ts";
|
|
8
9
|
export type { FlythroughWords } from "./flythrough.words.ts";
|
package/dist/flythrough-entry.js
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
export { mountFlythrough } from "./flythrough.dom.js";
|
|
3
3
|
export { CHANNELS, PACE, RAMP, STAGE_IDS, cameraAt, flightPlan, foldTime, glide, glideSeconds, mainView, mapBox, mapFrame, monotoneSlopes, nextRate, stageAt, startOf, stagesOf } from "./flythrough.path.js";
|
|
4
4
|
export { flythroughMarkup, graticuleMarkup, mapMarkup } from "./flythrough.markup.js";
|
|
5
|
+
export { ILLUSTRATIVE_PLAY, PLAY_LOOK, PLAY_PERIOD, PLAY_SECONDS, PLAY_STILL, playAlpha, playMarkup, playMoment } from "./flythrough.game.js";
|
|
5
6
|
export { FLYTHROUGH_STYLE } from "./flythrough.style.js";
|
|
6
7
|
export { FLYTHROUGH_WORDS } from "./flythrough.words.js";
|
package/dist/flythrough.dom.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { arenaById, arenaNameIn } from "./arenas.js";
|
|
6
6
|
import { STAGE_IDS, cameraAt, coverSide, flightPlan, foldTime, glide, glideSeconds, lengthWords, mainView, mapBox, mapFrame, nextRate, niceLength, stageAt, startOf, stagesOf, } from "./flythrough.path.js";
|
|
7
7
|
import { flythroughMarkup, graticuleMarkup, gridPath, mapMarkup, stageWords } from "./flythrough.markup.js";
|
|
8
|
+
import { PLAY_STILL, playAlpha, playMarkup, playMoment } from "./flythrough.game.js";
|
|
8
9
|
import { FLYTHROUGH_WORDS, say } from "./flythrough.words.js";
|
|
9
10
|
import { LENS } from "./turn.camera.js";
|
|
10
11
|
import { mountKyogijoTurn } from "./turn.dom.js";
|
|
@@ -84,6 +85,7 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
84
85
|
const scaleBar = root.querySelector(".kf-scale-bar");
|
|
85
86
|
const scaleText = root.querySelector(".kf-scale-text");
|
|
86
87
|
const caption = root.querySelector(".kf-caption");
|
|
88
|
+
const badge = root.querySelector(".kf-badge");
|
|
87
89
|
const live = root.querySelector(".kf-live");
|
|
88
90
|
const media = win?.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
89
91
|
const calm = () => settings.motion === "none" || (settings.motion !== "smooth" && media?.matches === true);
|
|
@@ -92,8 +94,25 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
92
94
|
let turn;
|
|
93
95
|
// The settings object is kept: the view reads its `lens` at every paint, and the path shortens the lens as the camera comes into the bowl.
|
|
94
96
|
const turnSettings = { preset: "overhead", controls: false, venue: { roof: true }, motion: "none", id: `${id}-turn`, lens: LENS };
|
|
95
|
-
if (stageList.some((one) => one === "arena" || one === "bowl" || one === "rink"))
|
|
97
|
+
if (stageList.some((one) => one === "arena" || one === "bowl" || one === "rink" || one === "game"))
|
|
96
98
|
turn = mountKyogijoTurn(turnHost, drawing, turnSettings);
|
|
99
|
+
// ---- The game: a plane that lies on the floor of the 3D arena, with the play's dots drawn on it by the replay's own layers. It takes the floor's own transform after every paint, and stands in the same layer of the stack
|
|
100
|
+
// as the floor, above it and under everything that stands up (the glass, the boards, the stands), so that what is in front of the ice hides the play as it hides the ice. ----
|
|
101
|
+
let gameEl;
|
|
102
|
+
let gameLayers = null;
|
|
103
|
+
let floorEl = null;
|
|
104
|
+
if (turn?.mode === "3d" && stageList.includes("game")) {
|
|
105
|
+
const world = turnHost.querySelector(".ky-turn-world");
|
|
106
|
+
floorEl = world?.querySelector(".ky-turn-floor") ?? null;
|
|
107
|
+
const box = turn.svg()?.getAttribute("viewBox");
|
|
108
|
+
if (world !== null && floorEl !== null && box !== null && box !== undefined) {
|
|
109
|
+
gameEl = doc.createElement("div");
|
|
110
|
+
gameEl.className = "kf-game";
|
|
111
|
+
gameEl.innerHTML = `<svg class="kyogijo ky-nhl" data-sport="nhl" xmlns="http://www.w3.org/2000/svg" viewBox="${box}" aria-hidden="true" focusable="false"${options.theme === undefined ? "" : ` data-theme="${options.theme}"`}><g class="kf-game-layers"></g></svg>`;
|
|
112
|
+
floorEl.after(gameEl);
|
|
113
|
+
gameLayers = gameEl.querySelector("g.kf-game-layers");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
97
116
|
// ---- The site plan: the building's outline and the rink, in the drawing's own feet; the sheet under it is the floor's. ----
|
|
98
117
|
const plan = planVenue(venue);
|
|
99
118
|
const rink = venueRink(venue);
|
|
@@ -169,8 +188,8 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
169
188
|
el.style.setProperty(prop, value);
|
|
170
189
|
};
|
|
171
190
|
const num = (value, places = 2) => String(Math.round(value * 10 ** places) / 10 ** places);
|
|
172
|
-
/** Put a camera on the screen. */
|
|
173
|
-
const render = (cam) => {
|
|
191
|
+
/** Put a camera on the screen; `clockAt` is the seconds the clock has run, from which the play's moment is worked out. */
|
|
192
|
+
const render = (cam, clockAt) => {
|
|
174
193
|
current = cam;
|
|
175
194
|
const W = width;
|
|
176
195
|
const H = height;
|
|
@@ -217,6 +236,27 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
217
236
|
turn.set({ yaw: cam.yaw, pitch: cam.pitch, zoom: Math.exp(cam.logZoom) }, false);
|
|
218
237
|
}
|
|
219
238
|
}
|
|
239
|
+
// The play on the ice, drawn again a thirtieth of a second at a time, and the label that says it is made up.
|
|
240
|
+
const gameAt = startOf(flight, "game");
|
|
241
|
+
const gameShown = cam.game > 0.003 && live3d && gameEl !== undefined && floorEl !== null && gameAt !== undefined;
|
|
242
|
+
if (gameEl !== undefined)
|
|
243
|
+
put("game", gameEl, "visibility", gameShown ? "visible" : "hidden");
|
|
244
|
+
put("badge", badge, "visibility", gameShown ? "visible" : "hidden");
|
|
245
|
+
if (gameShown && gameEl !== undefined && floorEl !== null && gameAt !== undefined) {
|
|
246
|
+
const moment = playMoment(clockAt - gameAt);
|
|
247
|
+
const alpha = cam.game * playAlpha(moment);
|
|
248
|
+
put("game", gameEl, "opacity", num(alpha, 3));
|
|
249
|
+
put("badge", badge, "opacity", num(alpha, 3));
|
|
250
|
+
put("game", gameEl, "width", floorEl.style.width);
|
|
251
|
+
put("game", gameEl, "height", floorEl.style.height);
|
|
252
|
+
// The 3D view has just been painted: the plane takes the floor's place.
|
|
253
|
+
gameEl.style.transform = floorEl.style.transform;
|
|
254
|
+
const frame = String(Math.round(moment * 30));
|
|
255
|
+
if (written["game:frame"] !== frame && gameLayers !== null) {
|
|
256
|
+
written["game:frame"] = frame;
|
|
257
|
+
gameLayers.innerHTML = playMarkup(moment, { id: `${id}-play`, language });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
220
260
|
// The pin, the scale bar (true on the map and the plan, not in perspective) and the stage's words.
|
|
221
261
|
put("pin", pin, "visibility", mapOn ? "visible" : "hidden");
|
|
222
262
|
put("pin", pin, "opacity", num(cam.map, 3));
|
|
@@ -253,6 +293,8 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
253
293
|
};
|
|
254
294
|
// ---- The clock. ----
|
|
255
295
|
let time = 0;
|
|
296
|
+
/** The seconds the clock has run, not folded back by the turn's period as `time` is: the play's moment is worked out from it, so that the play does not jump when the turn comes round. */
|
|
297
|
+
let clock = 0;
|
|
256
298
|
let rate = 0;
|
|
257
299
|
let playing = false;
|
|
258
300
|
let journey;
|
|
@@ -276,7 +318,7 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
276
318
|
const show = (at, announce = true) => {
|
|
277
319
|
time = foldTime(flight, at);
|
|
278
320
|
const cam = cameraAt(flight, time);
|
|
279
|
-
render(cam);
|
|
321
|
+
render(cam, clock);
|
|
280
322
|
enter(stageAt(flight, time), announce);
|
|
281
323
|
};
|
|
282
324
|
const stopLoop = () => {
|
|
@@ -300,12 +342,14 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
300
342
|
journey.elapsed += dt;
|
|
301
343
|
const done = journey.elapsed >= journey.seconds;
|
|
302
344
|
time = done ? journey.to : glide(journey.from, journey.to, journey.elapsed, journey.seconds);
|
|
345
|
+
clock = time;
|
|
303
346
|
if (done)
|
|
304
347
|
journey = undefined;
|
|
305
348
|
}
|
|
306
349
|
else {
|
|
307
350
|
rate = nextRate(rate, playing, dt);
|
|
308
351
|
time += rate * dt * speed;
|
|
352
|
+
clock += rate * dt * speed;
|
|
309
353
|
}
|
|
310
354
|
show(time, true);
|
|
311
355
|
if (busy())
|
|
@@ -334,7 +378,8 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
334
378
|
};
|
|
335
379
|
const stillStage = (to, announce = true) => {
|
|
336
380
|
time = startOf(flight, to) ?? 0;
|
|
337
|
-
|
|
381
|
+
clock = time;
|
|
382
|
+
render(flight.rest[to], to === "game" ? time + PLAY_STILL : time);
|
|
338
383
|
enter(to, announce);
|
|
339
384
|
};
|
|
340
385
|
const scheduleStill = () => {
|
|
@@ -508,7 +553,8 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
508
553
|
stopLoop();
|
|
509
554
|
journey = undefined;
|
|
510
555
|
rate = 0;
|
|
511
|
-
|
|
556
|
+
clock = Math.max(0, seconds);
|
|
557
|
+
show(clock, false);
|
|
512
558
|
},
|
|
513
559
|
destroy() {
|
|
514
560
|
if (!alive)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReplayLook, ReplayTimeline } from "./replay.types.ts";
|
|
2
|
+
import type { Language } from "./types.ts";
|
|
3
|
+
/** A place on the rink, in feet from its top left corner as the drawing has it wide: along the rink (0 to 200) and across it (0 to 85). */
|
|
4
|
+
type P = readonly [number, number];
|
|
5
|
+
/** A skater's way: where he is at a time (seconds), from one waypoint to the next. */
|
|
6
|
+
type Way = readonly (readonly [number, number, number])[];
|
|
7
|
+
/** How long the play runs, and how long it is held at the end (the goal's marks still on the ice) before it starts again. */
|
|
8
|
+
export declare const PLAY_SECONDS = 10;
|
|
9
|
+
export declare const PLAY_HOLD = 1.5;
|
|
10
|
+
/** The seconds of the whole run, the play and the hold: what the loop's period is. */
|
|
11
|
+
export declare const PLAY_PERIOD: number;
|
|
12
|
+
/** The moment a still of the play (for a reader who prefers reduced motion) is taken at: the shot has just been taken. */
|
|
13
|
+
export declare const PLAY_STILL = 6.4;
|
|
14
|
+
/** The seconds the dots fade in and out at the ends of a run, so that the play does not jump back to its start in view. */
|
|
15
|
+
export declare const PLAY_FADE = 0.5;
|
|
16
|
+
/** A point of a way at a time: a cubic through the waypoints with the slope between the neighbours at each, so that a skater never stops or turns on a corner. */
|
|
17
|
+
export declare function at(way: Way, t: number): P;
|
|
18
|
+
/** The puck at a time. */
|
|
19
|
+
export declare function puckAt(t: number): P;
|
|
20
|
+
/** The play, as a replay's timeline in the surface's own feet. It is made once. */
|
|
21
|
+
export declare const ILLUSTRATIVE_PLAY: ReplayTimeline;
|
|
22
|
+
/** How the play is drawn: a short trail behind the puck, the dots large enough to see from the stands, events that stay a moment. */
|
|
23
|
+
export declare const PLAY_LOOK: ReplayLook;
|
|
24
|
+
/** Where a moment of a run is in the play, looping: `seconds` after the run began, brought into one period. */
|
|
25
|
+
export declare const playMoment: (seconds: number) => number;
|
|
26
|
+
/** How much of the play shows at a moment of a run: it fades in at the start and out at the end of each run. 0 to 1. */
|
|
27
|
+
export declare function playAlpha(moment: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* The play at a moment of a run, as the layers' SVG (a `g.ky-layers` in the drawing's own feet, to go inside an `svg.kyogijo` with the floor's viewBox): the dots with their numbers, the puck and its trail, and the events that are on
|
|
30
|
+
* the ice at that time, drawn by the replay's own layers. The same moment always gives the same string.
|
|
31
|
+
*/
|
|
32
|
+
export declare function playMarkup(moment: number, options: {
|
|
33
|
+
id: string;
|
|
34
|
+
language?: Language;
|
|
35
|
+
}): string;
|
|
36
|
+
export {};
|