@johnmorrisdotca/kyogijo-stadiums 0.40.2 → 0.44.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 +72 -0
- package/NOTICE.md +22 -0
- package/README.md +1 -3
- package/dist/cityctx-entry.d.ts +3 -0
- package/dist/cityctx-entry.js +3 -0
- package/dist/cityctx.d.ts +19 -0
- package/dist/cityctx.data.d.ts +11 -0
- package/dist/cityctx.data.js +7139 -0
- package/dist/cityctx.js +34 -0
- package/dist/context.d.ts +175 -0
- package/dist/context.js +369 -0
- package/dist/crowd.d.ts +9 -0
- package/dist/district-entry.d.ts +16 -1
- package/dist/district-entry.js +9 -0
- package/dist/district.d.ts +99 -4
- package/dist/district.dom.d.ts +31 -0
- package/dist/district.dom.js +52 -0
- package/dist/district.js +103 -25
- package/dist/district.turn.js +89 -19
- package/dist/gates.d.ts +56 -0
- package/dist/gates.js +95 -0
- package/dist/halls.d.ts +2 -0
- package/dist/halls.js +1 -1
- package/dist/kodokan.d.ts +116 -0
- package/dist/kodokan.data.d.ts +61 -0
- package/dist/kodokan.data.js +20 -0
- package/dist/kodokan.js +92 -0
- package/dist/osm-entry.d.ts +2 -0
- package/dist/osm-entry.js +2 -0
- package/dist/osm.context.data.d.ts +10 -0
- package/dist/osm.context.data.js +1107 -0
- package/dist/outside.d.ts +43 -0
- package/dist/outside.dom.d.ts +44 -0
- package/dist/outside.dom.js +203 -0
- package/dist/outside.js +227 -0
- package/dist/outside.paint.d.ts +57 -0
- package/dist/outside.paint.js +88 -0
- package/dist/plateau-entry.d.ts +3 -0
- package/dist/plateau-entry.js +3 -0
- package/dist/plateau.credit.d.ts +5 -0
- package/dist/plateau.credit.js +7 -0
- package/dist/plateau.data.d.ts +7 -0
- package/dist/plateau.data.js +757 -0
- package/dist/stadiums.d.ts +14 -0
- package/dist/stadiums.data.js +2 -1
- package/dist/stadiums.js +2 -0
- package/dist/strings.d.ts +3 -1
- package/dist/strings.js +4 -4
- package/dist/turn.dom.d.ts +10 -0
- package/dist/turn.dom.js +21 -0
- package/dist/turn.kit.d.ts +12 -3
- package/dist/turn.kit.js +7 -0
- package/dist/turn.markup.js +15 -3
- package/dist/turn.model.d.ts +3 -0
- package/dist/turn.style.js +20 -3
- package/dist/venue.real.d.ts +22 -1
- package/dist/venue.real.js +76 -7
- package/dist/venues.d.ts +9 -1
- package/dist/venues.data.js +2 -1
- package/dist/venues.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walk.d.ts +56 -0
- package/dist/walk.data.d.ts +4 -0
- package/dist/walk.data.js +1295 -0
- package/dist/walk.js +120 -0
- package/package.json +25 -1
package/dist/stadiums.d.ts
CHANGED
|
@@ -80,6 +80,13 @@ export interface RealStadiumData {
|
|
|
80
80
|
};
|
|
81
81
|
read: string;
|
|
82
82
|
};
|
|
83
|
+
/** The building's lower band, a podium under the concourse levels: its height in feet and how it is known (ours where the sources do not say). */
|
|
84
|
+
facade?: {
|
|
85
|
+
podium: number;
|
|
86
|
+
unit: "ft";
|
|
87
|
+
basis: StadiumBasis;
|
|
88
|
+
read: string;
|
|
89
|
+
};
|
|
83
90
|
roof: {
|
|
84
91
|
kind: StadiumRoof;
|
|
85
92
|
basis: StadiumBasis;
|
|
@@ -90,6 +97,13 @@ export interface RealStadiumData {
|
|
|
90
97
|
width: number;
|
|
91
98
|
unit: "m";
|
|
92
99
|
};
|
|
100
|
+
/** A cable roof's masts round its rim, as the engineering article gives them: how many, and how high they rise over the ring beam, in metres (the source is named, apart from Wikipedia's, in `source`). */
|
|
101
|
+
masts?: {
|
|
102
|
+
count: number;
|
|
103
|
+
height: number;
|
|
104
|
+
unit: "m";
|
|
105
|
+
source: string;
|
|
106
|
+
};
|
|
93
107
|
/** A canopy's share of the seats it covers, as the article gives it. */
|
|
94
108
|
cover?: number;
|
|
95
109
|
/** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap). */
|
package/dist/stadiums.data.js
CHANGED
|
@@ -7,7 +7,8 @@ export const STADIUM_DATA = [
|
|
|
7
7
|
"configs": { "cfl": { "sport": "cfl", "seats": 54500, "source": "wikipedia", "read": "the infobox's first capacity, 54,500, which Wikidata's capacity (P1083) also gives; the article's stand for football of the CFL's club" }, "soccer": { "sport": "soccer", "seats": 52497, "source": "wikipedia", "read": "the infobox's capacity for the 2026 world cup of soccer, 52,497, 2,003 fewer than its first" } },
|
|
8
8
|
"wikidataSeats": [54500],
|
|
9
9
|
"tiers": { "count": 2, "basis": "documented", "lowerSeats": 27695, "read": "the infobox's lower bowl of 27,695 seats, and the article's lower and upper bowls (the club that plays soccer there sold only the lower bowl)" },
|
|
10
|
-
"roof": { "kind": "cable", "basis": "documented", "read": "the article: a retractable roof supported by cables, the largest of its kind, its opening 100 by 85 metres (the size of the field), the fabric retracting into a pod in the middle of the opening above the suspended video board", "opening": { "length": 100, "width": 85, "unit": "m" } },
|
|
10
|
+
"roof": { "kind": "cable", "basis": "documented", "read": "the article: a retractable roof supported by cables, the largest of its kind, its opening 100 by 85 metres (the size of the field), the fabric retracting into a pod in the middle of the opening above the suspended video board", "opening": { "length": 100, "width": 85, "unit": "m" }, "masts": { "count": 36, "height": 47.5, "unit": "m", "source": "https://www.structuremag.org/article/bc-place-revitalization/ (STRUCTURE magazine, 'BC Place Revitalization': thirty-six steel perimeter masts rise 47.5 metres above the original roof's concrete ring beam, carrying a radial cable truss post-tensioned in a new steel compression ring; the roof is about 40,000 square metres, clear spans of 227 by 186 metres, its centre 7,500 square metres retractable into the centre gondola above the video scoreboard); read on 2026-10-22, facts only" } },
|
|
11
|
+
"facade": { "podium": 40, "unit": "ft", "basis": "plausible", "read": "the brief: a lower podium ring under the concourse levels, in the building's own outline; its height is ours (the sources do not say)" },
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
14
|
"id": "lumen-field",
|
package/dist/stadiums.js
CHANGED
|
@@ -307,6 +307,8 @@ function plan(stadium, config, rink) {
|
|
|
307
307
|
roof: cable ? "cable" : canopies === undefined ? "fixed" : "canopy",
|
|
308
308
|
edge,
|
|
309
309
|
...(cable ? { crown: { rise, opening } } : {}),
|
|
310
|
+
...(stadium.facade === undefined ? {} : { podium: stadium.facade.podium }),
|
|
311
|
+
...(cable && stadium.roof.masts !== undefined ? { masts: { count: stadium.roof.masts.count, height: round(stadium.roof.masts.height * FEET_PER_METRE) } } : {}),
|
|
310
312
|
...(canopies === undefined ? {} : { canopies }),
|
|
311
313
|
},
|
|
312
314
|
};
|
package/dist/strings.d.ts
CHANGED
|
@@ -529,6 +529,8 @@ export interface KyogijoWords {
|
|
|
529
529
|
districtNeighbours: string;
|
|
530
530
|
districtCity: string;
|
|
531
531
|
districtCredit: string;
|
|
532
|
+
/** The people outside a district's venues: {percent} of the venues' seats, {people} people, on ground people may stand on. */
|
|
533
|
+
districtOutside: string;
|
|
532
534
|
/** A game decided on penalties: {name}, and the shoot-out score {a}-{b}, winner first. */
|
|
533
535
|
wonPens: string;
|
|
534
536
|
/** The shoot-out, as it is written beside a status: {a} and {b}, in the order of the sides on the drawing. */
|
|
@@ -1046,7 +1048,7 @@ export interface KyogijoWords {
|
|
|
1046
1048
|
item: string;
|
|
1047
1049
|
itemSag: string;
|
|
1048
1050
|
/** The names of what stands on the surface, by kind. */
|
|
1049
|
-
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" | "judges" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board", string>;
|
|
1051
|
+
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" | "judges" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board" | "blocks", string>;
|
|
1050
1052
|
};
|
|
1051
1053
|
/** The names of the zones that `zoneAt` answers with (rules.ts), by id. */
|
|
1052
1054
|
rules: Record<RuleId, string>;
|
package/dist/strings.js
CHANGED
|
@@ -199,7 +199,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
199
199
|
},
|
|
200
200
|
},
|
|
201
201
|
scoreTitle: "{away} {awayScore}, {home} {homeScore}",
|
|
202
|
-
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
202
|
+
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", districtOutside: "People outside: {percent} percent of the seats of the venues, {people} people, stand on the sidewalks, plazas and grounds round them, and none on a road.", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
203
203
|
budo: {
|
|
204
204
|
sides: { north: "north", south: "south", east: "east", west: "west" },
|
|
205
205
|
judo: "a judo contest area of {contest} by {contest} metres in a safety area {safety} metres wide, laid with tatami of {tatami}",
|
|
@@ -455,7 +455,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
455
455
|
announce: "{name} view", announceAngle: "Turned to {yaw} degrees, seen from {pitch} degrees above the floor.",
|
|
456
456
|
flat: "This browser cannot draw the 3D view, so the flat drawing is shown.",
|
|
457
457
|
standing: "Standing on it, in the 3D view: {list}.", item: "{name} {h}", itemSag: "{name} {h} at the posts, {low} at the middle",
|
|
458
|
-
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", judges: "name cards on the judges' table", deck: "platform", "cage-fence": "fence", "cage-posts": "padded posts", "ring-ropes": "ropes", "ring-posts": "corner posts", "ribbon-boards": "LED ribbon boards", "hung-board": "centre-hung scoreboard" },
|
|
458
|
+
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", judges: "name cards on the judges' table", deck: "platform", "cage-fence": "fence", "cage-posts": "padded posts", "ring-ropes": "ropes", "ring-posts": "corner posts", "ribbon-boards": "LED ribbon boards", "hung-board": "centre-hung scoreboard", blocks: "surrounding buildings" },
|
|
459
459
|
},
|
|
460
460
|
rules: {
|
|
461
461
|
"defensive-zone": "Defensive zone",
|
|
@@ -758,7 +758,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
758
758
|
},
|
|
759
759
|
},
|
|
760
760
|
scoreTitle: "{away} {awayScore}、{home} {homeScore}",
|
|
761
|
-
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
761
|
+
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", districtOutside: "会場の外の人出:会場の全座席数の{percent}%にあたる{people}人が、会場の周りの歩道、広場、敷地に立っています。車道には立っていません。", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
762
762
|
budo: {
|
|
763
763
|
sides: { north: "北", south: "南", east: "東", west: "西" },
|
|
764
764
|
judo: "柔道の試合場。試合場内は{contest}m四方、幅{safety}mの安全地帯があり、{tatami}の畳を敷いています",
|
|
@@ -1014,7 +1014,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
1014
1014
|
announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
|
|
1015
1015
|
flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
|
|
1016
1016
|
standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
|
|
1017
|
-
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: "審判席と記録台", judges: "審判員席の名札", deck: "台", "cage-fence": "金網", "cage-posts": "パッド付き支柱", "ring-ropes": "ロープ", "ring-posts": "コーナーポスト", "ribbon-boards": "LEDリボンビジョン", "hung-board": "センターハングビジョン(吊り下げ式スコアボード)" },
|
|
1017
|
+
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: "審判席と記録台", judges: "審判員席の名札", deck: "台", "cage-fence": "金網", "cage-posts": "パッド付き支柱", "ring-ropes": "ロープ", "ring-posts": "コーナーポスト", "ribbon-boards": "LEDリボンビジョン", "hung-board": "センターハングビジョン(吊り下げ式スコアボード)", blocks: "周辺の建物" },
|
|
1018
1018
|
},
|
|
1019
1019
|
rules: {
|
|
1020
1020
|
"defensive-zone": "ディフェンディングゾーン",
|
package/dist/turn.dom.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type TurnDirection, type TurnPreset, type TurnView } from "./turn.camer
|
|
|
2
2
|
import { type TurnModel, type TurnSettings } from "./turn.model.ts";
|
|
3
3
|
import type { ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
4
4
|
import type { VenueSettings } from "./venue.ts";
|
|
5
|
+
import type { OutsideLevel } from "./outside.paint.ts";
|
|
5
6
|
/** What `mountKyogijoTurn` gives back. `O` is what `update` takes: the options of a drawing, or (for `mountMultiTurn`) of a tournament layout. */
|
|
6
7
|
export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
7
8
|
/** `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. */
|
|
@@ -33,6 +34,15 @@ export interface KyogijoTurn<O = SurfaceOptions | ScoreOptions> {
|
|
|
33
34
|
setDirection(direction: TurnDirection): void;
|
|
34
35
|
/** Start the auto-turn if it is stopped and stop it if it is running; it gives whether it is running now. */
|
|
35
36
|
togglePlay(): boolean;
|
|
37
|
+
/** For a view with a crowd outside (a district asked for one): show another level at once, without drawing the view again, so that the turn goes on. */
|
|
38
|
+
setOutside?(level: OutsideLevel): void;
|
|
39
|
+
/** What the crowd outside's last paint cost, in milliseconds, how many people it drew and how (`dots` or `density`), or `undefined` where there is no crowd or it has not been drawn. */
|
|
40
|
+
outsideCost?(): {
|
|
41
|
+
ms: number;
|
|
42
|
+
people: number;
|
|
43
|
+
mode: string;
|
|
44
|
+
pixels: number;
|
|
45
|
+
} | undefined;
|
|
36
46
|
/** The flat drawing that is the floor, or `null` after `destroy`. */
|
|
37
47
|
svg(): SVGSVGElement | null;
|
|
38
48
|
/** Stop listening and empty the container. */
|
package/dist/turn.dom.js
CHANGED
|
@@ -9,6 +9,7 @@ import { controlsMarkup, layoutOf, turnMarkupOf, worldMarkup } from "./turn.mark
|
|
|
9
9
|
import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
10
10
|
import { esc } from "./format.js";
|
|
11
11
|
import { turnBoardMarkup } from "./overlay.js";
|
|
12
|
+
import { attachOutside3d } from "./outside.dom.js";
|
|
12
13
|
/** True where the page can draw a plane in 3D by one `matrix3d`: that is, nearly everywhere with a stylesheet. */
|
|
13
14
|
export function turnSupported(win = typeof window === "undefined" ? undefined : window) {
|
|
14
15
|
const css = win?.CSS;
|
|
@@ -109,6 +110,14 @@ export function mountTurn(host, options, settings, build) {
|
|
|
109
110
|
faded = elements.map((one) => one.style.opacity);
|
|
110
111
|
};
|
|
111
112
|
gather();
|
|
113
|
+
// The crowd outside, a canvas on the floor (the floor's matrix carries it), painted again only when its level, its detail or its colour changes.
|
|
114
|
+
let outside;
|
|
115
|
+
const crowd = () => {
|
|
116
|
+
outside?.destroy();
|
|
117
|
+
const floor = stage.querySelector(".ky-turn-floor");
|
|
118
|
+
outside = model.outside === undefined || floor === null ? undefined : attachOutside3d(floor, model.outside);
|
|
119
|
+
};
|
|
120
|
+
crowd();
|
|
112
121
|
const buttons = () => root.querySelectorAll("button[data-preset]");
|
|
113
122
|
/** The Play button says what it is and what it does: pressed while the view turns. */
|
|
114
123
|
const syncPlay = () => {
|
|
@@ -127,6 +136,7 @@ export function mountTurn(host, options, settings, build) {
|
|
|
127
136
|
const width = stage.clientWidth || 960;
|
|
128
137
|
const height = stage.clientHeight || 600;
|
|
129
138
|
const layout = layoutOf(model, now, width, height, settings.lens);
|
|
139
|
+
outside?.zoom(now.zoom);
|
|
130
140
|
elements.forEach((one, i) => {
|
|
131
141
|
if (written[i] !== layout.transforms[i])
|
|
132
142
|
one.style.transform = written[i] = layout.transforms[i];
|
|
@@ -477,6 +487,7 @@ export function mountTurn(host, options, settings, build) {
|
|
|
477
487
|
world.setAttribute("data-theme", forced);
|
|
478
488
|
}
|
|
479
489
|
gather();
|
|
490
|
+
crowd();
|
|
480
491
|
// The board is a layer of its own over the view, after the scene: it is written again, and the view's turn is not touched.
|
|
481
492
|
stage.querySelector(".ky-turn-board")?.remove();
|
|
482
493
|
if (model.board !== undefined)
|
|
@@ -517,11 +528,21 @@ export function mountTurn(host, options, settings, build) {
|
|
|
517
528
|
setPlaying(!playing);
|
|
518
529
|
return playing;
|
|
519
530
|
},
|
|
531
|
+
setOutside(level) {
|
|
532
|
+
if (!alive)
|
|
533
|
+
return;
|
|
534
|
+
outside?.setLevel(level);
|
|
535
|
+
},
|
|
536
|
+
outsideCost() {
|
|
537
|
+
const last = outside?.last;
|
|
538
|
+
return last === undefined ? undefined : { ms: last.ms, people: last.stats.people, mode: last.stats.mode, pixels: last.pixels };
|
|
539
|
+
},
|
|
520
540
|
svg: () => (alive ? root.querySelector(".ky-turn-floor svg.kyogijo") : null),
|
|
521
541
|
destroy() {
|
|
522
542
|
if (!alive)
|
|
523
543
|
return;
|
|
524
544
|
alive = false;
|
|
545
|
+
outside?.destroy();
|
|
525
546
|
stop();
|
|
526
547
|
stopTick();
|
|
527
548
|
seen?.disconnect();
|
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" | "surround" | "chair" | "desk" | "card" | "sign" | "apron" | "bench" | "floor" | "ribbon" | "hung-board";
|
|
4
|
+
export type PartKind = "mast" | "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" | "card" | "sign" | "apron" | "bench" | "floor" | "ribbon" | "hung-board" | "block";
|
|
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`. */
|
|
@@ -35,14 +35,23 @@ export interface Plane {
|
|
|
35
35
|
fadeTo?: number;
|
|
36
36
|
/** 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. */
|
|
37
37
|
fadeAt?: number;
|
|
38
|
+
/**
|
|
39
|
+
* A `shy` plane that gives way by the side the eye is on and not by how low it is: every wall on the eye's side of what it surrounds fades the same, at any tilt, and one on the far side stays whole. A see-through building's walls
|
|
40
|
+
* (a raised room is seen through every wall between it and the eye, at a low angle and from straight above alike).
|
|
41
|
+
*/
|
|
42
|
+
fadeSide?: boolean;
|
|
43
|
+
/** A plate the other planes of its building stand on (a floor of a building picked out): it is placed by its farthest corner, so that it is drawn under everything on it, whatever way the view turns. */
|
|
44
|
+
under?: boolean;
|
|
38
45
|
/**
|
|
39
46
|
* The building (a `TurnModel.groups` index) a plane belongs to, in a scene of several that stand apart (a district's): the view draws one building's planes all before another's, by which side of the line between
|
|
40
47
|
* them the eye is on, and the planes of one building among themselves by depth.
|
|
41
48
|
*/
|
|
42
49
|
group?: number;
|
|
43
50
|
}
|
|
51
|
+
/** The `group` of a plane that belongs to no building of its scene but stands apart from all of them: a district's surrounding blocks. The view stacks these among themselves by depth and then among the buildings' planes by depth, where the buildings' are stacked building by building. */
|
|
52
|
+
export declare const APART = -1;
|
|
44
53
|
/** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
|
|
45
|
-
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" | "judges" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board";
|
|
54
|
+
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" | "judges" | "deck" | "cage-fence" | "cage-posts" | "ring-ropes" | "ring-posts" | "ribbon-boards" | "hung-board" | "blocks";
|
|
46
55
|
/** A thing that stands on the surface: where, how high, and the planes it is made of. */
|
|
47
56
|
export interface Upright {
|
|
48
57
|
id: string;
|
|
@@ -102,7 +111,7 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
|
|
|
102
111
|
* 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
|
|
103
112
|
* from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
|
|
104
113
|
*/
|
|
105
|
-
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
|
|
114
|
+
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt" | "fadeSide">, yaw: number, pitch: number): number;
|
|
106
115
|
/** A rectangle on the surface, in feet: its corners. */
|
|
107
116
|
export type Rect = {
|
|
108
117
|
x0: number;
|
package/dist/turn.kit.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// The frame is the drawing's own: x along the surface and y across it, both in feet, as the SVG has them (y grows downwards on the page), and z the height
|
|
6
6
|
// above the surface, in feet, growing towards the eye of someone looking straight down.
|
|
7
|
+
/** The `group` of a plane that belongs to no building of its scene but stands apart from all of them: a district's surrounding blocks. The view stacks these among themselves by depth and then among the buildings' planes by depth, where the buildings' are stacked building by building. */
|
|
8
|
+
export const APART = -1;
|
|
7
9
|
/** The four numbers a plane's SVG needs: its length along `u` and along `v`, in feet. */
|
|
8
10
|
export const size = (p) => [Math.hypot(...p.u), Math.hypot(...p.v)];
|
|
9
11
|
export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
@@ -108,6 +110,11 @@ export function fadeOf(p, yaw, pitch) {
|
|
|
108
110
|
const a = (yaw * Math.PI) / 180;
|
|
109
111
|
// The horizontal direction from the surface to the eye (the same sum as the depth of a point).
|
|
110
112
|
const toward = p.shy[0] * Math.sin(a) + p.shy[1] * Math.cos(a);
|
|
113
|
+
// By side: whole on the far side, a short ramp across the walls edge-on to the eye, and the same fade on every wall of the near side, whatever the tilt.
|
|
114
|
+
if (p.fadeSide === true) {
|
|
115
|
+
const t = Math.min(1, Math.max(0, (toward + 0.05) / 0.15));
|
|
116
|
+
return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
|
|
117
|
+
}
|
|
111
118
|
const hides = toward * Math.sin((pitch * Math.PI) / 180);
|
|
112
119
|
const from = p.fadeAt ?? 0.1;
|
|
113
120
|
const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
|
package/dist/turn.markup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the elements are stacked far to near by `z-index`. A browser that cannot keep nested 3D layers (Safari on a phone can lose them) draws it as it is meant.
|
|
4
4
|
// The same text is what a page is given from `turnMarkup` and what the mount writes; the mount then makes it turn.
|
|
5
5
|
import { TURN_PRESET_NAMES, cameraMatrix, depthOf, eyeDistance, fit, matrix3d, multiply, presetOf, turnDirection } from "./turn.camera.js";
|
|
6
|
-
import { cross, facing, fadeOf, n, size, unit } from "./turn.kit.js";
|
|
6
|
+
import { APART, cross, facing, fadeOf, n, size, unit } from "./turn.kit.js";
|
|
7
7
|
import { buildTurn, turnWords } from "./turn.model.js";
|
|
8
8
|
import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
9
9
|
import { esc } from "./format.js";
|
|
@@ -66,12 +66,24 @@ export function layoutOf(model, view, width, height, lens) {
|
|
|
66
66
|
const transforms = [floor, ...planes].map((p) => matrix3d(multiply(camera, planeMatrix(p, s, centre))));
|
|
67
67
|
// Far to near: a plane's depth is that of its middle. The ground of a hall is under everything. The floor is next, unless it stands raised on something (a table tennis table's top, on its legs): then it is
|
|
68
68
|
// sorted with the rest, by its middle, so that the legs and the skirt on the far side are drawn under it and the ones on the near side over it, and nothing under the top shows through it.
|
|
69
|
-
const depth = (p) =>
|
|
69
|
+
const depth = (p) => {
|
|
70
|
+
// A see-through building's wall is a tall plane round what stands in it: a far wall is behind all of it and a near one in front of all of it, however high its middle is, so a far wall is placed by its foot and a near one by its top.
|
|
71
|
+
const zs = [p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]];
|
|
72
|
+
const z = p.fadeSide === true ? (fadeOf(p, view.yaw, view.pitch) >= 1 ? Math.min(...zs) : Math.max(...zs)) : (p.o[2] + (p.u[2] + p.v[2]) / 2);
|
|
73
|
+
if (p.under === true)
|
|
74
|
+
return Math.min(...[[0, 0], [1, 0], [0, 1], [1, 1]].map(([a, b]) => depthOf([(p.o[0] + a * p.u[0] + b * p.v[0] - centre[0]) * s, (p.o[1] + a * p.u[1] + b * p.v[1] - centre[1]) * s, (p.o[2] + a * p.u[2] + b * p.v[2]) * s], view, at)));
|
|
75
|
+
return depthOf([(p.o[0] + (p.u[0] + p.v[0]) / 2 - centre[0]) * s, (p.o[1] + (p.u[1] + p.v[1]) / 2 - centre[1]) * s, z * s], view, at);
|
|
76
|
+
};
|
|
70
77
|
const raised = model.uprights.lift > 0;
|
|
71
78
|
const order = [...planes.map((p, i) => ({ i: i + 1, ground: p.kind === "ground", depth: depth(p), group: p.group })), ...(raised ? [{ i: 0, ground: false, depth: depth(floor), group: undefined }] : [])].filter((one) => !one.ground);
|
|
72
79
|
// Buildings that stand apart are drawn one after another, the far one first; a plane of none (the floor) goes by its depth alone.
|
|
73
80
|
const rank = model.groups === undefined ? undefined : groupRanks(model.groups, view.yaw);
|
|
74
|
-
|
|
81
|
+
// The planes of the blocks that stand round the buildings (`APART`) are stacked by depth among themselves and then woven into the buildings' by depth, each list keeping its own order.
|
|
82
|
+
const apart = order.filter((one) => one.group === APART).sort((a, b) => a.depth - b.depth);
|
|
83
|
+
const own = order.filter((one) => one.group !== APART).sort((a, b) => (rank === undefined || a.group === undefined || b.group === undefined ? 0 : rank[a.group] - rank[b.group]) || a.depth - b.depth);
|
|
84
|
+
order.length = 0;
|
|
85
|
+
for (let i = 0, j = 0; i < own.length || j < apart.length;)
|
|
86
|
+
order.push(j >= apart.length || (i < own.length && own[i].depth <= apart[j].depth) ? own[i++] : apart[j++]);
|
|
75
87
|
const stack = new Array(model.planes.length + 1).fill(0);
|
|
76
88
|
stack[0] = 1;
|
|
77
89
|
order.forEach((one, rank) => {
|
package/dist/turn.model.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type UprightOptions, type Uprights } from "./uprights.ts";
|
|
|
5
5
|
import { type ResolvedBoard } from "./overlay.ts";
|
|
6
6
|
import type { VenueSettings } from "./venue.ts";
|
|
7
7
|
import type { Box3 } from "./turn.camera.ts";
|
|
8
|
+
import type { OutsideSpec } from "./outside.dom.ts";
|
|
8
9
|
import type { Language, LightingOptions, LightingPreset, ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
9
10
|
/** What can be asked of a 3D view. */
|
|
10
11
|
export interface TurnSettings {
|
|
@@ -112,6 +113,8 @@ export interface TurnModel {
|
|
|
112
113
|
}[];
|
|
113
114
|
/** The board the options ask for (`board`), laid over the view: a layer of its own, never a plane of the scene. */
|
|
114
115
|
board?: ResolvedBoard;
|
|
116
|
+
/** The crowd outside the venues of a district, asked for (`DistrictOptions.outside`): a canvas on the floor that the mount paints (`outside.dom.ts`), never a plane of the scene. */
|
|
117
|
+
outside?: OutsideSpec;
|
|
115
118
|
}
|
|
116
119
|
/** The lighting a venue is built with, as a model's field: the venue's own setting, else the crowd's, and none where there is no venue. */
|
|
117
120
|
export declare const lightingOf: (venue: {
|
package/dist/turn.style.js
CHANGED
|
@@ -5,9 +5,9 @@ import { KYOGIJO_TURN_BOARD_STYLE } from "./overlay.js";
|
|
|
5
5
|
* defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
|
|
6
6
|
*/
|
|
7
7
|
export const KYOGIJO_TURN_STYLE = `
|
|
8
|
-
:where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-ivy:#5b8a45;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT}}
|
|
9
|
-
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}}
|
|
10
|
-
:where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}
|
|
8
|
+
:where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-ivy:#5b8a45;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT};--ky-up-blk-a:#e9ece4;--ky-up-blk-b:#c9cdc3;--ky-up-blk-c:#a4aa9f;--ky-up-blk-roof:#f4f5f0;--ky-up-blk-line:rgba(40,46,42,.38);--ky-up-plate:rgba(47,109,176,.5);--ky-up-plate-line:#2f6db0}
|
|
9
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}}
|
|
10
|
+
:where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK};--ky-up-blk-a:#59626b;--ky-up-blk-b:#444b53;--ky-up-blk-c:#30363c;--ky-up-blk-roof:#69727b;--ky-up-blk-line:rgba(220,226,220,.3);--ky-up-plate:rgba(127,176,232,.45);--ky-up-plate-line:#7fb0e8}
|
|
11
11
|
.ky-turn{position:relative;display:block;width:100%;color:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);background:var(--ky-turn-bg)}
|
|
12
12
|
.ky-turn-view{position:relative;isolation:isolate;width:100%;aspect-ratio:var(--ky-turn-ratio,16/10);min-height:260px;overflow:hidden;touch-action:pan-y;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:12px;outline-offset:2px}
|
|
13
13
|
.ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
|
|
@@ -68,9 +68,26 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
68
68
|
.ky-turn .u-fascia{fill:var(--ky-up-fascia)}
|
|
69
69
|
.ky-turn .u-shell{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.6}
|
|
70
70
|
.ky-turn .u-ivy{fill:var(--ky-up-ivy)}
|
|
71
|
+
/* The buildings round a district's venues (context.ts): three tones of wall by the way each faces, and a lighter roof. */
|
|
72
|
+
.ky-turn .u-block{stroke:var(--ky-up-blk-line);stroke-width:.5}
|
|
73
|
+
.ky-turn .u-block-a{fill:var(--ky-up-blk-a)}
|
|
74
|
+
.ky-turn .u-block-b{fill:var(--ky-up-blk-b)}
|
|
75
|
+
.ky-turn .u-block-c{fill:var(--ky-up-blk-c)}
|
|
76
|
+
.ky-turn .u-slab{stroke:var(--ky-up-blk-line);stroke-width:.5}
|
|
77
|
+
.ky-turn .u-slab-a{fill:var(--ky-up-blk-a)}
|
|
78
|
+
.ky-turn .u-slab-b{fill:var(--ky-up-blk-b)}
|
|
79
|
+
.ky-turn .u-slab-c{fill:var(--ky-up-blk-c)}
|
|
80
|
+
.ky-turn .u-slab-top{fill:var(--ky-up-blk-roof);stroke:var(--ky-up-blk-line);stroke-width:.5}
|
|
81
|
+
.ky-turn .u-floorplate{fill:var(--ky-up-plate);stroke:var(--ky-up-plate-line);stroke-width:1.2;stroke-linejoin:round}
|
|
82
|
+
.ky-turn .u-block-roof{fill:var(--ky-up-blk-roof);stroke:var(--ky-up-blk-line);stroke-width:.5;stroke-linejoin:round}
|
|
71
83
|
.ky-turn .u-roof{fill:var(--ky-up-roof);stroke:var(--ky-up-roofline);stroke-width:.8;stroke-linejoin:round}
|
|
72
84
|
.ky-turn .u-signplate{fill:var(--ky-turn-bg);fill-opacity:.9;stroke:var(--ky-turn-rule);stroke-width:.5}
|
|
73
85
|
.ky-turn .u-sign{fill:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600}
|
|
86
|
+
.ky-turn .u-mast{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.4}
|
|
87
|
+
.ky-turn .u-skylight{fill:var(--ky-up-skylight,#a9c8da);fill-opacity:.9;stroke:var(--ky-up-roofline);stroke-width:.5}
|
|
88
|
+
.ky-turn .u-podium{fill:#000;fill-opacity:.16}
|
|
89
|
+
.ky-turn .u-ledge{fill:var(--ky-up-roofline)}
|
|
90
|
+
.ky-turn .u-glazing{fill:#0b1a24;fill-opacity:.32}
|
|
74
91
|
.ky-turn .u-cable{fill:none;stroke:var(--ky-up-roofline);stroke-width:1}
|
|
75
92
|
.ky-turn .u-fabric{fill:var(--ky-up-roof);fill-opacity:.55;stroke:none}
|
|
76
93
|
.ky-turn .u-pod{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.6}
|
package/dist/venue.real.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Pt, type RealShell, type StandRing } from "./crowd.ts";
|
|
2
|
-
import { type Upright } from "./turn.kit.ts";
|
|
2
|
+
import { type Plane, type Upright } from "./turn.kit.ts";
|
|
3
3
|
/** How far a lid rises from its edge towards the middle, and how far a saddle's high ends stand over its low sides, in feet (ours: the sources give a roof's kind, not its height). */
|
|
4
4
|
export declare const LID_RISE = 22;
|
|
5
5
|
export declare const SADDLE_RISE = 30;
|
|
@@ -9,6 +9,22 @@ export declare function saddleAt(real: Pick<RealShell, "footprint">, [x, y]: Pt)
|
|
|
9
9
|
export declare const roofAt: (real: RealShell, p: Pt) => number;
|
|
10
10
|
/** The point where a ray from `from` first meets a polygon, or `undefined`. */
|
|
11
11
|
export declare function firstHit(poly: readonly Pt[], from: Pt, angle: number): Pt | undefined;
|
|
12
|
+
/** How tall the glazed concourse band over a podium is, in feet (ours). */
|
|
13
|
+
export declare const GLAZING = 14;
|
|
14
|
+
/** The point of the hall's rim at an angle from the middle: where a ray meets the hall's outline or the building's, whichever it meets first. */
|
|
15
|
+
export declare function rimPoint(real: Pick<RealShell, "hall" | "footprint">, c: Pt, angle: number): Pt;
|
|
16
|
+
/** How far a mast leans out from the vertical, in degrees (ours: the sources give the count and the height of BC Place's masts, and the photographs of the building show them leaning out from the rim). */
|
|
17
|
+
export declare const MAST_LEAN = 12;
|
|
18
|
+
/** How thick a mast is drawn, in feet (ours). */
|
|
19
|
+
export declare const MAST_WIDTH = 6;
|
|
20
|
+
/**
|
|
21
|
+
* The masts of a cable roof: `count` of them round the rim, each rising from the rim at the roof's edge, `height` feet, leaning out from the middle. A mast is two thin strips crossed, one in the plane of the slant and the
|
|
22
|
+
* radius and one in the plane of the slant and the tangent, so that it is seen from every side (as a post is). The count and the height are the sources'; the lean and the thickness are ours.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mastPlanes(real: Pick<RealShell, "hall" | "footprint">, c: Pt, z: number, masts: {
|
|
25
|
+
count: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}): Plane[];
|
|
12
28
|
/**
|
|
13
29
|
* The walls and the roof of a real venue (`parts` says which are made). The hall is the building's outline cut to the bowl's wall and a margin (`RealShell.hall`): its rim is where a ray from the middle meets the one or the other first, in `steps`
|
|
14
30
|
* rays. A wall stands on each stretch of the rim, up to the roof's height over it; the roof is a band from the rim in to the inner edge of the top tier (the middle stays open, as the 3D view's roofs are).
|
|
@@ -17,6 +33,11 @@ export declare function realBuilding(real: RealShell, top: StandRing | undefined
|
|
|
17
33
|
walls: boolean;
|
|
18
34
|
roof: boolean;
|
|
19
35
|
}): Items;
|
|
36
|
+
/** How far a skylight cap stands over the inner edge of the roof's band, and how wide the skylight bar is, in feet (ours: the sources do not say how high the roof closes or how wide the glass is). */
|
|
37
|
+
export declare const CAP_RISE = 7;
|
|
38
|
+
export declare const SKYLIGHT_WIDTH = 14;
|
|
39
|
+
/** The share of the opening's length the skylight bar runs over, from the middle each way. */
|
|
40
|
+
export declare const SKYLIGHT_SHARE = 0.78;
|
|
20
41
|
/** How far a canopy's low edge, the field's side of it, stands under its high edge: the OpenStreetMap roof's `roof:height` is the rise, and the canopy rises to the back of the stands (ours: the sources do not say which way it slopes). */
|
|
21
42
|
export declare const CANOPY_NOTE = "low over the field, high over the back of the stands";
|
|
22
43
|
/** How many rings of pieces an air-supported roof is drawn in, from the rim in to the crown (ours: the roof is smooth, the drawing is faceted). */
|
package/dist/venue.real.js
CHANGED
|
@@ -53,6 +53,46 @@ function slopeThrough(points) {
|
|
|
53
53
|
const [gx, gy] = Math.abs(det) < 1e-9 ? [0, 0] : [(sxz * syy - syz * sxy) / det, (syz * sxx - sxz * sxy) / det];
|
|
54
54
|
return { x0: mx, y0: my, z0: mz, gx, gy };
|
|
55
55
|
}
|
|
56
|
+
/** How tall the glazed concourse band over a podium is, in feet (ours). */
|
|
57
|
+
export const GLAZING = 14;
|
|
58
|
+
/** A wall's two bands: the podium at its foot (a shade darker), a ledge over it and a band of glazing over the ledge, on the wall's own plane (its y runs down from the top). */
|
|
59
|
+
function podiumBands(long, high, podium) {
|
|
60
|
+
const top = Math.min(podium, high * 0.6);
|
|
61
|
+
const glaze = Math.min(GLAZING, Math.max(0, high - top - 4));
|
|
62
|
+
return `<rect class="u-podium" y="${n(high - top)}" width="${n(long)}" height="${n(top)}"/><rect class="u-ledge" y="${n(high - top - 1.2)}" width="${n(long)}" height="1.2"/>${glaze > 2 ? `<rect class="u-glazing" y="${n(high - top - 1.2 - glaze)}" width="${n(long)}" height="${n(glaze)}"/>` : ""}`;
|
|
63
|
+
}
|
|
64
|
+
/** The point of the hall's rim at an angle from the middle: where a ray meets the hall's outline or the building's, whichever it meets first. */
|
|
65
|
+
export function rimPoint(real, c, angle) {
|
|
66
|
+
const cap = rayAt(real.hall, c[0], c[1], angle);
|
|
67
|
+
const out = firstHit(real.footprint, c, angle);
|
|
68
|
+
return out !== undefined && Math.hypot(out[0] - c[0], out[1] - c[1]) < Math.hypot(cap[0] - c[0], cap[1] - c[1]) ? out : cap;
|
|
69
|
+
}
|
|
70
|
+
/** How far a mast leans out from the vertical, in degrees (ours: the sources give the count and the height of BC Place's masts, and the photographs of the building show them leaning out from the rim). */
|
|
71
|
+
export const MAST_LEAN = 12;
|
|
72
|
+
/** How thick a mast is drawn, in feet (ours). */
|
|
73
|
+
export const MAST_WIDTH = 6;
|
|
74
|
+
/**
|
|
75
|
+
* The masts of a cable roof: `count` of them round the rim, each rising from the rim at the roof's edge, `height` feet, leaning out from the middle. A mast is two thin strips crossed, one in the plane of the slant and the
|
|
76
|
+
* radius and one in the plane of the slant and the tangent, so that it is seen from every side (as a post is). The count and the height are the sources'; the lean and the thickness are ours.
|
|
77
|
+
*/
|
|
78
|
+
export function mastPlanes(real, c, z, masts) {
|
|
79
|
+
const planes = [];
|
|
80
|
+
const lean = (MAST_LEAN * Math.PI) / 180;
|
|
81
|
+
for (let i = 0; i < masts.count; i += 1) {
|
|
82
|
+
const angle = (2 * Math.PI * (i + 0.5)) / masts.count;
|
|
83
|
+
const [bx, by] = rimPoint(real, c, angle);
|
|
84
|
+
const [rx, ry] = [Math.cos(angle), Math.sin(angle)];
|
|
85
|
+
const [tx, ty] = [-ry, rx];
|
|
86
|
+
const slant = [rx * masts.height * Math.sin(lean), ry * masts.height * Math.sin(lean), masts.height * Math.cos(lean)];
|
|
87
|
+
const long = Math.hypot(...slant);
|
|
88
|
+
const half = MAST_WIDTH / 2;
|
|
89
|
+
const svg = `<polygon class="u-mast" points="0 ${n(half)} ${n(long)} ${n(half * 0.45)} ${n(long)} ${n(half * 1.55)}"/>`;
|
|
90
|
+
// A strip along the slant, across the tangent; and one along the slant, across the radius.
|
|
91
|
+
planes.push({ id: `mast-${i}-a`, kind: "mast", o: [bx - tx * half, by - ty * half, z], u: slant, v: [tx * MAST_WIDTH, ty * MAST_WIDTH, 0], svg });
|
|
92
|
+
planes.push({ id: `mast-${i}-b`, kind: "mast", o: [bx - rx * half, by - ry * half, z], u: slant, v: [rx * MAST_WIDTH, ry * MAST_WIDTH, 0], svg });
|
|
93
|
+
}
|
|
94
|
+
return planes;
|
|
95
|
+
}
|
|
56
96
|
/**
|
|
57
97
|
* The walls and the roof of a real venue (`parts` says which are made). The hall is the building's outline cut to the bowl's wall and a margin (`RealShell.hall`): its rim is where a ray from the middle meets the one or the other first, in `steps`
|
|
58
98
|
* rays. A wall stands on each stretch of the rim, up to the roof's height over it; the roof is a band from the rim in to the inner edge of the top tier (the middle stays open, as the 3D view's roofs are).
|
|
@@ -60,12 +100,7 @@ function slopeThrough(points) {
|
|
|
60
100
|
export function realBuilding(real, top, centre, lift, steps, open = false, parts = { walls: true, roof: true }) {
|
|
61
101
|
const items = [];
|
|
62
102
|
const c = [centre[0], centre[1]];
|
|
63
|
-
const rim = Array.from({ length: steps }, (_, k) =>
|
|
64
|
-
const angle = (2 * Math.PI * k) / steps;
|
|
65
|
-
const cap = rayAt(real.hall, c[0], c[1], angle);
|
|
66
|
-
const out = firstHit(real.footprint, c, angle);
|
|
67
|
-
return out !== undefined && Math.hypot(out[0] - c[0], out[1] - c[1]) < Math.hypot(cap[0] - c[0], cap[1] - c[1]) ? out : cap;
|
|
68
|
-
});
|
|
103
|
+
const rim = Array.from({ length: steps }, (_, k) => rimPoint(real, c, (2 * Math.PI * k) / steps));
|
|
69
104
|
const planes = [];
|
|
70
105
|
rim.forEach((a, i) => {
|
|
71
106
|
const b = rim[(i + 1) % steps];
|
|
@@ -73,7 +108,7 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
73
108
|
if (long < 1)
|
|
74
109
|
return;
|
|
75
110
|
const high = lift + (real.wall ?? roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
|
|
76
|
-
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : "u-shell"}" width="${n(long)}" height="${n(high)}"
|
|
111
|
+
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : "u-shell"}" width="${n(long)}" height="${n(high)}"/>${real.podium === undefined ? "" : podiumBands(long, high, real.podium)}`), centre), fadeTo: 0.02 });
|
|
77
112
|
});
|
|
78
113
|
if (parts.walls && planes.length > 0)
|
|
79
114
|
items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
|
|
@@ -127,12 +162,46 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
127
162
|
if (piece !== undefined)
|
|
128
163
|
roof.push({ ...piece, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] });
|
|
129
164
|
}
|
|
165
|
+
if (crown === undefined && real.skylight === true)
|
|
166
|
+
roof.push(...skylightCap(inner, c, lift + real.edge + rise));
|
|
130
167
|
if (crown !== undefined)
|
|
131
168
|
roof.push(...cablePod(lift + real.edge + crown.rise, inner, c, steps, open));
|
|
169
|
+
if (crown !== undefined && real.masts !== undefined)
|
|
170
|
+
roof.push(...mastPlanes(real, c, lift + real.edge, real.masts));
|
|
132
171
|
if (roof.length > 0)
|
|
133
172
|
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(roof), planes: roof });
|
|
134
173
|
return items;
|
|
135
174
|
}
|
|
175
|
+
/** How far a skylight cap stands over the inner edge of the roof's band, and how wide the skylight bar is, in feet (ours: the sources do not say how high the roof closes or how wide the glass is). */
|
|
176
|
+
export const CAP_RISE = 7;
|
|
177
|
+
export const SKYLIGHT_WIDTH = 14;
|
|
178
|
+
/** The share of the opening's length the skylight bar runs over, from the middle each way. */
|
|
179
|
+
export const SKYLIGHT_SHARE = 0.78;
|
|
180
|
+
/**
|
|
181
|
+
* Close the roof's middle: the opening the band leaves is covered at `z` by a flat cap raised `CAP_RISE` over the band's inner edge, with a short skirt joining the two, and over the middle of the cap along the long axis lies a long
|
|
182
|
+
* skylight bar of glass (the roof of Rogers Arena has one; the surface's long side lies along x in the drawing's frame, so the bar runs along x). The shape and the sizes are ours.
|
|
183
|
+
*/
|
|
184
|
+
function skylightCap(hole, c, z) {
|
|
185
|
+
const planes = [];
|
|
186
|
+
const top = z + CAP_RISE;
|
|
187
|
+
const cap = raked("cap", "roof", hole, { x0: c[0], y0: c[1], z0: top, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>`);
|
|
188
|
+
if (cap !== undefined)
|
|
189
|
+
planes.push(cap);
|
|
190
|
+
hole.forEach((a, k) => {
|
|
191
|
+
const b = hole[(k + 1) % hole.length];
|
|
192
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
193
|
+
if (long < 0.5)
|
|
194
|
+
return;
|
|
195
|
+
planes.push({ ...shy(wall(`skirt-${k}`, "roof", a[0], a[1], b[0], b[1], z, top, `<rect class="u-roof" width="${n(long)}" height="${n(CAP_RISE)}"/>`), c), fadeTo: 0.3 });
|
|
196
|
+
});
|
|
197
|
+
const xs = hole.map((p) => p[0]);
|
|
198
|
+
const half = ((Math.max(...xs) - Math.min(...xs)) / 2) * SKYLIGHT_SHARE;
|
|
199
|
+
const bar = [[c[0] - half, c[1] - SKYLIGHT_WIDTH / 2], [c[0] + half, c[1] - SKYLIGHT_WIDTH / 2], [c[0] + half, c[1] + SKYLIGHT_WIDTH / 2], [c[0] - half, c[1] + SKYLIGHT_WIDTH / 2]];
|
|
200
|
+
const glass = raked("skylight", "roof", bar, { x0: c[0], y0: c[1], z0: top + 0.4, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-skylight" points="${pointList(corners)}"/>`);
|
|
201
|
+
if (glass !== undefined)
|
|
202
|
+
planes.push(glass);
|
|
203
|
+
return planes;
|
|
204
|
+
}
|
|
136
205
|
/** The top of a set of planes. */
|
|
137
206
|
const heightOf = (planes) => Math.max(...planes.flatMap((p) => [p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]]));
|
|
138
207
|
/** The two points where a ray from `from` first enters a polygon and last leaves it (the polygon is a band round the middle, so a ray crosses it once), or `undefined` where it misses. */
|