@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
|
@@ -5,6 +5,7 @@ export const STADIUM_OSM = {
|
|
|
5
5
|
"base": "2026-10-20T21:46:16Z",
|
|
6
6
|
"licence": "ODbL-1.0",
|
|
7
7
|
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
8
|
+
"height": 61,
|
|
8
9
|
"axis": { "heading": 52.8, "basis": "opening", "strength": 1.47, "elongation": 1.19, "centre": [-123.112002, 49.276698] },
|
|
9
10
|
"roof": {
|
|
10
11
|
"source": "relation/5836463",
|
|
@@ -55,4 +56,117 @@ export const STADIUM_OSM = {
|
|
|
55
56
|
[-123.113623, 49.276478],
|
|
56
57
|
],
|
|
57
58
|
},
|
|
59
|
+
"lumen-field": {
|
|
60
|
+
"source": "relation/2305831",
|
|
61
|
+
"read": "2026-10-20",
|
|
62
|
+
"base": "2026-10-20T23:05:49Z",
|
|
63
|
+
"licence": "ODbL-1.0",
|
|
64
|
+
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
65
|
+
"axis": { "heading": 0, "basis": "pitch", "strength": 1.32, "elongation": 2.16, "centre": [-122.33163, 47.595154] },
|
|
66
|
+
"canopies": [
|
|
67
|
+
{ "source": "way/458208106", "height": 36, "rise": 10, "ring": [
|
|
68
|
+
[-122.332395, 47.596154],
|
|
69
|
+
[-122.33234, 47.595145],
|
|
70
|
+
[-122.332404, 47.594143],
|
|
71
|
+
[-122.332683, 47.594276],
|
|
72
|
+
[-122.332869, 47.594407],
|
|
73
|
+
[-122.333005, 47.594605],
|
|
74
|
+
[-122.333075, 47.594764],
|
|
75
|
+
[-122.333159, 47.595095],
|
|
76
|
+
[-122.33313, 47.59539],
|
|
77
|
+
[-122.332993, 47.595744],
|
|
78
|
+
[-122.332861, 47.59592],
|
|
79
|
+
[-122.332747, 47.596004],
|
|
80
|
+
[-122.332452, 47.596154],
|
|
81
|
+
] },
|
|
82
|
+
{ "source": "way/458208107", "height": 36, "rise": 10, "ring": [
|
|
83
|
+
[-122.330897, 47.596155],
|
|
84
|
+
[-122.330837, 47.596159],
|
|
85
|
+
[-122.33062, 47.596055],
|
|
86
|
+
[-122.330448, 47.595948],
|
|
87
|
+
[-122.330298, 47.595762],
|
|
88
|
+
[-122.330117, 47.5953],
|
|
89
|
+
[-122.330116, 47.59501],
|
|
90
|
+
[-122.330212, 47.594711],
|
|
91
|
+
[-122.33041, 47.594391],
|
|
92
|
+
[-122.330582, 47.594279],
|
|
93
|
+
[-122.330872, 47.59415],
|
|
94
|
+
[-122.330942, 47.595174],
|
|
95
|
+
[-122.330936, 47.595579],
|
|
96
|
+
] },
|
|
97
|
+
],
|
|
98
|
+
"ring": [
|
|
99
|
+
[-122.332249, 47.59627],
|
|
100
|
+
[-122.332463, 47.596203],
|
|
101
|
+
[-122.332661, 47.596118],
|
|
102
|
+
[-122.33284, 47.596015],
|
|
103
|
+
[-122.332996, 47.595897],
|
|
104
|
+
[-122.333126, 47.595765],
|
|
105
|
+
[-122.333162, 47.595677],
|
|
106
|
+
[-122.333156, 47.594577],
|
|
107
|
+
[-122.333041, 47.594455],
|
|
108
|
+
[-122.332904, 47.594343],
|
|
109
|
+
[-122.332747, 47.594244],
|
|
110
|
+
[-122.332574, 47.594159],
|
|
111
|
+
[-122.332187, 47.594034],
|
|
112
|
+
[-122.331979, 47.593997],
|
|
113
|
+
[-122.331766, 47.593978],
|
|
114
|
+
[-122.331338, 47.593994],
|
|
115
|
+
[-122.331311, 47.594047],
|
|
116
|
+
[-122.331129, 47.594028],
|
|
117
|
+
[-122.330739, 47.594148],
|
|
118
|
+
[-122.330405, 47.594329],
|
|
119
|
+
[-122.330265, 47.594439],
|
|
120
|
+
[-122.330166, 47.594722],
|
|
121
|
+
[-122.330125, 47.59496],
|
|
122
|
+
[-122.330117, 47.5953],
|
|
123
|
+
[-122.330168, 47.595589],
|
|
124
|
+
[-122.330269, 47.59587],
|
|
125
|
+
[-122.330506, 47.596048],
|
|
126
|
+
[-122.330646, 47.596124],
|
|
127
|
+
[-122.331021, 47.596263],
|
|
128
|
+
[-122.331021, 47.596093],
|
|
129
|
+
[-122.331134, 47.596093],
|
|
130
|
+
[-122.331227, 47.596281],
|
|
131
|
+
[-122.332031, 47.596283],
|
|
132
|
+
[-122.332131, 47.596089],
|
|
133
|
+
[-122.33225, 47.596089],
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
"t-mobile-park": {
|
|
137
|
+
"source": "relation/3008332",
|
|
138
|
+
"read": "2026-10-20",
|
|
139
|
+
"base": "2026-10-20T23:06:50Z",
|
|
140
|
+
"licence": "ODbL-1.0",
|
|
141
|
+
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
142
|
+
"height": 21,
|
|
143
|
+
"axis": { "heading": 49.4, "basis": "infield", "strength": 1.43, "elongation": 1, "centre": [-122.332905, 47.591117] },
|
|
144
|
+
"ring": [
|
|
145
|
+
[-122.330843, 47.59228],
|
|
146
|
+
[-122.331382, 47.590448],
|
|
147
|
+
[-122.333677, 47.590452],
|
|
148
|
+
[-122.333678, 47.59047],
|
|
149
|
+
[-122.333819, 47.590456],
|
|
150
|
+
[-122.333945, 47.5905],
|
|
151
|
+
[-122.333997, 47.590554],
|
|
152
|
+
[-122.334013, 47.590618],
|
|
153
|
+
[-122.333989, 47.590681],
|
|
154
|
+
[-122.33393, 47.59073],
|
|
155
|
+
[-122.333978, 47.59073],
|
|
156
|
+
[-122.333975, 47.59216],
|
|
157
|
+
[-122.333739, 47.59216],
|
|
158
|
+
[-122.33374, 47.591759],
|
|
159
|
+
[-122.333776, 47.591759],
|
|
160
|
+
[-122.333776, 47.591748],
|
|
161
|
+
[-122.333683, 47.591723],
|
|
162
|
+
[-122.333451, 47.592099],
|
|
163
|
+
[-122.333388, 47.592119],
|
|
164
|
+
[-122.333409, 47.592138],
|
|
165
|
+
[-122.333293, 47.5922],
|
|
166
|
+
[-122.333231, 47.592154],
|
|
167
|
+
[-122.333131, 47.592213],
|
|
168
|
+
[-122.333256, 47.592215],
|
|
169
|
+
[-122.333251, 47.592271],
|
|
170
|
+
],
|
|
171
|
+
},
|
|
58
172
|
};
|
package/dist/strings.d.ts
CHANGED
|
@@ -516,6 +516,10 @@ export interface KyogijoWords {
|
|
|
516
516
|
stadiumOpening: string;
|
|
517
517
|
stadiumBuilding: string;
|
|
518
518
|
stadiumNone: string;
|
|
519
|
+
/** A ballpark's stands: {seats} seats fitted inside the outline of {stadium}'s building; and how the pitch or the infield says the field lies. */
|
|
520
|
+
stadiumPark: string;
|
|
521
|
+
stadiumPitch: string;
|
|
522
|
+
stadiumInfield: string;
|
|
519
523
|
/** A district: a plan of {count} real venues at their real positions, {mode}; and what `mode` says for the two kinds of plan, and the word before the credit. */
|
|
520
524
|
district: string;
|
|
521
525
|
districtNeighbours: 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.", 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.", 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: " " },
|
|
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}",
|
|
@@ -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: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", 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: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", 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: "" },
|
|
762
762
|
budo: {
|
|
763
763
|
sides: { north: "北", south: "南", east: "東", west: "西" },
|
|
764
764
|
judo: "柔道の試合場。試合場内は{contest}m四方、幅{safety}mの安全地帯があり、{tatami}の畳を敷いています",
|
package/dist/venue.js
CHANGED
|
@@ -116,16 +116,16 @@ export function standsVenueOf(stands, crowd, o, v, lift) {
|
|
|
116
116
|
}
|
|
117
117
|
// The low wall under the first row of a tier the walks do not lead to (a ballpark's wall seats stand on the fence, which the fence's own planes make).
|
|
118
118
|
if (ring.tier !== "wall" && !chained.has(ring) && ring.height[0] > 0.5) {
|
|
119
|
-
|
|
119
|
+
strips.forEach((strip, si) => {
|
|
120
120
|
for (let i = 0; i + 1 < strip.inner.length; i += 1) {
|
|
121
121
|
const [a, b] = [strip.inner[i], strip.inner[i + 1]];
|
|
122
122
|
if (Math.hypot(b[0] - a[0], b[1] - a[1]) < 0.5 || inCover([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]))
|
|
123
123
|
continue;
|
|
124
124
|
const h = lift + ring.height[0];
|
|
125
|
-
const w = wall(`${ring.id}-front-${i}`, "fascia", a[0], a[1], b[0], b[1], 0, h, `<rect class="u-fascia" width="${n(Math.hypot(b[0] - a[0], b[1] - a[1]))}" height="${n(h)}"/>`);
|
|
125
|
+
const w = wall(`${ring.id}-front-${si === 0 ? "" : `s${si}-`}${i}`, "fascia", a[0], a[1], b[0], b[1], 0, h, `<rect class="u-fascia" width="${n(Math.hypot(b[0] - a[0], b[1] - a[1]))}" height="${n(h)}"/>`);
|
|
126
126
|
seatPlanes.push(shy(w, centre));
|
|
127
127
|
}
|
|
128
|
-
}
|
|
128
|
+
});
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
// The walks between the tiers: a plane from the outer edge of one tier up to the first row of the next.
|
package/dist/venue.real.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare function realBuilding(real: RealShell, top: StandRing | undefined
|
|
|
15
15
|
walls: boolean;
|
|
16
16
|
roof: boolean;
|
|
17
17
|
}): Items;
|
|
18
|
+
/** 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). */
|
|
19
|
+
export declare const CANOPY_NOTE = "low over the field, high over the back of the stands";
|
|
18
20
|
/** The pod in the middle of a cable roof's opening, as a share of the opening's size, and how thick it is, in feet (ours: the article says the fabric gathers into a pod above the video board, not how large it is). */
|
|
19
21
|
export declare const POD: {
|
|
20
22
|
readonly share: 0.24;
|
package/dist/venue.real.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The building of a real venue, raised: its walls, standing on the outline OpenStreetMap gives, and its roof over the top tier, a lid that rises towards the middle, a saddle that rises at the two ends and falls at the two
|
|
2
|
-
// sides,
|
|
2
|
+
// sides, a cable-supported roof that rises to an opening over the surface which a retractable fabric closes (BC Place's, with the fabric gathered into its pod when `roofOpen` is asked), canopies over some of the
|
|
3
|
+
// stands that leave the field open (Lumen Field's), or retractable panels high over a ballpark (T-Mobile Park's, resting over its east side when `roofOpen` is asked). All are asked for with the roof
|
|
3
4
|
// (`venue: { roof: true }`) and give way to the eye as the other walls do. Pure: data in, planes out.
|
|
4
5
|
import { rayAt } from "./crowd.js";
|
|
5
6
|
import { n, shy, wall } from "./turn.kit.js";
|
|
@@ -70,11 +71,23 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
70
71
|
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
71
72
|
if (long < 1)
|
|
72
73
|
return;
|
|
73
|
-
const high = lift + roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
|
|
74
|
+
const high = lift + (real.wall ?? roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
|
|
74
75
|
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="u-shell" width="${n(long)}" height="${n(high)}"/>`), centre), fadeTo: 0.02 });
|
|
75
76
|
});
|
|
76
77
|
if (parts.walls && planes.length > 0)
|
|
77
78
|
items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
|
|
79
|
+
if (parts.roof && real.roof === "canopy" && real.canopies !== undefined) {
|
|
80
|
+
const planes = canopyRoof(real.canopies, c, lift, steps);
|
|
81
|
+
if (planes.length > 0)
|
|
82
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
83
|
+
return items;
|
|
84
|
+
}
|
|
85
|
+
if (parts.roof && real.roof === "panels" && real.panels !== undefined) {
|
|
86
|
+
const planes = panelsRoof(real.panels, c, lift, steps, open);
|
|
87
|
+
if (planes.length > 0)
|
|
88
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
89
|
+
return items;
|
|
90
|
+
}
|
|
78
91
|
const crown = real.roof === "cable" ? real.crown : undefined;
|
|
79
92
|
if (!parts.roof || (top?.hole == null && crown === undefined))
|
|
80
93
|
return items;
|
|
@@ -105,20 +118,121 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
105
118
|
roof.push({ ...piece, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] });
|
|
106
119
|
}
|
|
107
120
|
if (crown !== undefined)
|
|
108
|
-
roof.push(...cablePod(real
|
|
121
|
+
roof.push(...cablePod(lift + real.edge + crown.rise, inner, c, steps, open));
|
|
109
122
|
if (roof.length > 0)
|
|
110
|
-
items.push({ id: "roof", kind: "roof", at: centre, height:
|
|
123
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(roof), planes: roof });
|
|
111
124
|
return items;
|
|
112
125
|
}
|
|
126
|
+
/** The top of a set of planes. */
|
|
127
|
+
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]]));
|
|
128
|
+
/** 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. */
|
|
129
|
+
function span(poly, from, angle) {
|
|
130
|
+
const [dx, dy] = [Math.cos(angle), Math.sin(angle)];
|
|
131
|
+
const hits = [];
|
|
132
|
+
poly.forEach(([ax, ay], i) => {
|
|
133
|
+
const [bx, by] = poly[(i + 1) % poly.length];
|
|
134
|
+
const [ex, ey] = [bx - ax, by - ay];
|
|
135
|
+
const den = dx * ey - dy * ex;
|
|
136
|
+
if (Math.abs(den) < 1e-12)
|
|
137
|
+
return;
|
|
138
|
+
const t = ((ax - from[0]) * ey - (ay - from[1]) * ex) / den;
|
|
139
|
+
const u = ((ax - from[0]) * dy - (ay - from[1]) * dx) / den;
|
|
140
|
+
if (t > 1e-6 && u >= 0 && u < 1)
|
|
141
|
+
hits.push(t);
|
|
142
|
+
});
|
|
143
|
+
if (hits.length < 2)
|
|
144
|
+
return undefined;
|
|
145
|
+
const [lo, hi] = [Math.min(...hits), Math.max(...hits)];
|
|
146
|
+
return [[from[0] + dx * lo, from[1] + dy * lo], [from[0] + dx * hi, from[1] + dy * hi]];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A piece of roof between two rays: the quad `[inner a, inner b, outer b, outer a]` at the heights given, as a plane that gives way to the eye. A quad whose edges are not parallel is not flat at those heights, so
|
|
150
|
+
* with `exact` it is two triangles, each a plane through its corners.
|
|
151
|
+
*/
|
|
152
|
+
function piece(id, quad, heights, c, cables, exact = false) {
|
|
153
|
+
const [ai, bi, b, a] = quad;
|
|
154
|
+
const [ex, ey] = [b[0] - a[0], b[1] - a[1]];
|
|
155
|
+
const el = Math.hypot(ex, ey);
|
|
156
|
+
if (el < 1e-6)
|
|
157
|
+
return [];
|
|
158
|
+
const make = (name, poly, hs, cable) => {
|
|
159
|
+
const slope = slopeThrough(poly.map((p, i) => [p[0], p[1], hs[i]]));
|
|
160
|
+
const mid = [poly.reduce((s, p) => s + p[0], 0) / poly.length, poly.reduce((s, p) => s + p[1], 0) / poly.length];
|
|
161
|
+
const away = Math.hypot(mid[0] - c[0], mid[1] - c[1]) || 1;
|
|
162
|
+
const paint = (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>${cable ? `<path class="u-cable" d="M${n(corners[0][0])} ${n(corners[0][1])}L${n(corners[3][0])} ${n(corners[3][1])}"/>` : ""}`;
|
|
163
|
+
const plane = raked(name, "roof", poly, slope, [ex / el, ey / el], paint);
|
|
164
|
+
return plane === undefined ? [] : [{ ...plane, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] }];
|
|
165
|
+
};
|
|
166
|
+
if (!exact)
|
|
167
|
+
return make(id, quad, heights, cables);
|
|
168
|
+
return [...make(`${id}a`, [ai, bi, b], [heights[0], heights[1], heights[2]], false), ...make(`${id}b`, [ai, b, a], [heights[0], heights[2], heights[3]], false)];
|
|
169
|
+
}
|
|
170
|
+
/** 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). */
|
|
171
|
+
export const CANOPY_NOTE = "low over the field, high over the back of the stands";
|
|
172
|
+
/**
|
|
173
|
+
* Canopies over some of the stands (Lumen Field's two, one each side of the field): each OpenStreetMap roof outline is cut by the rays from the middle of the field into the strips between them, the edge towards
|
|
174
|
+
* the field low and the edge towards the back of the stands `rise` higher.
|
|
175
|
+
*/
|
|
176
|
+
function canopyRoof(canopies, c, lift, steps) {
|
|
177
|
+
const planes = [];
|
|
178
|
+
canopies.forEach((canopy, ci) => {
|
|
179
|
+
const spans = Array.from({ length: steps }, (_, k) => span(canopy.ring, c, (2 * Math.PI * k) / steps));
|
|
180
|
+
for (let k = 0; k < steps; k += 1) {
|
|
181
|
+
const [s, t] = [spans[k], spans[(k + 1) % steps]];
|
|
182
|
+
if (s === undefined || t === undefined)
|
|
183
|
+
continue;
|
|
184
|
+
const [lo, hi] = [lift + canopy.low, lift + canopy.low + canopy.rise];
|
|
185
|
+
planes.push(...piece(`roof-c${ci}-${k}`, [s[0], t[0], t[1], s[1]], [lo, lo, hi, hi], c, false, true));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return planes;
|
|
189
|
+
}
|
|
190
|
+
/** The share of a retractable roof's cover that the closed roof's flat crown takes (the sections meet in the middle at the crown's height). */
|
|
191
|
+
const PANEL_CROWN = 0.3;
|
|
192
|
+
/** How thick a parked section is, in feet (ours). */
|
|
193
|
+
const PANEL_THICK = 7;
|
|
194
|
+
/**
|
|
195
|
+
* A ballpark's retractable roof: closed, a band from the rim of its cover up to a crown over the middle with the fabric laid over the crown (as the cable roof's); open, only the sections resting over the side of the
|
|
196
|
+
* building they park on, as slabs at the rim's height.
|
|
197
|
+
*/
|
|
198
|
+
function panelsRoof(panels, c, lift, steps, open) {
|
|
199
|
+
const planes = [];
|
|
200
|
+
if (!open) {
|
|
201
|
+
const rim = Array.from({ length: steps }, (_, k) => firstHit(panels.cover, c, (2 * Math.PI * k) / steps) ?? c);
|
|
202
|
+
const inner = rim.map((p) => [c[0] + (p[0] - c[0]) * PANEL_CROWN, c[1] + (p[1] - c[1]) * PANEL_CROWN]);
|
|
203
|
+
for (let k = 0; k < steps; k += 1) {
|
|
204
|
+
const [a, b] = [rim[k], rim[(k + 1) % steps]];
|
|
205
|
+
const [ai, bi] = [inner[k], inner[(k + 1) % steps]];
|
|
206
|
+
const [lo, hi] = [lift + panels.bottom, lift + panels.top];
|
|
207
|
+
planes.push(...piece(`roof-${k}`, [ai, bi, b, a], [hi, hi, lo, lo], c, false, true));
|
|
208
|
+
}
|
|
209
|
+
// The crown itself: a flat cap over the middle, in the fabric's drawing.
|
|
210
|
+
planes.push(...cablePod(lift + panels.top, inner, c, steps, false));
|
|
211
|
+
return planes;
|
|
212
|
+
}
|
|
213
|
+
// Open: the parked sections as slabs.
|
|
214
|
+
const z = lift + panels.bottom;
|
|
215
|
+
const top = z + PANEL_THICK;
|
|
216
|
+
const slab = raked("parked-top", "roof", panels.parked, { x0: panels.parked[0][0], y0: panels.parked[0][1], z0: top, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-pod" points="${pointList(corners)}"/>`);
|
|
217
|
+
if (slab !== undefined)
|
|
218
|
+
planes.push({ ...slab, id: "parked-top" });
|
|
219
|
+
panels.parked.forEach((a, i) => {
|
|
220
|
+
const b = panels.parked[(i + 1) % panels.parked.length];
|
|
221
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
222
|
+
if (long < 0.5)
|
|
223
|
+
return;
|
|
224
|
+
planes.push({ ...shy(wall(`parked-${i}`, "roof", a[0], a[1], b[0], b[1], z, top, `<rect class="u-pod" width="${n(long)}" height="${n(PANEL_THICK)}"/>`), c), fadeTo: 0.3 });
|
|
225
|
+
});
|
|
226
|
+
return planes;
|
|
227
|
+
}
|
|
113
228
|
/** The pod in the middle of a cable roof's opening, as a share of the opening's size, and how thick it is, in feet (ours: the article says the fabric gathers into a pod above the video board, not how large it is). */
|
|
114
229
|
export const POD = { share: 0.24, thick: 9 };
|
|
115
230
|
/**
|
|
116
231
|
* What lies over the opening of a cable roof: the pod in the middle (a box hung at the crown's height) and, with the roof closed, the fabric between the pod and the opening's edge, drawn thin so that the seats
|
|
117
232
|
* show through it. Open, only the pod is there: the fabric has gathered into it.
|
|
118
233
|
*/
|
|
119
|
-
function cablePod(
|
|
234
|
+
function cablePod(z, edge, c, steps, open) {
|
|
120
235
|
const planes = [];
|
|
121
|
-
const z = lift + real.edge + crown.rise;
|
|
122
236
|
const pod = edge.map((p) => [c[0] + (p[0] - c[0]) * POD.share, c[1] + (p[1] - c[1]) * POD.share]);
|
|
123
237
|
if (!open) {
|
|
124
238
|
for (let k = 0; k < steps; k += 1) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.31.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.31.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kyogijo-stadiums",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Kyogijo Stadiums 競技場, 40 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, the MMA cage, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sports",
|
|
@@ -292,8 +292,9 @@
|
|
|
292
292
|
"prepublishOnly": "pnpm check",
|
|
293
293
|
"site": "pnpm build && node scripts/site.mjs",
|
|
294
294
|
"test:demo": "pnpm site && playwright test",
|
|
295
|
-
"screenshots:readme": "pnpm site && node scripts/readme-pictures.mjs && node scripts/readme-animation.mjs",
|
|
295
|
+
"screenshots:readme": "pnpm site && node scripts/readme-pictures.mjs && node scripts/readme-animation.mjs && node scripts/readme-flythrough.mjs",
|
|
296
296
|
"screenshots:animation": "pnpm site && node scripts/readme-animation.mjs",
|
|
297
|
+
"screenshots:flythrough": "pnpm site && node scripts/readme-flythrough.mjs",
|
|
297
298
|
"test:readme": "pnpm build && node scripts/check-readme-examples.mjs",
|
|
298
299
|
"docs:make": "node scripts/docs.mjs",
|
|
299
300
|
"parks": "node scripts/build-parks.mjs",
|