@johnmorrisdotca/kyogijo-stadiums 0.44.1 → 0.45.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 +76 -0
- package/dist/areas-entry.d.ts +2 -0
- package/dist/areas-entry.js +2 -0
- package/dist/areas.d.ts +46 -0
- package/dist/areas.js +87 -0
- package/dist/cityctx.d.ts +3 -6
- package/dist/cityctx.data.d.ts +5 -8
- package/dist/cityctx.data.js +5 -7137
- package/dist/cityctx.js +3 -6
- package/dist/cityctx.seattle-center.data.d.ts +3 -0
- package/dist/cityctx.seattle-center.data.js +1883 -0
- package/dist/cityctx.seattle-city.data.d.ts +3 -0
- package/dist/cityctx.seattle-city.data.js +3778 -0
- package/dist/cityctx.seattle-sodo.data.d.ts +3 -0
- package/dist/cityctx.seattle-sodo.data.js +1419 -0
- package/dist/cityctx.vancouver-nature.data.d.ts +3 -0
- package/dist/cityctx.vancouver-nature.data.js +57 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +20 -1
- package/dist/district.turn.js +61 -6
- package/dist/judging.mount.js +7 -0
- package/dist/outside.dom.d.ts +5 -0
- package/dist/outside.dom.js +13 -5
- package/dist/quality.d.ts +110 -0
- package/dist/quality.js +160 -0
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/turn.camera.js +17 -7
- package/dist/turn.dom.d.ts +7 -0
- package/dist/turn.dom.js +198 -20
- package/dist/turn.kit.d.ts +5 -0
- package/dist/turn.markup.d.ts +16 -8
- package/dist/turn.markup.js +99 -20
- package/dist/turn.model.d.ts +13 -0
- package/dist/turn.style.js +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ContextArea } from "./context.ts";
|
|
2
|
+
/** Vancouver: water, green and trees round BC Place and Rogers Arena: 0 buildings, 0 street lines, 28 land cover outlines and 1370 trees within 450 metres of the middle, read 2026-10-22. */
|
|
3
|
+
export declare const VANCOUVER_NATURE: ContextArea;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Vancouver: water, green and trees round BC Place and Rogers Arena: 0 buildings, 0 street lines, 28 land cover outlines and 1370 trees within 450 metres of the middle, read 2026-10-22. */
|
|
2
|
+
export const VANCOUVER_NATURE = {
|
|
3
|
+
"key": "vancouver-nature",
|
|
4
|
+
"name": { "en": "Vancouver: water, green and trees round BC Place and Rogers Arena", "ja": "バンクーバー:BCプレイスとロジャース・アリーナ周辺の水面・緑地・樹木" },
|
|
5
|
+
"origin": [-123.11038, 49.277237],
|
|
6
|
+
"radius": 450,
|
|
7
|
+
"grid": 0.5,
|
|
8
|
+
"layers": {
|
|
9
|
+
"landCover": {
|
|
10
|
+
"source": "VoxCity 1.7.0 land cover from OpenStreetMap",
|
|
11
|
+
"read": "2026-10-22",
|
|
12
|
+
"licence": "ODbL 1.0 (OpenStreetMap); VoxCity is MIT",
|
|
13
|
+
"attribution": { "en": "Land cover made with VoxCity (MIT) from OpenStreetMap", "ja": "土地被覆:OpenStreetMap から VoxCity(MIT)で作成" },
|
|
14
|
+
/** Each outline: the class (0 water, 1 green, 2 wood), then the first point (east, north) and each step to the next, in grid steps from `origin`. A hole is an outline of its own (filled even-odd). */
|
|
15
|
+
"rows": [
|
|
16
|
+
[0, 1838, 1998, -80, -120, 80, -40],
|
|
17
|
+
[0, 1558, 1998, -479, -200, 479, 40],
|
|
18
|
+
[0, 1119, -1079, -1758, 40, 0, -280],
|
|
19
|
+
[0, -80, -1159, 120, 80, 0, -40],
|
|
20
|
+
[0, -679, -1358, -1319, 159, 0, -799],
|
|
21
|
+
[1, 799, 1878, -80, -120, 80, -40],
|
|
22
|
+
[1, 1079, 1758, -120, -120, 639, 160],
|
|
23
|
+
[1, -1319, 1358, -199, -39, 199, -40],
|
|
24
|
+
[1, -1478, 1239, -120, -240, 160, 160],
|
|
25
|
+
[1, 80, 1199, -160, -120, 160, -80],
|
|
26
|
+
[1, -1119, 440, -160, 0, 0, -80],
|
|
27
|
+
[1, 599, 479, -40, -279, 280, 200, 0, 40],
|
|
28
|
+
[1, 639, 400, 40, 40, 0, -40],
|
|
29
|
+
[1, 1998, 479, -40, -359, 40, 0],
|
|
30
|
+
[1, 1159, 519, -480, -359, 520, -40],
|
|
31
|
+
[1, 1918, 80, -120, -120, 160, -40],
|
|
32
|
+
[1, 599, -160, -40, -160, 120, 40],
|
|
33
|
+
[1, 1438, -320, -119, -159, 119, 0],
|
|
34
|
+
[1, 1119, -240, -120, -479, 160, -40],
|
|
35
|
+
[1, 1718, -519, -200, -400, 160, 0],
|
|
36
|
+
[1, -919, -959, 320, 0, -40, 120],
|
|
37
|
+
[1, 1159, -1159, -40, 80, -240, -80],
|
|
38
|
+
[1, -1598, -919, -200, -439, 240, 319],
|
|
39
|
+
[1, 959, -1279, -120, -239, 80, -40],
|
|
40
|
+
[1, 120, -1119, -240, -160, -40, -119, 280, -120],
|
|
41
|
+
[1, -40, -1358, 40, 39, 0, -39],
|
|
42
|
+
[1, -799, -1598, -160, -120, 200, 40],
|
|
43
|
+
[1, -1598, -1918, -200, -80, 240, 0],
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
"trees": {
|
|
47
|
+
"source": "OpenStreetMap natural=tree and the City of Vancouver's Public trees: https://opendata.vancouver.ca/explore/dataset/public-trees/",
|
|
48
|
+
"read": "2026-10-22",
|
|
49
|
+
"licence": "ODbL 1.0 (OpenStreetMap) and the Open Government Licence - Vancouver",
|
|
50
|
+
"attribution": { "en": "Trees: City of Vancouver open data, Public trees, under the Open Government Licence - Vancouver; simplified and edited by Kyogijo Stadiums", "ja": "樹木:バンクーバー市オープンデータ「Public trees」(Open Government Licence - Vancouver);Kyogijo Stadiums が加工して作成" },
|
|
51
|
+
/** One row for all the trees: the first tree's east, north and crown radius, then each next tree's step in east and north from the one before and its crown radius, all in grid steps. */
|
|
52
|
+
"rows": [
|
|
53
|
+
[-343, -825, 5, 4, 3, 7, 5, 4, 6, 8, 6, 5, 27, 11, 4, -1, 1, 5, 7, 6, 5, -13, 1, 5, -131, 8, 5, 15, 2, 5, -16, 2, 4, 0, 8, 5, -4, 2, 5, 20, 1, 5, -22, 3, 5, 41, 8, 5, -17, 0, 5, 10, 0, 5, 11, 4, 5, 57, 1, 4, -2, 1, 5, -19, 0, 5, 76, 3, 5, -208, 1, 5, 314, 1, 5, -228, 1, 4, -13, 1, 5, -55, 1, 5, 75, 1, 5, 112, 2, 4, 118, 0, 5, -313, 1, 5, 25, 2, 5, 261, 3, 5, -221, 1, 5, 258, 1, 5, -249, 0, 5, -52, 0, 5, -7, 1, 4, -2, 1, 5, 131, 0, 5, -166, 2, 5, 321, 1, 5, 29, 0, 5, -168, 1, 4, -136, 2, 5, 280, 1, 5, -315, 2, 5, 320, 2, 5, -248, 0, 5, 5, 1, 5, -33, 1, 5, -33, 3, 5, 315, 0, 5, -274, 3, 5, 140, 0, 5, -12, 1, 5, -223, 1, 5, 65, 3, 5, -59, 8, 5, 106, 0, 5, -36, 0, 5, 42, 1, 4, 4, 5, 5, 5, 1, 4, 107, 0, 5, -147, 1, 5, -77, 1, 5, 213, 0, 5, 192, 3, 5, -281, 2, 5, 290, 3, 5, -324, 0, 5, 351, 1, 5, -340, 5, 5, 5, 2, 5, 119, 1, 5, -13, 1, 5, -101, 3, 5, -148, 1, 3, 152, 1, 4, -172, 0, 5, 4, 0, 3, 210, 2, 4, -1, 3, 5, 86, 2, 5, -76, 4, 5, -35, 0, 4, -2, 1, 5, -165, 1, 5, 293, 1, 5, -44, 0, 5, 11, 0, 5, 20, 3, 5, -67, 1, 5, 3, 1, 4, -43, 0, 5, 158, 0, 5, -151, 1, 4, 90, 3, 5, -209, 0, 5, 121, 1, 5, 40, 2, 5, -32, 1, 5, -1, 3, 5, -115, 2, 5, 174, 1, 5, -11, 0, 4, 23, 0, 5, -26, 1, 5, -31, 2, 5, -2, 1, 5, 71, 1, 5, -186, 3, 5, 129, 3, 5, -3, 1, 5, -113, 4, 5, 126, 2, 5, -1, 1, 5, 66, 4, 5, -178, 2, 5, -119, 6, 3, -6, 1, 5, 139, 1, 5, -206, 2, 5, 85, 4, 5, 135, 4, 5, 2, 0, 3, 87, 1, 5, -2, 1, 4, 30, 0, 5, -233, 0, 4, -3, 2, 5, 130, 2, 5, 68, 4, 5, -181, 3, 5, 128, 1, 5, 77, 2, 5, -73, 0, 3, 73, 1, 4, -186, 4, 5, 149, 1, 5, -26, 1, 5, 24, 1, 4, -21, 0, 3, 51, 1, 5, -161, 6, 5, 147, 8, 5, -126, 1, 4, 126, 0, 4, 448, 1, 5, -687, 0, 5, 109, 0, 5, -236, 0, 5, 58, 7, 5, 79, 2, 5, 127, 5, 4, -269, 1, 5, 264, 0, 5, 559, 6, 5, -429, 0, 5, -108, 4, 4, -4, 1, 5, -291, 0, 5, 74, 1, 5, 326, 2, 4, -177, 1, 5, 200, 4, 5, -12, 4, 4, 424, 3, 5, 84, 7, 5, -496, 2, 5, 450, 2, 5, -443, 3, 5, -80, 6, 5, 90, 2, 5, -469, 1, 4, 393, 6, 5, 3, 1, 4, 516, 1, 5, -13, 1, 5, -886, 6, 3, 395, 0, 5, 3, 1, 4, 82, 1, 5, -66, 8, 4, 451, 0, 5, -11, 0, 5, -443, 1, 5, -403, 0, 5, 486, 1, 5, 360, 5, 5, -448, 5, 5, 24, 1, 4, 436, 4, 5, -888, 0, 5, 878, 1, 5, -832, 0, 5, -41, 1, 7, 459, 3, 5, -18, 1, 5, 23, 1, 4, 15, 4, 4, -427, 4, 5, -38, 2, 7, 479, 3, 4, -62, 0, 5, 38, 0, 5, -452, 4, 5, 472, 0, 5, -5, 7, 5, 20, 3, 5, -477, 1, 5, 5, 3, 7, 467, 2, 5, -332, 3, 5, -131, 3, 5, 287, 3, 5, -157, 2, 5, 476, 1, 5, -14, 2, 5, -580, 2, 6, 338, 1, 5, -216, 2, 4, -5, 1, 5, 228, 4, 5, -14, 1, 5, -199, 2, 5, 182, 1, 5, 9, 0, 5, -204, 1, 5, -112, 1, 5, 108, 0, 5, 227, 1, 5, -327, 1, 6, 504, 2, 4, -189, 1, 5, -201, 0, 5, 491, 2, 5, -300, 1, 5, 196, 2, 5, -403, 0, 5, -94, 4, 5, 107, 1, 5, -12, 3, 4, 523, 2, 5, -527, 0, 5, 16, 2, 5, -89, 7, 7, 103, 1, 5, 425, 0, 4, 88, 3, 5, -89, 0, 5, -525, 2, 5, 97, 3, 5, -19, 3, 5, 140, 5, 5, 1283, 2, 5, -1296, 1, 5, 347, 0, 4, -478, 0, 5, 1419, 0, 5, -9, 2, 5, -935, 0, 5, 928, 0, 5, 33, 1, 5, -43, 0, 5, -26, 0, 5, -1334, 2, 5, 1306, 1, 5, -21, 0, 5, 112, 1, 5, -208, 0, 5, 107, 1, 5, -1156, 0, 5, -14, 2, 5, 329, 2, 5, 703, 1, 5, 239, 2, 5, -1279, 0, 5, -155, 0, 7, 1213, 3, 5, -711, 3, 5, -375, 0, 5, 918, 1, 5, 15, 0, 5, -1122, 5, 5, 72, 0, 4, 8, 0, 5, 1425, 2, 5, -854, 0, 5, -76, 0, 5, 685, 1, 5, -670, 5, 4, -396, 0, 5, -201, 3, 5, 192, 1, 5, 1316, 0, 5, -1506, 0, 4, 593, 0, 5, -506, 3, 5, 115, 0, 5, -380, 1, 5, 271, 0, 8, 1163, 1, 5, -649, 1, 4, -3, 3, 5, 115, 0, 5, -727, 2, 5, 200, 0, 5, 1317, 0, 5, -1414, 0, 7, 1198, 3, 5, -673, 0, 4, 54, 2, 5, 607, 0, 5, -1094, 0, 5, -354, 0, 5, 1423, 2, 5, -1155, 0, 5, -275, 2, 5, 208, 2, 3, -47, 1, 4, 44, 1, 5, -47, 0, 5, 631, 2, 5, -465, 1, 5, 488, 1, 4, -571, 1, 5, 3, 0, 4, 111, 1, 5, -332, 0, 5, 264, 1, 3, 582, 1, 5, -61, 2, 5, -795, 0, 5, 139, 1, 5, 49, 2, 5, 2, 2, 4, 1447, 1, 5, -1628, 3, 5, 122, 1, 5, 154, 1, 3, -40, 0, 3, -2, 1, 5, -272, 0, 5, 0, 2, 5, 856, 1, 4, -648, 6, 3, -57, 0, 5, 54, 1, 5, 118, 1, 5, 1123, 1, 5, -1113, 2, 5, 519, 1, 5, -565, 3, 5, -271, 1, 5, 47, 2, 5, 231, 1, 4, -155, 0, 5, 52, 0, 5, 4, 1, 3, 673, 2, 5, -838, 8, 5, 1427, 1, 5, -1327, 1, 4, -53, 0, 5, 104, 1, 5, -54, 1, 5, 758, 0, 5, -578, 1, 4, -1, 1, 5, -262, 4, 5, 41, 2, 5, -43, 0, 5, 127, 2, 4, -4, 2, 5, -108, 8, 5, -3, 1, 5, 1395, 1, 5, -1291, 1, 3, -4, 0, 5, 779, 2, 4, 66, 1, 5, -929, 6, 6, 860, 1, 5, -788, 0, 5, 807, 4, 4, 498, 3, 5, -503, 4, 5, -885, 3, 5, 907, 2, 4, -907, 6, 5, 903, 0, 5, -892, 3, 5, 913, 5, 4, -845, 2, 5, 772, 0, 5, -848, 2, 4, 919, 2, 5, -922, 0, 5, -10, 0, 5, 835, 2, 5, 48, 0, 5, -879, 5, 5, 941, 1, 5, -953, 0, 5, 10, 1, 5, 864, 6, 5, -883, 1, 5, 11, 1, 5, 242, 4, 5, -247, 1, 5, 1410, 0, 5, -1280, 0, 5, 767, 1, 5, -899, 1, 5, -8, 8, 5, -21, 2, 5, 19, 0, 5, 300, 1, 5, -143, 0, 3, 117, 0, 5, 630, 2, 5, 11, 1, 5, -23, 0, 5, 810, 4, 5, -1714, 3, 5, 181, 2, 3, 132, 2, 5, 610, 2, 5, 795, 0, 5, -810, 0, 5, -10, 1, 5, -920, 1, 5, 1726, 2, 5, -1387, 0, 5, -327, 1, 5, -3, 2, 5, -19, 5, 5, 958, 2, 5, 508, 0, 5, -1458, 4, 5, 1105, 2, 4, -1108, 1, 5, 1105, 1, 5, -870, 1, 3, 714, 2, 5, 179, 7, 4, -1141, 1, 5, -11, 0, 5, 344, 3, 5, -73, 0, 3, 897, 3, 4, -3, 1, 5, -827, 1, 5, 699, 7, 5, -709, 2, 5, -43, 0, 3, 632, 3, 5, -594, 2, 5, -5, 5, 5, 915, 0, 4, -5, 2, 5, -931, 3, 5, 730, 0, 5, -704, 1, 5, 926, 1, 5, -931, 4, 5, 699, 5, 5, -710, 1, 5, 623, 3, 5, -896, 1, 5, -1, 3, 5, 974, 9, 5, -676, 7, 5, -276, 1, 3, -4, 1, 5, -39, 0, 5, 3, 1, 3, 1451, 1, 5, -469, 2, 5, -34, 4, 5, -940, 2, 3, 1463, 0, 3, -1464, 0, 5, 40, 1, 5, 4, 1, 5, 903, 1, 5, 304, 0, 4, 197, 1, 3, -545, 1, 5, 50, 0, 5, 508, 2, 5, -213, 0, 5, -1234, 6, 5, 11, 11, 5, 38, 0, 5, -37, 1, 3, 1436, 0, 5, -16, 5, 5, -1371, 4, 5, 1177, 0, 4, -1179, 0, 5, 1175, 2, 5, 216, 1, 3, -16, 11, 5, 17, 3, 3, -213, 0, 4, -1194, 6, 5, 1191, 2, 5, -1181, 6, 5, 8, 8, 7, 1177, 3, 14, -856, 3, 5, -40, 1, 5, -22, 0, 5, -213, 1, 3, 216, 2, 7, -251, 2, 6, 278, 4, 5, -236, 0, 5, 2, 1, 5, 195, 3, 6, -227, 4, 6, 175, 1, 5, 103, 3, 4, 1, 1, 5, -52, 0, 5, -11, 3, 6, -4, 1, 5, -202, 1, 6, 322, 7, 5, 149, 0, 5, -266, 2, 5, -11, 0, 5, 1427, 2, 5, -1610, 0, 3, 479, 1, 5, 655, 2, 4, -1002, 3, 5, 1447, 1, 7, -1531, 1, 6, 1083, 0, 5, 466, 0, 5, -804, 1, 3, -788, 0, 5, 7, 2, 3, 1550, 0, 5, -938, 1, 5, -289, 2, 6, 1210, 1, 5, -999, 1, 5, 982, 2, 5, -1473, 0, 5, 449, 1, 5, -446, 0, 4, 58, 3, 5, 1499, 0, 5, -41, 0, 5, -64, 1, 5, 107, 0, 8, -39, 0, 7, 26, 0, 5, -1584, 2, 5, 322, 1, 5, -189, 1, 5, -2, 0, 4, 1415, 0, 6, -1219, 0, 3, 472, 1, 3, 316, 1, 4, 416, 1, 7, -73, 0, 5, -346, 3, 5, 327, 1, 5, 147, 1, 6, -42, 0, 5, -32, 2, 7, -1433, 0, 5, 1401, 0, 5, -1359, 1, 5, 1302, 0, 7, -3, 1, 5, 124, 0, 7, -66, 1, 7, -818, 1, 5, -15, 0, 5, 935, 0, 5, -764, 1, 3, 683, 0, 8, -93, 0, 7, -2, 1, 5, 63, 0, 7, -25, 1, 5, -1328, 0, 4, 0, 2, 5, 233, 4, 5, -41, 0, 6, 43, 0, 3, 1134, 1, 5, 6, 0, 6, -45, 1, 7, -667, 0, 5, -131, 1, 5, -616, 1, 3, 599, 2, 5, 832, 1, 5, -1148, 3, 5, 1149, 0, 6, 13, 3, 5, -682, 1, 5, 5, 0, 4, 612, 0, 5, 70, 1, 7, -845, 0, 5, 761, 0, 5, 18, 0, 7, -11, 1, 7, -1080, 1, 5, 292, 0, 5, 813, 0, 8, 19, 3, 7, -33, 6, 7, -99, 0, 7, 93, 0, 5, -1075, 1, 3, 482, 1, 4, -4, 0, 5, 577, 1, 5, -767, 0, 5, 947, 0, 7, -266, 1, 7, 220, 0, 7, -921, 1, 5, -223, 1, 5, 917, 0, 5, -1197, 1, 5, 1366, 0, 4, -1362, 0, 5, 1351, 1, 4, -27, 0, 8, -1399, 0, 5, 1463, 1, 5, -13, 0, 5, -41, 1, 7, -152, 0, 5, -1254, 1, 4, 1347, 0, 5, -89, 0, 9, 110, 1, 5, 159, 0, 9, -50, 1, 5, -74, 1, 5, -169, 0, 5, 220, 0, 6, -1099, 0, 5, 203, 2, 5, 241, 0, 5, -225, 0, 5, -256, 1, 3, 1059, 1, 5, 66, 1, 7, -12, 0, 5, -73, 1, 5, 162, 0, 5, -1439, 1, 5, 1404, 0, 5, -172, 0, 5, -1323, 1, 3, 1232, 0, 5, 296, 0, 7, -198, 2, 7, 46, 2, 5, 147, 0, 5, -967, 1, 5, 320, 0, 5, -307, 1, 5, -506, 1, 6, 763, 0, 5, 518, 1, 7, 113, 1, 5, -1075, 0, 5, 1028, 0, 5, -1420, 1, 3, 1228, 0, 5, -1114, 0, 5, 231, 1, 5, 1022, 0, 5, 27, 0, 5, -1354, 0, 6, 1331, 0, 7, -74, 1, 4, 218, 0, 5, -221, 1, 5, -1175, 0, 4, 779, 2, 5, 200, 0, 5, -198, 1, 4, 434, 0, 5, 69, 2, 5, 32, 0, 5, -215, 0, 4, -1231, 1, 5, 1394, 1, 5, -168, 0, 5, -1185, 1, 5, -41, 0, 8, 375, 0, 5, -9, 0, 5, 1030, 1, 7, -530, 0, 5, -767, 0, 6, 1325, 2, 5, -48, 1, 3, 83, 1, 5, 3, 0, 4, -1041, 0, 5, 799, 0, 4, -797, 1, 4, 974, 1, 6, -59, 1, 5, 96, 0, 5, 93, 1, 5, -312, 1, 5, -638, 0, 5, 833, 2, 5, -1383, 1, 5, 450, 0, 5, 966, 1, 5, -561, 0, 5, 530, 2, 7, -829, 0, 5, -428, 1, 3, 1051, 0, 4, -2, 1, 5, -649, 1, 5, 899, 1, 5, -129, 1, 6, 204, 1, 7, -1553, 1, 5, 614, 0, 5, -575, 1, 5, 450, 0, 5, -583, 1, 5, 1423, 0, 5, -743, 0, 5, -14, 1, 5, 644, 1, 5, -290, 1, 4, -2, 0, 5, -501, 0, 5, 197, 3, 5, 825, 2, 5, -1415, 0, 5, 563, 1, 5, -402, 0, 5, -39, 0, 5, 1378, 0, 5, -973, 1, 5, 649, 1, 5, 0, 0, 4, 135, 1, 6, 108, 1, 7, -1533, 0, 5, 1507, 0, 5, -794, 1, 5, 832, 0, 7, -34, 0, 5, -827, 1, 5, -679, 0, 6, 1618, 0, 5, -596, 1, 4, -744, 1, 5, 1346, 0, 4, -347, 1, 5, -649, 0, 5, -529, 0, 5, 616, 3, 5, 844, 0, 7, -1316, 1, 5, 1360, 0, 4, -1359, 0, 4, 1357, 1, 5, 28, 1, 5, -117, 1, 7, -5, 0, 5, 39, 1, 6, -926, 0, 5, 952, 0, 7, -160, 2, 6, 69, 0, 5, -759, 0, 5, -472, 0, 5, -237, 0, 4, 277, 1, 5, 1202, 1, 7, -1404, 0, 5, 732, 1, 5, 819, 1, 5, -244, 1, 6, 219, 0, 5, -20, 1, 5, -894, 0, 5, 966, 0, 7, -938, 1, 5, 851, 0, 5, -190, 0, 5, -1348, 0, 5, 1542, 1, 7, -29, 1, 4, -948, 0, 5, 887, 1, 5, -1470, 0, 6, 1459, 2, 5, 78, 0, 4, -751, 0, 5, -102, 1, 5, -631, 0, 5, 2, 1, 3, 658, 0, 5, 937, 1, 5, -1072, 0, 5, 1024, 0, 5, -155, 1, 5, 177, 0, 5, -231, 0, 4, 0, 2, 5, -703, 1, 5, -405, 0, 5, 530, 0, 5, -527, 0, 3, 215, 1, 3, -3, 0, 5, 877, 1, 5, -657, 1, 5, 852, 0, 5, -24, 0, 4, -177, 0, 5, -598, 2, 5, 679, 0, 5, 4, 0, 5, -1412, 1, 6, 1590, 0, 4, -459, 0, 5, 436, 0, 5, 21, 1, 5, -167, 1, 5, 127, 0, 7, 24, 0, 4, -27, 1, 5, -444, 1, 5, 269, 0, 5, -686, 1, 5, 74, 1, 5, 325, 0, 5, 268, 1, 5, 46, 1, 5, -5, 0, 5, -674, 2, 5, -179, 0, 5, 124, 1, 5, 888, 0, 7, -861, 0, 5, -250, 0, 5, 971, 1, 5, -1149, 1, 5, 1069, 0, 5, 247, 1, 5, -169, 0, 5, -46, 0, 5, -1141, 0, 5, 524, 3, 5, -32, 1, 5, -282, 1, 5, 264, 1, 5, 852, 0, 7, -145, 0, 5, -732, 1, 5, -165, 0, 5, 4, 2, 5, 828, 0, 5, 20, 1, 4, -23, 2, 5, 64, 0, 5, 189, 0, 5, -1086, 0, 5, 175, 1, 5, -253, 0, 5, 934, 1, 5, -622, 0, 5, -257, 1, 5, 1054, 0, 8, -162, 1, 4, -1094, 1, 5, -275, 0, 3, 240, 0, 4, -244, 0, 5, 1558, 1, 5, -1274, 0, 6, 1087, 0, 5, -854, 0, 5, 401, 1, 5, -677, 0, 5, 445, 1, 5, 502, 0, 5, 384, 0, 5, -1007, 1, 5, -78, 0, 5, -71, 1, 5, 771, 0, 5, 364, 1, 5, -171, 0, 5, -701, 0, 5, -209, 2, 5, 59, 1, 7, 865, 0, 4, -1139, 2, 4, 271, 1, 5, -274, 0, 5, 1227, 2, 5, -74, 1, 4, -58, 0, 6, -771, 0, 5, 909, 2, 8, -84, 0, 5, -926, 0, 5, 915, 0, 4, 163, 1, 6, -2, 0, 5, -821, 0, 5, -63, 1, 5, 235, 0, 5, -919, 1, 5, 1398, 0, 5, -35, 1, 5, -1321, 0, 3, 1374, 1, 4, 170, 1, 5, -1306, 0, 5, 135, 0, 5, 1, 3, 5, 995, 1, 5, -938, 0, 5, 200, 0, 5, 911, 1, 5, -1018, 1, 5, 192, 0, 5, -196, 0, 5, 46, 0, 5, 779, 1, 5, -1377, 0, 5, 1468, 0, 5, 93, 1, 5, -1282, 1, 5, 117, 1, 5, 1157, 0, 5, -172, 1, 7, -659, 0, 5, 829, 1, 5, 26, 0, 5, -131, 1, 5, 41, 1, 5, -832, 0, 5, 787, 0, 5, -550, 1, 5, 606, 1, 7, -1171, 0, 5, 432, 0, 5, 603, 1, 6, 110, 0, 5, -860, 0, 10, -2, 2, 5, -320, 0, 5, 1163, 1, 5, -722, 0, 5, -276, 0, 5, 1107, 0, 5, -1271, 0, 4, 93, 0, 5, 262, 3, 5, 738, 0, 5, 160, 0, 5, 4, 1, 5, -781, 0, 5, -714, 0, 5, 1383, 1, 5, -860, 0, 6, -128, 2, 5, 748, 0, 5, -859, 0, 5, 1024, 1, 5, 168, 0, 5, -1095, 0, 5, -58, 0, 6, 988, 1, 6, -789, 0, 5, 164, 0, 5, -301, 1, 5, 334, 0, 5, 744, 1, 5, -879, 1, 5, 878, 1, 5, 53, 1, 6, -1220, 0, 5, 1152, 0, 4, -4, 1, 5, -110, 1, 5, 2, 1, 5, -609, 0, 5, -432, 0, 5, -268, 0, 5, 667, 1, 5, 804, 0, 5, -84, 1, 5, 70, 2, 6, -63, 0, 7, -105, 0, 7, -1299, 1, 5, 1126, 1, 5, -1200, 0, 5, 1522, 0, 5, -1514, 1, 5, 751, 0, 5, -744, 1, 5, 778, 0, 5, 712, 1, 5, -1459, 2, 5, 372, 0, 5, -394, 1, 5, 1373, 1, 6, 89, 0, 8, -1390, 1, 5, 669, 1, 5, 713, 0, 5, -677, 0, 5, -378, 2, 5, 777, 0, 4, 178, 1, 7, -4, 0, 5, -1285, 1, 5, 1361, 0, 5, -250, 2, 5, 201, 1, 5, -628, 0, 5, -717, 1, 5, 350, 0, 5, -298, 0, 5, 35, 1, 5, 1325, 0, 5, -11, 1, 8, -839, 2, 5, 195, 0, 5, 640, 0, 5, -839, 0, 5, -203, 1, 5, -279, 1, 5, 1305, 0, 7, -19, 0, 5, -638, 2, 5, 643, 2, 5, -18, 1, 5, -1338, 0, 5, 1343, 1, 8, -1303, 1, 5, 1094, 0, 4, -412, 3, 5, 411, 2, 5, -870, 0, 6, 347, 0, 9, -600, 2, 5, 1449, 1, 6, -1397, 1, 5, 505, 2, 5, 162, 0, 5, 726, 0, 5, -1400, 4, 5, 564, 0, 3, 816, 0, 5, -301, 2, 4, -1114, 0, 5, 9, 0, 3, 222, 0, 5, 1, 1, 4, 485, 0, 5, -389, 1, 5, 1069, 0, 5, -582, 1, 3, 298, 1, 5, -556, 1, 5, -504, 0, 5, 1212, 2, 4, 115, 0, 5, -129, 3, 5, 111, 1, 5, -1352, 1, 5, 603, 0, 8, 626, 0, 5, -1186, 1, 3, 1061, 2, 5, -547, 0, 5, 776, 1, 5, -122, 2, 4, -876, 2, 5, -324, 1, 5, 188, 0, 5, 904, 1, 5, 89, 0, 4, -628, 1, 5, -500, 2, 5, 1113, 2, 5, -15, 3, 5, -585, 1, 7, -520, 0, 5, 1262, 1, 4, -1298, 0, 5, 603, 0, 9, -5, 1, 5, -43, 0, 5, 574, 1, 5, -800, 0, 5, 968, 0, 5, -1242, 0, 5, 1062, 1, 5, 115, 0, 3, -1068, 1, 5, -154, 5, 5, 38, 1, 5, -37, 0, 3, 39, 0, 3, 288, 1, 5, 869, 0, 5, -1142, 0, 5, 90, 2, 4, -3, 1, 5, 1031, 3, 5, -608, 1, 7, 662, 0, 5, -1178, 3, 5, 1103, 2, 5, -813, 0, 5, -208, 1, 5, 660, 0, 5, 504, 0, 5, -269, 1, 5, -891, 0, 4, 1124, 1, 5, -132, 1, 5, -500, 0, 10, 675, 1, 5, -1114, 0, 5, 817, 2, 5, -219, 0, 5, 487, 3, 3, -1149, 0, 3, -60, 1, 3, 57, 1, 5, 458, 0, 5, -517, 1, 5, 913, 1, 5, 293, 1, 5, -490, 1, 5, -491, 1, 5, 824, 1, 5, -434, 1, 3, -644, 2, 5, 558, 3, 5, -228, 1, 5, 231, 0, 5, -428, 2, 5, 130, 0, 5, 577, 3, 5, -12, 1, 5, 419, 1, 3, -266, 1, 5, -969, 1, 5, 1043, 1, 5, -89, 2, 5, -847, 3, 5, 1128, 1, 5, -296, 0, 5, 85, 1, 5, 30, 1, 5, 5, 1, 5, -31, 0, 5, -1017, 0, 3, 1253, 0, 4, -926, 0, 5, 698, 2, 5, -219, 1, 5, 9, 0, 5, 12, 2, 5, -742, 1, 5, 6, 0, 4, 173, 1, 5, 721, 4, 5, -166, 0, 5, -298, 1, 5, 11, 0, 5, 452, 1, 5, 274, 3, 4, -245, 0, 5, 218, 3, 5, -215, 0, 5, -241, 3, 5, -268, 4, 5, -220, 2, 5, 298, 5, 5, 427, 1, 5, -451, 0, 5, -4, 1, 5, 460, 0, 5, -1021, 1, 5, 1239, 2, 5, 3, 1, 3, -740, 3, 4, -10, 3, 5, -499, 0, 3, 612, 1, 5, -296, 0, 5, 71, 0, 5, 168, 0, 5, 59, 1, 5, -617, 0, 5, 998, 1, 5, -402, 2, 5, 404, 1, 4, -421, 2, 5, 452, 1, 5, -4, 0, 5, -1035, 1, 3, 337, 1, 5, 72, 0, 5, -413, 1, 5, 561, 2, 5, -66, 1, 5, 147, 0, 5, -61, 1, 5, 38, 2, 5, -118, 1, 5, 509, 0, 5, -589, 2, 5, 270, 0, 4, 321, 1, 4, -522, 1, 4, -341, 2, 9, 393, 1, 5, 111, 1, 5, -105, 0, 5, 22, 2, 5, 472, 3, 5, -554, 0, 4, -381, 1, 4, 371, 0, 5, 561, 1, 5, -936, 1, 5, 57, 1, 9, 371, 1, 5, 29, 1, 5, 109, 6, 5, -127, 1, 5, -428, 1, 5, 55, 0, 9, 873, 0, 5, -594, 1, 5, 591, 0, 5, -579, 1, 5, 10, 2, 4, 47, 2, 5, -393, 0, 4, 886, 4, 5, 1, 1, 5, -885, 0, 5, 380, 5, 5, 536, 1, 5, -3, 2, 5, -530, 0, 5, -368, 7, 4, 264, 4, 5, 90, 3, 5, 544, 0, 5, -548, 2, 5, -255, 2, 5, 472, 0, 5, 301, 5, 5, 2, 3, 5, -793, 5, 7, 504, 1, 5, -494, 9, 9, 552, 2, 3, -5, 0, 5, -39, 2, 5, -238, 2, 5, 5, 0, 7, -266, 3, 8, 558, 9, 3, -5, 0, 5, 214, 1, 5, -545, 1, 5, -14, 2, 5, 72, 0, 6, 239, 1, 6, -313, 1, 5, 599, 7, 5, -238, 2, 5, -332, 0, 5, -93, 0, 5, 660, 0, 5, -27, 1, 4, -482, 1, 5, -100, 2, 5, 580, 3, 5, -526, 3, 5, 291, 1, 5, -409, 2, 5, 452, 0, 5, -454, 2, 6, -88, 0, 7, 505, 1, 5, 262, 1, 5, -4, 1, 5, -624, 0, 5, -56, 2, 5, 191, 3, 6, -265, 2, 6, 549, 1, 5, -431, 3, 5, 391, 1, 5, -280, 1, 5, 281, 0, 5, -449, 2, 6, -51, 2, 6, 214, 0, 5, 501, 4, 4, -767, 0, 4, 325, 3, 5, -263, 3, 6, 224, 0, 5, -142, 1, 5, 134, 2, 5, -136, 0, 5, -76, 1, 5, 506, 2, 5, -501, 3, 7, 505, 1, 5, -271, 5, 4, -175, 0, 3, 170, 0, 5, 316, 1, 5, -271, 0, 5, -2, 3, 5, 248, 6, 5, -472, 1, 5, 474, 1, 6, -477, 0, 5, 517, 2, 3, -277, 2, 5, -251, 7, 5, 502, 4, 3, -479, 0, 5, 519, 1, 4, -5, 1, 5, -51, 2, 5, -219, 2, 5, -27, 9, 4, -2, 1, 5, 240, 0, 5, -192, 5, 6, -2, 1, 5, -33, 8, 5],
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
package/dist/context.d.ts
CHANGED
|
@@ -145,6 +145,8 @@ export declare function placeContext(areas: readonly ContextArea[], origin: LonL
|
|
|
145
145
|
w: number;
|
|
146
146
|
h: number;
|
|
147
147
|
}, venues: readonly ContextVenue[]): ContextPlan;
|
|
148
|
+
/** A share of the trees, spread evenly over the lot: every tree at 1, none at 0, and at 0.5 every other. The same trees every time, so that a view drawn again at a tier keeps the ones it kept. */
|
|
149
|
+
export declare function thinTrees(trees: readonly ContextTree[], share: number): ContextTree[];
|
|
148
150
|
/** The tone (0 low to 3 tall) of a building of `feet` height. */
|
|
149
151
|
export declare const toneOf: (feet: number) => number;
|
|
150
152
|
/** The streets flat on the floor, under the buildings: a path for each kind and width (a foot), widest and lowest kinds first, so that a sidewalk lies over the carriageway beside it and a viaduct's deck over the street under it. */
|
package/dist/context.js
CHANGED
|
@@ -149,6 +149,23 @@ export function placeContext(areas, origin, turn, frame, venues) {
|
|
|
149
149
|
const credits = (language) => [...new Set(layers.map((l) => l.attribution[language]).filter((text) => text !== ""))];
|
|
150
150
|
return { blocks, streets, land, trees, owned, attribution: { en: credits("en"), ja: credits("ja") } };
|
|
151
151
|
}
|
|
152
|
+
/** A share of the trees, spread evenly over the lot: every tree at 1, none at 0, and at 0.5 every other. The same trees every time, so that a view drawn again at a tier keeps the ones it kept. */
|
|
153
|
+
export function thinTrees(trees, share) {
|
|
154
|
+
if (share >= 1)
|
|
155
|
+
return [...trees];
|
|
156
|
+
if (!(share > 0))
|
|
157
|
+
return [];
|
|
158
|
+
const kept = [];
|
|
159
|
+
let owed = 0;
|
|
160
|
+
for (const tree of trees) {
|
|
161
|
+
owed += share;
|
|
162
|
+
if (owed >= 1) {
|
|
163
|
+
owed -= 1;
|
|
164
|
+
kept.push(tree);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return kept;
|
|
168
|
+
}
|
|
152
169
|
/** The tone (0 low to 3 tall) of a building of `feet` height. */
|
|
153
170
|
export const toneOf = (feet) => CONTEXT_TONES.filter((m) => feet / FEET_PER_METRE >= m).length;
|
|
154
171
|
const num = (value) => String(Math.round(value * 100) / 100);
|
|
@@ -299,7 +316,9 @@ export function blockPlanes(b, prefix) {
|
|
|
299
316
|
const ghost = (p) => (b.ghost ? { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.16 } : p);
|
|
300
317
|
// A see-through building: each wall gives way as the eye comes round to its side and looks in, to a trace of itself, and the roof is always nearly clear.
|
|
301
318
|
const xray = b.xray;
|
|
302
|
-
|
|
319
|
+
// A wall of a solid building, nothing showing through it, is hidden by the nearer walls and the roof while it faces away: `cull` says which side of the plane is the building's outside (a wall's `u` x `v` points to the left of its walk, which is in when the ring is wound clockwise).
|
|
320
|
+
const outside = b.ghost || xray !== undefined || wound === 0 ? undefined : wound > 0 ? -1 : 1;
|
|
321
|
+
const wall1 = (p) => (xray === undefined ? (outside === undefined ? ghost(p) : { ...p, cull: outside }) : { ...shy(p, xray), fadeTo: 0.06, fadeSide: true });
|
|
303
322
|
const roof1 = (p) => (xray === undefined ? ghost(p) : { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.1 });
|
|
304
323
|
b.ring.forEach((p, i) => {
|
|
305
324
|
const q = b.ring[(i + 1) % b.ring.length];
|
package/dist/district.turn.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// plan drawn with each venue's building only. The model it makes is a `TurnModel`, so the camera, the controls, the pointer, the auto-turn and the steady fit of every other 3D view are this one's; its
|
|
4
4
|
// buildings are stacked building by building (`Plane.group`), the far one first, so that a roof never shows through a nearer building.
|
|
5
5
|
import { describeDistrict, districtId, drawDistrict, drawMember, nestedDrawing, planDistrict, surfaceCentre } from "./district.js";
|
|
6
|
-
import { blockPlanes, contextUprights, shellBlock, slabPlanes } from "./context.js";
|
|
6
|
+
import { blockPlanes, contextUprights, shellBlock, slabPlanes, thinTrees } from "./context.js";
|
|
7
|
+
import { TIER_PROFILES, tierOf } from "./quality.js";
|
|
7
8
|
import { dojo } from "./budo/dojo.api.js";
|
|
8
9
|
import { KODOKAN_FLOORS, KODOKAN_GALLERY_LEVEL } from "./kodokan.js";
|
|
9
10
|
import { APART, n, plane } from "./turn.kit.js";
|
|
@@ -54,6 +55,27 @@ function placeInDistrict(p, placed, centre, prefix, group, lift = 0) {
|
|
|
54
55
|
}
|
|
55
56
|
return moved;
|
|
56
57
|
}
|
|
58
|
+
/** The box a district at `high` is framed to, kept for the options it was worked out for, so that a lower tier is framed exactly as `high` is: the camera does not move when the tier changes. */
|
|
59
|
+
const HIGH_BOX = new WeakMap();
|
|
60
|
+
/** The small parts of a venue that the lower tiers leave out: its furniture and fittings, never its stands, walls, floor or roof. */
|
|
61
|
+
const SMALL_PARTS = new Set(["bench", "glass", "post", "goal", "stanchion", "pole", "pylon", "mast", "fascia", "rail", "platform", "apron", "flag", "board", "frame", "rim", "net", "antenna", "tape"]);
|
|
62
|
+
/** How high a venue that is drawn as a block stands, in feet, by what it is: a stand-in for its stands and roof, near what its own model reaches. */
|
|
63
|
+
const BLOCK_HEIGHT = { arena: 100, stadium: 115, park: 95, hall: 60 };
|
|
64
|
+
/** A venue drawn as a proxy is a closed block while it is smaller than this share of the plan's longest side, and a ring of walls open at the top (the field and its stands show inside) above it. */
|
|
65
|
+
const SOLID_BELOW = 0.12;
|
|
66
|
+
/** The planes of a venue drawn as a proxy, in the venue's own group, tinted as a venue and not as the buildings round it: its outline standing up to its height, closed by a roof when it is small, a ring of walls seen from both sides when it is not. */
|
|
67
|
+
function venueBlock(key, kind, ring, prefix, group, open) {
|
|
68
|
+
const height = BLOCK_HEIGHT[kind] ?? 90;
|
|
69
|
+
const block = shellBlock(`venue-${key}`, ring, open ? height * 0.6 : height, undefined);
|
|
70
|
+
const planes = blockPlanes(block, `${prefix}proxy-`).filter((p) => !open || /w\d+$/.test(p.id));
|
|
71
|
+
return planes.map((p) => {
|
|
72
|
+
const tinted = { ...p, svg: p.svg.replace(/u-block-roof/g, "u-proxy-roof").replace(/u-block-([abc])/g, "u-proxy-$1").replace(/u-block /g, "u-proxy "), group };
|
|
73
|
+
// Seen from both sides: the inside of the far wall shows over the field.
|
|
74
|
+
if (open)
|
|
75
|
+
delete tinted.cull;
|
|
76
|
+
return tinted;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
57
79
|
/** A name that faces the eye, standing over a building: its text on a plate. */
|
|
58
80
|
function sign(id, text, at, bottom, size, group) {
|
|
59
81
|
const wide = Math.max(size * 2.2, size * 0.62 * text.length + size * 1.2);
|
|
@@ -70,6 +92,9 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
70
92
|
const plan = planDistrict(options);
|
|
71
93
|
const base = districtId(options);
|
|
72
94
|
const id = settings.id ?? `${base}-turn`;
|
|
95
|
+
// How much is drawn: the tier asked for (a view that chooses by its frames builds it again at each tier).
|
|
96
|
+
const profile = TIER_PROFILES[tierOf(settings.quality)];
|
|
97
|
+
const longest = Math.max(plan.view.w, plan.view.h);
|
|
73
98
|
const labels = options.labels ?? plan.mode === "city";
|
|
74
99
|
const items = [];
|
|
75
100
|
const defs = [];
|
|
@@ -77,12 +102,23 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
77
102
|
plan.members.forEach((placed, i) => {
|
|
78
103
|
const member = placed.member;
|
|
79
104
|
const prefix = `m${i}-`;
|
|
105
|
+
// A small venue, in a tier that draws such as a block: its outline standing up to its height, and nothing of its stands.
|
|
106
|
+
const xs = placed.footprint.map((q) => q[0]);
|
|
107
|
+
const ys = placed.footprint.map((q) => q[1]);
|
|
108
|
+
const plain = member.shell === undefined && member.parts === undefined && member.elevation === undefined && member.plate === undefined && member.slabs === undefined;
|
|
109
|
+
const span = Math.max(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
|
|
110
|
+
// (A district of one venue never draws it as a block: it is what the view is of.)
|
|
111
|
+
if (plain && plan.members.length > 1 && profile.proxy > 0 && span < profile.proxy * longest) {
|
|
112
|
+
items.push({ id: `${prefix}proxy`, kind: "blocks", at: placed.at, height: BLOCK_HEIGHT[member.kind] ?? 90, planes: venueBlock(member.key, member.kind, placed.footprint, prefix, i, span >= SOLID_BELOW * longest) });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
80
115
|
// A venue may have several surfaces (the rooms of a floor): each is built as its own 3D view is, with the crowd drawn without its seats on its floor and the stands raised, and put where the plan puts it, in the venue's one group.
|
|
81
116
|
placed.parts.forEach((pp, j) => {
|
|
82
117
|
const part = pp.part;
|
|
83
118
|
const tag = j === 0 ? "" : `p${j}`;
|
|
84
119
|
const memberId = `${id}-m${i}${tag}`;
|
|
85
|
-
const
|
|
120
|
+
const crowd = part.options.crowd;
|
|
121
|
+
const model = buildTurn({ ...part.options, ...(profile.simple && typeof crowd === "object" && crowd !== null ? { crowd: { ...crowd, detail: "plain", density: Math.min(crowd.density ?? 0.85, 0.6) } } : {}), language, ...(options.theme === undefined ? {} : { theme: options.theme }), id: `${base}-m${i}${tag}` }, { ...settings, id: memberId, venue: { roof: true, ...(typeof settings.venue === "object" ? settings.venue : {}) }, preset: undefined, view: undefined });
|
|
86
122
|
const centre = surfaceCentre(part.options);
|
|
87
123
|
const pfx = `m${i}${tag}-`;
|
|
88
124
|
// A surface on an upper floor (the Kodokan's dojos) stands that high over the ground, on a floor drawn in the air; the ground under it is left bare.
|
|
@@ -91,7 +127,7 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
91
127
|
const lowest = (p) => Math.min(p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]);
|
|
92
128
|
const shown = (p) => part.show === undefined || (part.show === "gallery") === (lowest(p) >= KODOKAN_GALLERY_LEVEL - 1e-6);
|
|
93
129
|
for (const item of model.uprights.items) {
|
|
94
|
-
const planes = item.planes.filter((p) => p.kind !== "ground" && shown(p)).map((p) => placeInDistrict(p, pp, centre, pfx, i, lift));
|
|
130
|
+
const planes = item.planes.filter((p) => p.kind !== "ground" && shown(p) && !(profile.simple && SMALL_PARTS.has(p.kind))).map((p) => placeInDistrict(p, pp, centre, pfx, i, lift));
|
|
95
131
|
if (planes.length > 0) {
|
|
96
132
|
const [ax, ay] = item.at;
|
|
97
133
|
const [rx, ry] = rot([ax - centre[0], ay - centre[1]], pp.angle);
|
|
@@ -142,6 +178,9 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
142
178
|
// The great dojo's floor, seen with every floor, has its own slab; and a solid building hides what is in it.
|
|
143
179
|
if (!chosen && ((member.plate === undefined && slab.floor === 7) || options.building === "solid"))
|
|
144
180
|
continue;
|
|
181
|
+
// The faint floors are as big as the building (each is a layer of its own, a megapixel and more): the lower tiers keep the chosen floor's and leave the others out.
|
|
182
|
+
if (!chosen && profile.simple)
|
|
183
|
+
continue;
|
|
145
184
|
const ids = chosen ? { top: `${prefix}plate`, side: `${prefix}plate-s` } : { top: `${prefix}plate-f${slab.floor}-top`, side: `${prefix}plate-f${slab.floor}-s` };
|
|
146
185
|
const planes = slabPlanes(placed.footprint, slab.level, thick, ids, chosen ? "chosen" : "faint").map((p) => ({ ...p, group: i }));
|
|
147
186
|
items.push({ id: ids.top, kind: "deck", at: placed.at, height: slab.level, planes });
|
|
@@ -156,7 +195,7 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
156
195
|
});
|
|
157
196
|
// The buildings round the venues stand apart from them: each block's planes are of no venue's group.
|
|
158
197
|
if (plan.context !== undefined)
|
|
159
|
-
for (const item of contextUprights(plan.context))
|
|
198
|
+
for (const item of contextUprights(plan.context, profile.blocks))
|
|
160
199
|
items.push({ ...item, planes: item.planes.map((p) => ({ ...p, group: APART })) });
|
|
161
200
|
if (labels) {
|
|
162
201
|
const size = Math.max(plan.view.w, plan.view.h) * (plan.mode === "city" ? 0.016 : 0.022);
|
|
@@ -172,9 +211,24 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
172
211
|
const area = { x0: view.x, y0: view.y, x1: view.x + view.w, y1: view.y + view.h };
|
|
173
212
|
const uprights = { items, lift: 0, thin: Math.hypot(view.w, view.h) * 0.0012, area, whole: true, ...(defs.length === 0 ? {} : { defs: defs.join("") }) };
|
|
174
213
|
const planes = planesOf(uprights);
|
|
175
|
-
|
|
214
|
+
// The camera is fitted to what the district is at `high`, whatever tier it is drawn at: a view that changes tier keeps its camera where it is, and the lower tiers stand in the same frame.
|
|
215
|
+
let box = sceneBox(planes, area, 0, centre, scale);
|
|
216
|
+
if (profile.tier !== "high") {
|
|
217
|
+
const key = JSON.stringify(settings.venue ?? null);
|
|
218
|
+
const known = HIGH_BOX.get(options);
|
|
219
|
+
if (known?.key === key)
|
|
220
|
+
box = known.box;
|
|
221
|
+
else {
|
|
222
|
+
box = buildDistrictTurn(options, { ...settings, quality: "high" }).box;
|
|
223
|
+
HIGH_BOX.set(options, { key, box });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else
|
|
227
|
+
HIGH_BOX.set(options, { key: JSON.stringify(settings.venue ?? null), box });
|
|
176
228
|
// (The floor has no names: the 3D view's own signs, which face the eye, carry them.)
|
|
177
|
-
|
|
229
|
+
// (The trees of the tiers that draw fewer are thinned on the floor only: a tree is a dot there.)
|
|
230
|
+
const floorPlan = profile.trees < 1 && plan.context?.trees !== undefined ? { ...plan, context: { ...plan.context, trees: thinTrees(plan.context.trees, profile.trees) } } : plan;
|
|
231
|
+
const floor = drawDistrict({ ...options, id: base, labels: false }, floorPlan, (member, i, part) => drawMember(member, { ...options, id: base }, i, base, { floor: true }, part));
|
|
178
232
|
const { title, description } = describeDistrict(options, plan);
|
|
179
233
|
const presets = districtPresets(plan);
|
|
180
234
|
const surfaceLike = { sport: "soccer", language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.style === undefined ? {} : { style: options.style }) };
|
|
@@ -199,6 +253,7 @@ export function buildDistrictTurn(options, settings = {}) {
|
|
|
199
253
|
centre,
|
|
200
254
|
view: openingView(settings, presets),
|
|
201
255
|
turnable: true,
|
|
256
|
+
tiered: true,
|
|
202
257
|
presets,
|
|
203
258
|
// A scene that turns about its middle with buildings far from it wants a longer lens, so that what swings toward the eye does not swell.
|
|
204
259
|
lens: 12,
|
package/dist/judging.mount.js
CHANGED
|
@@ -62,6 +62,13 @@ export function mountJudgingBroadcast(host, options, settings = {}) {
|
|
|
62
62
|
direction: () => turn.direction(),
|
|
63
63
|
setDirection: (direction) => turn.setDirection(direction),
|
|
64
64
|
togglePlay: () => turn.togglePlay(),
|
|
65
|
+
get quality() {
|
|
66
|
+
return turn.quality;
|
|
67
|
+
},
|
|
68
|
+
get tier() {
|
|
69
|
+
return turn.tier;
|
|
70
|
+
},
|
|
71
|
+
setQuality: (quality) => turn.setQuality(quality),
|
|
65
72
|
svg: () => turn.svg(),
|
|
66
73
|
destroy: () => {
|
|
67
74
|
turn.destroy();
|
package/dist/outside.dom.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface OutsideLayer {
|
|
|
16
16
|
/** The level shown. */
|
|
17
17
|
readonly level: OutsideLevel;
|
|
18
18
|
setLevel(level: OutsideLevel): void;
|
|
19
|
+
/** How much of the crowd a quality tier draws: the share of the people (0 hides the canvas) and the share of the usual width of the canvas. Painted again when it changes. */
|
|
20
|
+
setDetail(detail: {
|
|
21
|
+
people: number;
|
|
22
|
+
width: number;
|
|
23
|
+
}): void;
|
|
19
24
|
/** The 3D view's zoom: a near view is painted finer (its people discs, not density). The paint is redone only when the zoom crosses `NEAR`. */
|
|
20
25
|
zoom(zoom: number): void;
|
|
21
26
|
/** What the last paint cost, in milliseconds, and drew: `undefined` before the first. */
|
package/dist/outside.dom.js
CHANGED
|
@@ -37,6 +37,7 @@ function watchTheme(doc, change) {
|
|
|
37
37
|
function layerOf(canvas, spec, colourFrom, size) {
|
|
38
38
|
const doc = canvas.ownerDocument;
|
|
39
39
|
let level = spec.level;
|
|
40
|
+
let detail = { people: 1, width: 1 };
|
|
40
41
|
let ink = inkOf(colourFrom());
|
|
41
42
|
let drawn = "";
|
|
42
43
|
let alive = true;
|
|
@@ -62,11 +63,18 @@ function layerOf(canvas, spec, colourFrom, size) {
|
|
|
62
63
|
layer.zoomTier = tier;
|
|
63
64
|
layer.repaint();
|
|
64
65
|
},
|
|
66
|
+
setDetail(next) {
|
|
67
|
+
if (next.people === detail.people && next.width === detail.width)
|
|
68
|
+
return;
|
|
69
|
+
detail = next;
|
|
70
|
+
layer.repaint();
|
|
71
|
+
},
|
|
65
72
|
repaint(force = false) {
|
|
66
73
|
if (!alive)
|
|
67
74
|
return;
|
|
68
|
-
const width = Math.min(MAX_WIDE, Math.max(1, Math.round(size())));
|
|
69
|
-
|
|
75
|
+
const width = Math.min(MAX_WIDE, Math.max(1, Math.round(size() * detail.width)));
|
|
76
|
+
const shown = Math.round(level * detail.people);
|
|
77
|
+
if (level === 0 || shown === 0) {
|
|
70
78
|
// Nobody: the canvas is made one pixel, so that it holds no memory.
|
|
71
79
|
if (canvas.width !== 1 || canvas.height !== 1) {
|
|
72
80
|
canvas.width = 1;
|
|
@@ -81,11 +89,11 @@ function layerOf(canvas, spec, colourFrom, size) {
|
|
|
81
89
|
return;
|
|
82
90
|
canvas.hidden = false;
|
|
83
91
|
const frame = frameOf(spec.viewBox, width);
|
|
84
|
-
const key = `${
|
|
92
|
+
const key = `${shown}|${frame.width}x${frame.height}|${ink.join(",")}`;
|
|
85
93
|
if (!force && key === drawn)
|
|
86
94
|
return;
|
|
87
95
|
const started = performance.now();
|
|
88
|
-
const { rgba, stats } = rasterOutside(crowd,
|
|
96
|
+
const { rgba, stats } = rasterOutside(crowd, shown, frame, ink);
|
|
89
97
|
if (canvas.width !== frame.width || canvas.height !== frame.height) {
|
|
90
98
|
canvas.width = frame.width;
|
|
91
99
|
canvas.height = frame.height;
|
|
@@ -134,7 +142,7 @@ export function attachOutside3d(floor, spec, prefetchCrowd = true) {
|
|
|
134
142
|
canvas.setAttribute("data-outside", String(spec.level));
|
|
135
143
|
canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none";
|
|
136
144
|
floor.append(canvas);
|
|
137
|
-
const layer = layerOf(canvas, spec, () => floor.querySelector(".ky-district") ?? floor, () => (parseFloat(floor.style.width) || floor.clientWidth || 1400) * (layer.zoomTier === 1 ? 2.4 : 1.4));
|
|
145
|
+
const layer = layerOf(canvas, spec, () => floor.querySelector(".ky-district") ?? floor, () => ((parseFloat(floor.style.width) || floor.clientWidth || 1400) / (parseFloat(floor.dataset.raster ?? "") || 1)) * (layer.zoomTier === 1 ? 2.4 : 1.4));
|
|
138
146
|
const set = layer.setLevel;
|
|
139
147
|
layer.setLevel = (next) => {
|
|
140
148
|
canvas.setAttribute("data-outside", String(next));
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** The tiers of detail, from the most to the least. */
|
|
2
|
+
export type QualityTier = "high" | "medium" | "low";
|
|
3
|
+
/** What a view is asked for: one tier, or `auto`, which chooses by the frames the view makes. */
|
|
4
|
+
export type Quality = "auto" | QualityTier;
|
|
5
|
+
/** The tiers, highest first: a step down is one place later in this list. */
|
|
6
|
+
export declare const QUALITY_TIERS: readonly QualityTier[];
|
|
7
|
+
/** The values a page can ask for: `auto` and the tiers. */
|
|
8
|
+
export declare const QUALITIES: readonly Quality[];
|
|
9
|
+
/** What a tier draws. */
|
|
10
|
+
export interface TierProfile {
|
|
11
|
+
tier: QualityTier;
|
|
12
|
+
/** The most buildings of a district's context that stand up as blocks in the 3D view (the tallest and widest first); the rest are the floor's flat footprints, which every tier draws. */
|
|
13
|
+
blocks: number;
|
|
14
|
+
/** The share of the context's trees that are drawn, 0 to 1. */
|
|
15
|
+
trees: number;
|
|
16
|
+
/** A venue whose longest side is under this share of the plan's longest side is drawn as its footprint standing up to its height, one block, in place of its stands and roof; 0 draws every venue in full. */
|
|
17
|
+
proxy: number;
|
|
18
|
+
/** The small parts of a venue (benches, glass, posts, goals, the fascia and its fittings) are left out, and the crowd in its stands is drawn as blocks of colour. */
|
|
19
|
+
simple: boolean;
|
|
20
|
+
/** The share of the crowd outside that is drawn, 0 to 1 (0 hides it), and the width of its canvas as a share of the usual. */
|
|
21
|
+
outside: {
|
|
22
|
+
people: number;
|
|
23
|
+
width: number;
|
|
24
|
+
};
|
|
25
|
+
/** The pixel ratio the layers are rastered at, at most: on a phone or a tablet, and elsewhere. `Infinity` leaves the device's own. */
|
|
26
|
+
ratio: {
|
|
27
|
+
handheld: number;
|
|
28
|
+
other: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** The profile of each tier. `high` leaves everything as it always was (the cap on a handheld's pixel ratio is the mode `auto`'s and is made in `rasterOf`). */
|
|
32
|
+
export declare const TIER_PROFILES: Record<QualityTier, TierProfile>;
|
|
33
|
+
/** Whether a value is one of the qualities. */
|
|
34
|
+
export declare const isQuality: (value: unknown) => value is Quality;
|
|
35
|
+
/** A quality from whatever a page or an address gave: the same if it is one, else `fallback`. */
|
|
36
|
+
export declare const qualityOf: (value: unknown, fallback?: Quality) => Quality;
|
|
37
|
+
/** The tier a view with no frames to go by is drawn at: the tier asked for, and `high` for `auto` (a page made on a server, a first draw). */
|
|
38
|
+
export declare const tierOf: (quality: Quality | undefined) => QualityTier;
|
|
39
|
+
/** The tier one step lower or higher than `tier`, or the same at the end. */
|
|
40
|
+
export declare const lower: (tier: QualityTier) => QualityTier;
|
|
41
|
+
export declare const higher: (tier: QualityTier) => QualityTier;
|
|
42
|
+
/** What decides whether the device is a phone or a tablet: it has a coarse pointer, or its screen is small. It is asked of the page's own window, never of the browser's name. */
|
|
43
|
+
export declare function handheld(win: {
|
|
44
|
+
matchMedia?: (query: string) => {
|
|
45
|
+
matches: boolean;
|
|
46
|
+
};
|
|
47
|
+
screen?: {
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
};
|
|
51
|
+
innerWidth?: number;
|
|
52
|
+
} | null | undefined): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The share of its size a layer is made at, from the device's pixel ratio `dpr`: a layer is made `share` times as wide and as high in CSS pixels and turned back to its size by its matrix, so that it holds `share` squared as many device
|
|
55
|
+
* pixels, as far as the engine keeps a layer's pixels to its own box (the layers of a district at a ratio of 3 are hundreds of megabytes, which a phone's browser does not always keep). An engine that draws a layer again at the scale its matrix has
|
|
56
|
+
* (Chromium does) makes the same pixels from it, so there it changes nothing, and the sizes this package reports are the boxes times the ratio squared, not what an engine allocates. 1 when the ratio is within the cap. The mode `auto` caps a handheld
|
|
57
|
+
* at 2 even at `high`; a tier asked for by name is exactly that tier's, and `high` asked for by name is the device's own ratio, as it always was.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rasterOf(quality: Quality, tier: QualityTier, dpr: number, isHandheld: boolean): number;
|
|
60
|
+
/**
|
|
61
|
+
* The most device pixels one layer may hold in a tier: a flat floor of a building is a layer as big as the building's box (the Kodokan's fourteen plates are 47 megabytes each at a ratio of 3), so a layer past the budget is made smaller
|
|
62
|
+
* still (see `rasterOf`). `Infinity` at `high`, by name or in `auto`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function layerBudget(tier: QualityTier, isHandheld: boolean): number;
|
|
65
|
+
/**
|
|
66
|
+
* The share of its size each layer is made at, from the sizes the layers would have at full size (their boxes in CSS pixels), the share every layer is made at (`rasterOf`), the device's pixel ratio and the layer budget: a layer is
|
|
67
|
+
* made at the smaller of the share and the one that holds `budget` device pixels, never under a fifth. The floor comes first, then the planes, as `layoutOf` takes them.
|
|
68
|
+
*/
|
|
69
|
+
export declare function rasterShares(sizes: readonly (readonly [number, number])[], share: number, dpr: number, budget: number): number[];
|
|
70
|
+
/** How the governor judges the frames. Times are in milliseconds. */
|
|
71
|
+
export interface GovernorTuning {
|
|
72
|
+
/** The frames in a window it keeps (the oldest are dropped past twice this), and the fewest it judges: a window is also at least `settleMs` long, so a view at 60 frames a second judges 42 frames and one at 6 frames a second judges 8. */
|
|
73
|
+
frames: number;
|
|
74
|
+
minFrames: number;
|
|
75
|
+
settleMs: number;
|
|
76
|
+
/** The first frames after the view is mounted are slow whatever it holds (scripts are still compiling, the page is still being laid out): they are not judged. */
|
|
77
|
+
warmMs: number;
|
|
78
|
+
/** The mean interval between frames above which the view is over budget (21 is below 48 frames a second: a view that misses a refresh in three does not hold 60; it widens on a display that refreshes slower than 60 Hz), and the mean at or under which it is steady (18.5 holds 60 frames a second). */
|
|
79
|
+
budgetMs: number;
|
|
80
|
+
steadyMs: number;
|
|
81
|
+
/** How long the view must stay steady before it tries the tier above, and the most that wait grows to after steps that were taken back. */
|
|
82
|
+
upAfterMs: number;
|
|
83
|
+
maxUpAfterMs: number;
|
|
84
|
+
/** Frames are ignored for this long after a step, while the view is drawn again. */
|
|
85
|
+
quietMs: number;
|
|
86
|
+
/** A gap longer than this between two frames is a pause (a hidden page, a stopped turn), not a slow frame. */
|
|
87
|
+
pauseMs: number;
|
|
88
|
+
}
|
|
89
|
+
export declare const GOVERNOR_TUNING: GovernorTuning;
|
|
90
|
+
/** What the governor does with a frame. */
|
|
91
|
+
export interface Governor {
|
|
92
|
+
/** The tier now. */
|
|
93
|
+
readonly tier: QualityTier;
|
|
94
|
+
/** Give it the time since the last frame and the time now; it gives the new tier when it steps, else `undefined`. */
|
|
95
|
+
frame(interval: number, now: number): QualityTier | undefined;
|
|
96
|
+
/** The interval of the display, in milliseconds, as the page measured it with nothing else going on (16.7 for 60 Hz, 33.3 for a phone in a power-saving mode): a display that refreshes slower than 60 Hz is not taken for a view that is too slow. 16.7 until told. */
|
|
97
|
+
display(interval: number): void;
|
|
98
|
+
/** Forget the frames so far (the turn stopped, the page was hidden). */
|
|
99
|
+
pause(): void;
|
|
100
|
+
/** Put it on a tier without a step (a page chose one). */
|
|
101
|
+
set(tier: QualityTier, now?: number): void;
|
|
102
|
+
/** How long it now waits, steady, before it tries a tier up. */
|
|
103
|
+
readonly wait: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The governor of a view that turns. It keeps a window of frame intervals; when their mean is over budget it steps one tier down, and when the mean has been steady for `wait` it tries one tier up. A try that goes over budget
|
|
107
|
+
* is taken back, and every step down within thirty seconds of a step up doubles the wait (up to `maxUpAfterMs`), so a device on the edge between two tiers settles on the lower one and stays there. A step up that holds (steady
|
|
108
|
+
* for `settleMs` after it) leaves the wait as it was.
|
|
109
|
+
*/
|
|
110
|
+
export declare function createGovernor(start: QualityTier, tuning?: Partial<GovernorTuning>): Governor;
|
package/dist/quality.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// How much a 3D view draws, so that it turns well on every device. A view has three tiers of detail, `high` (everything, as it always was), `medium` and `low`, and a mode, `auto`, that chooses the tier by watching the frames
|
|
2
|
+
// the view really makes while it turns: nothing here looks at the browser's name or the device's model. A tier says what is left out or made smaller (the buildings round a district stand up fewer, the trees and the crowd are thinner, a
|
|
3
|
+
// small or far venue is a block with its height, the layers are rastered at a lower pixel ratio); the governor steps a tier down when the frames run over budget for a while and back up when they have been steady for longer, with a wait
|
|
4
|
+
// after a step up that fails that doubles each time, so that the view never flickers between two tiers. Pure: numbers in, numbers out; `turn.dom.ts` feeds it frames and `district.turn.ts` reads the tier's profile.
|
|
5
|
+
/** The tiers, highest first: a step down is one place later in this list. */
|
|
6
|
+
export const QUALITY_TIERS = ["high", "medium", "low"];
|
|
7
|
+
/** The values a page can ask for: `auto` and the tiers. */
|
|
8
|
+
export const QUALITIES = ["auto", ...QUALITY_TIERS];
|
|
9
|
+
/** The profile of each tier. `high` leaves everything as it always was (the cap on a handheld's pixel ratio is the mode `auto`'s and is made in `rasterOf`). */
|
|
10
|
+
export const TIER_PROFILES = {
|
|
11
|
+
high: { tier: "high", blocks: 130, trees: 1, proxy: 0, simple: false, outside: { people: 1, width: 1 }, ratio: { handheld: Infinity, other: Infinity } },
|
|
12
|
+
medium: { tier: "medium", blocks: 24, trees: 0.5, proxy: 0.12, simple: true, outside: { people: 0.5, width: 0.7 }, ratio: { handheld: 1.5, other: 2 } },
|
|
13
|
+
low: { tier: "low", blocks: 0, trees: 0.25, proxy: 0.4, simple: true, outside: { people: 0, width: 0.5 }, ratio: { handheld: 1, other: 1 } },
|
|
14
|
+
};
|
|
15
|
+
/** Whether a value is one of the qualities. */
|
|
16
|
+
export const isQuality = (value) => typeof value === "string" && QUALITIES.includes(value);
|
|
17
|
+
/** A quality from whatever a page or an address gave: the same if it is one, else `fallback`. */
|
|
18
|
+
export const qualityOf = (value, fallback = "auto") => (isQuality(value) ? value : fallback);
|
|
19
|
+
/** The tier a view with no frames to go by is drawn at: the tier asked for, and `high` for `auto` (a page made on a server, a first draw). */
|
|
20
|
+
export const tierOf = (quality) => (quality === undefined || quality === "auto" ? "high" : quality);
|
|
21
|
+
/** The tier one step lower or higher than `tier`, or the same at the end. */
|
|
22
|
+
export const lower = (tier) => QUALITY_TIERS[Math.min(QUALITY_TIERS.length - 1, QUALITY_TIERS.indexOf(tier) + 1)];
|
|
23
|
+
export const higher = (tier) => QUALITY_TIERS[Math.max(0, QUALITY_TIERS.indexOf(tier) - 1)];
|
|
24
|
+
/** What decides whether the device is a phone or a tablet: it has a coarse pointer, or its screen is small. It is asked of the page's own window, never of the browser's name. */
|
|
25
|
+
export function handheld(win) {
|
|
26
|
+
if (win === null || win === undefined)
|
|
27
|
+
return false;
|
|
28
|
+
if (win.matchMedia?.("(pointer: coarse)").matches === true)
|
|
29
|
+
return true;
|
|
30
|
+
const sides = [win.screen?.width, win.screen?.height, win.innerWidth].filter((n) => typeof n === "number" && n > 0);
|
|
31
|
+
return sides.length > 0 && Math.min(...sides) <= 820;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The share of its size a layer is made at, from the device's pixel ratio `dpr`: a layer is made `share` times as wide and as high in CSS pixels and turned back to its size by its matrix, so that it holds `share` squared as many device
|
|
35
|
+
* pixels, as far as the engine keeps a layer's pixels to its own box (the layers of a district at a ratio of 3 are hundreds of megabytes, which a phone's browser does not always keep). An engine that draws a layer again at the scale its matrix has
|
|
36
|
+
* (Chromium does) makes the same pixels from it, so there it changes nothing, and the sizes this package reports are the boxes times the ratio squared, not what an engine allocates. 1 when the ratio is within the cap. The mode `auto` caps a handheld
|
|
37
|
+
* at 2 even at `high`; a tier asked for by name is exactly that tier's, and `high` asked for by name is the device's own ratio, as it always was.
|
|
38
|
+
*/
|
|
39
|
+
export function rasterOf(quality, tier, dpr, isHandheld) {
|
|
40
|
+
const cap = quality === "auto" && tier === "high" ? (isHandheld ? 2 : Infinity) : TIER_PROFILES[tier].ratio[isHandheld ? "handheld" : "other"];
|
|
41
|
+
if (!(dpr > 0) || dpr <= cap)
|
|
42
|
+
return 1;
|
|
43
|
+
return Math.max(0.25, Math.round((cap / dpr) * 1000) / 1000);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The most device pixels one layer may hold in a tier: a flat floor of a building is a layer as big as the building's box (the Kodokan's fourteen plates are 47 megabytes each at a ratio of 3), so a layer past the budget is made smaller
|
|
47
|
+
* still (see `rasterOf`). `Infinity` at `high`, by name or in `auto`.
|
|
48
|
+
*/
|
|
49
|
+
export function layerBudget(tier, isHandheld) {
|
|
50
|
+
if (tier === "high")
|
|
51
|
+
return Infinity;
|
|
52
|
+
return { medium: isHandheld ? 2e6 : 4e6, low: isHandheld ? 1e6 : 2e6 }[tier];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The share of its size each layer is made at, from the sizes the layers would have at full size (their boxes in CSS pixels), the share every layer is made at (`rasterOf`), the device's pixel ratio and the layer budget: a layer is
|
|
56
|
+
* made at the smaller of the share and the one that holds `budget` device pixels, never under a fifth. The floor comes first, then the planes, as `layoutOf` takes them.
|
|
57
|
+
*/
|
|
58
|
+
export function rasterShares(sizes, share, dpr, budget) {
|
|
59
|
+
const ratio = dpr > 0 ? dpr : 1;
|
|
60
|
+
return sizes.map(([w, h]) => {
|
|
61
|
+
const area = w * h * ratio * ratio;
|
|
62
|
+
const own = area > budget ? Math.sqrt(budget / area) : 1;
|
|
63
|
+
return Math.max(0.2, Math.round(Math.min(share, own) * 1000) / 1000);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export const GOVERNOR_TUNING = { frames: 24, minFrames: 8, settleMs: 700, warmMs: 600, budgetMs: 21, steadyMs: 18.5, upAfterMs: 5000, maxUpAfterMs: 300000, quietMs: 600, pauseMs: 250 };
|
|
67
|
+
/**
|
|
68
|
+
* The governor of a view that turns. It keeps a window of frame intervals; when their mean is over budget it steps one tier down, and when the mean has been steady for `wait` it tries one tier up. A try that goes over budget
|
|
69
|
+
* is taken back, and every step down within thirty seconds of a step up doubles the wait (up to `maxUpAfterMs`), so a device on the edge between two tiers settles on the lower one and stays there. A step up that holds (steady
|
|
70
|
+
* for `settleMs` after it) leaves the wait as it was.
|
|
71
|
+
*/
|
|
72
|
+
export function createGovernor(start, tuning = {}) {
|
|
73
|
+
const t = { ...GOVERNOR_TUNING, ...tuning };
|
|
74
|
+
let tier = start;
|
|
75
|
+
let wait = t.upAfterMs;
|
|
76
|
+
let window = [];
|
|
77
|
+
let since = 0;
|
|
78
|
+
let quietUntil = 0;
|
|
79
|
+
let steadyFrom;
|
|
80
|
+
let lastUp = -Infinity;
|
|
81
|
+
let firstAt;
|
|
82
|
+
// The interval of the display: told by the page, which measures it with nothing else going on (a view that cannot keep up looks, from its frames alone, like a screen that refreshes more slowly).
|
|
83
|
+
let refresh = 16.7;
|
|
84
|
+
const reset = (now) => {
|
|
85
|
+
window = [];
|
|
86
|
+
since = now;
|
|
87
|
+
steadyFrom = undefined;
|
|
88
|
+
};
|
|
89
|
+
const step = (to, now) => {
|
|
90
|
+
tier = to;
|
|
91
|
+
quietUntil = now + t.quietMs;
|
|
92
|
+
reset(now);
|
|
93
|
+
return tier;
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
get tier() {
|
|
97
|
+
return tier;
|
|
98
|
+
},
|
|
99
|
+
get wait() {
|
|
100
|
+
return wait;
|
|
101
|
+
},
|
|
102
|
+
display(interval) {
|
|
103
|
+
if (Number.isFinite(interval))
|
|
104
|
+
refresh = Math.min(34, Math.max(6, interval));
|
|
105
|
+
},
|
|
106
|
+
pause() {
|
|
107
|
+
window = [];
|
|
108
|
+
steadyFrom = undefined;
|
|
109
|
+
},
|
|
110
|
+
set(next, now = 0) {
|
|
111
|
+
tier = next;
|
|
112
|
+
quietUntil = now + t.quietMs;
|
|
113
|
+
reset(now);
|
|
114
|
+
},
|
|
115
|
+
frame(interval, now) {
|
|
116
|
+
if (interval > t.pauseMs || interval <= 0) {
|
|
117
|
+
window = [];
|
|
118
|
+
steadyFrom = undefined;
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
firstAt ?? (firstAt = now);
|
|
122
|
+
if (now < quietUntil || now - firstAt < t.warmMs)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (window.length === 0)
|
|
125
|
+
since = now - interval;
|
|
126
|
+
window.push(interval);
|
|
127
|
+
if (window.length > t.frames * 2)
|
|
128
|
+
window.shift();
|
|
129
|
+
if (window.length < t.minFrames || now - since < t.settleMs)
|
|
130
|
+
return undefined;
|
|
131
|
+
const mean = window.reduce((s, x) => s + x, 0) / window.length;
|
|
132
|
+
const slow = refresh / 16.7;
|
|
133
|
+
const budget = Math.max(t.budgetMs, t.budgetMs * slow);
|
|
134
|
+
// Steady is steady on any display: only a view that really holds 60 frames a second tries a tier up.
|
|
135
|
+
const steady = t.steadyMs;
|
|
136
|
+
// Over budget is judged without the slowest frames of the window (a hitch, a script that ran in the middle of a turn is not a slow view); steady is judged with all of them.
|
|
137
|
+
const sorted = [...window].sort((x, y) => x - y).slice(0, window.length - Math.max(1, Math.floor(window.length / 12)));
|
|
138
|
+
const trimmed = sorted.reduce((sum, x) => sum + x, 0) / sorted.length;
|
|
139
|
+
if (trimmed > budget && tier !== "low") {
|
|
140
|
+
// A step down soon after a step up is a try that failed: the next try waits longer.
|
|
141
|
+
if (now - lastUp < 30000)
|
|
142
|
+
wait = Math.min(t.maxUpAfterMs, wait * 2);
|
|
143
|
+
return step(lower(tier), now);
|
|
144
|
+
}
|
|
145
|
+
if (mean <= steady) {
|
|
146
|
+
steadyFrom ?? (steadyFrom = now);
|
|
147
|
+
if (tier !== "high" && now - steadyFrom >= wait) {
|
|
148
|
+
lastUp = now;
|
|
149
|
+
return step(higher(tier), now);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
steadyFrom = undefined;
|
|
154
|
+
// Window turned over: judge the next frames afresh once it is full of them.
|
|
155
|
+
if (window.length >= t.frames * 2)
|
|
156
|
+
window = window.slice(t.frames);
|
|
157
|
+
return undefined;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|