@johnmorrisdotca/kyogijo-stadiums 0.40.2 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/NOTICE.md +22 -0
  3. package/README.md +1 -3
  4. package/dist/cityctx-entry.d.ts +3 -0
  5. package/dist/cityctx-entry.js +3 -0
  6. package/dist/cityctx.d.ts +19 -0
  7. package/dist/cityctx.data.d.ts +11 -0
  8. package/dist/cityctx.data.js +7139 -0
  9. package/dist/cityctx.js +34 -0
  10. package/dist/context.d.ts +175 -0
  11. package/dist/context.js +369 -0
  12. package/dist/crowd.d.ts +9 -0
  13. package/dist/district-entry.d.ts +16 -1
  14. package/dist/district-entry.js +9 -0
  15. package/dist/district.d.ts +99 -4
  16. package/dist/district.dom.d.ts +31 -0
  17. package/dist/district.dom.js +52 -0
  18. package/dist/district.js +103 -25
  19. package/dist/district.turn.js +89 -19
  20. package/dist/gates.d.ts +56 -0
  21. package/dist/gates.js +95 -0
  22. package/dist/halls.d.ts +2 -0
  23. package/dist/halls.js +1 -1
  24. package/dist/kodokan.d.ts +116 -0
  25. package/dist/kodokan.data.d.ts +61 -0
  26. package/dist/kodokan.data.js +20 -0
  27. package/dist/kodokan.js +92 -0
  28. package/dist/osm-entry.d.ts +2 -0
  29. package/dist/osm-entry.js +2 -0
  30. package/dist/osm.context.data.d.ts +10 -0
  31. package/dist/osm.context.data.js +1107 -0
  32. package/dist/outside.d.ts +43 -0
  33. package/dist/outside.dom.d.ts +44 -0
  34. package/dist/outside.dom.js +203 -0
  35. package/dist/outside.js +227 -0
  36. package/dist/outside.paint.d.ts +57 -0
  37. package/dist/outside.paint.js +88 -0
  38. package/dist/plateau-entry.d.ts +3 -0
  39. package/dist/plateau-entry.js +3 -0
  40. package/dist/plateau.credit.d.ts +5 -0
  41. package/dist/plateau.credit.js +7 -0
  42. package/dist/plateau.data.d.ts +7 -0
  43. package/dist/plateau.data.js +757 -0
  44. package/dist/stadiums.d.ts +14 -0
  45. package/dist/stadiums.data.js +2 -1
  46. package/dist/stadiums.js +2 -0
  47. package/dist/strings.d.ts +3 -1
  48. package/dist/strings.js +4 -4
  49. package/dist/turn.dom.d.ts +10 -0
  50. package/dist/turn.dom.js +21 -0
  51. package/dist/turn.kit.d.ts +12 -3
  52. package/dist/turn.kit.js +7 -0
  53. package/dist/turn.markup.js +15 -3
  54. package/dist/turn.model.d.ts +3 -0
  55. package/dist/turn.style.js +20 -3
  56. package/dist/venue.real.d.ts +22 -1
  57. package/dist/venue.real.js +76 -7
  58. package/dist/venues.d.ts +9 -1
  59. package/dist/venues.data.js +2 -1
  60. package/dist/venues.js +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/dist/walk.d.ts +56 -0
  64. package/dist/walk.data.d.ts +4 -0
  65. package/dist/walk.data.js +1295 -0
  66. package/dist/walk.js +120 -0
  67. package/package.json +25 -1
@@ -0,0 +1,34 @@
1
+ import { SEATTLE_CENTER, SEATTLE_CITY, SEATTLE_SODO, VANCOUVER_NATURE } from "./cityctx.data.js";
2
+ import { VANCOUVER_CENTRE } from "./osm.context.data.js";
3
+ /** The venues each area is drawn round, by their ids (the part of a district's key before the colon). */
4
+ export const CITYCTX_VENUES = Object.freeze({
5
+ vancouver: ["bc-place", "rogers-arena"],
6
+ sodo: ["lumen-field", "t-mobile-park"],
7
+ center: ["climate-pledge-arena"],
8
+ });
9
+ /**
10
+ * The areas of context that go with some venues, for a district's `context` option: downtown Vancouver's buildings and streets (`VANCOUVER_CENTRE`, of `./osm`) with its water, green and trees (`VANCOUVER_NATURE`) for BC Place and
11
+ * Rogers Arena; Seattle's SoDo for Lumen Field and T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own, with streets and the buildings that matter from far, for a district that holds venues of both (they are
12
+ * three kilometres apart, so the detail of either would be a speck). A venue of neither city has none.
13
+ * @example
14
+ * ```ts
15
+ * import { contextFor } from "@johnmorrisdotca/kyogijo-stadiums/cityctx";
16
+ * console.log(contextFor(["bc-place:cfl", "rogers-arena"]).map((area) => area.key));
17
+ * // [ 'vancouver', 'vancouver-nature' ]
18
+ * ```
19
+ */
20
+ export function contextFor(venues) {
21
+ const ids = new Set(venues.map((key) => key.split(":")[0]));
22
+ const has = (list) => list.some((id) => ids.has(id));
23
+ const areas = [];
24
+ if (has(CITYCTX_VENUES.vancouver))
25
+ areas.push(VANCOUVER_CENTRE, VANCOUVER_NATURE);
26
+ const [sodo, center] = [has(CITYCTX_VENUES.sodo), has(CITYCTX_VENUES.center)];
27
+ if (sodo && center)
28
+ areas.push(SEATTLE_CITY);
29
+ else if (sodo)
30
+ areas.push(SEATTLE_SODO);
31
+ else if (center)
32
+ areas.push(SEATTLE_CENTER);
33
+ return areas;
34
+ }
@@ -0,0 +1,175 @@
1
+ import { type LonLat } from "./venues.geo.ts";
2
+ import { type Plane, type Upright } from "./turn.kit.ts";
3
+ import type { Pt } from "./crowd.ts";
4
+ /** One layer of an area's data: its rows, and where they came from and under what terms, since a layer has a source of its own (buildings from PLATEAU; trees and land cover from another). */
5
+ export interface ContextLayer {
6
+ /** What it was made from: the dataset and its page. */
7
+ source: string;
8
+ /** The day the source was read. */
9
+ read: string;
10
+ licence: string;
11
+ /** The credit that goes with a drawing made from this layer, in English and in Japanese. */
12
+ attribution: {
13
+ en: string;
14
+ ja: string;
15
+ };
16
+ /** The layer's rows, each a list of whole numbers in grid steps of the area's frame (the row format is the layer's own, said at its name in `ContextArea.layers`). */
17
+ rows: readonly (readonly number[])[];
18
+ }
19
+ /**
20
+ * An area of context round a district: ONE coordinate frame (the origin, the grid step and the radius, in metres east and north of the origin) and separate layers laid in it, each with its own source and licence. A layer an
21
+ * area lacks is simply not drawn. The rows of each layer, all in grid steps from the origin:
22
+ * - `buildings`: the height and the rise of the roof (0 for a flat one), then the first point (east, north) and each step to the next.
23
+ * - `streets`: the kind (0 a carriageway at street level, 1 one on a viaduct or a bridge, 2 a railway, 3 a sidewalk or a path) and the width in grid steps, then the first point and each step to the next, a line.
24
+ * - `landCover`: the class (0 water, 1 green, 2 wood), then the first point and each step to the next, an outline (a hole is an outline of its own, filled even-odd).
25
+ * - `trees`: one row: 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.
26
+ */
27
+ export interface ContextArea {
28
+ key: string;
29
+ name: {
30
+ en: string;
31
+ ja: string;
32
+ };
33
+ /** The middle of the area, `[longitude, latitude]`: the point the rows' coordinates are measured from. */
34
+ origin: readonly [number, number];
35
+ /** How far from the middle the data reaches, in metres. */
36
+ radius: number;
37
+ /** The step the coordinates and the heights are counted in, in metres (a half). */
38
+ grid: number;
39
+ /** A building this tall, in metres, is a landmark that the plan's frame grows to take in; `CONTEXT_LANDMARK` when missing. A city of many tall buildings (Vancouver's downtown) sets it above them, so that the frame stays round the venues. */
40
+ landmark?: number;
41
+ layers: {
42
+ buildings?: ContextLayer;
43
+ streets?: ContextLayer;
44
+ trees?: ContextLayer;
45
+ landCover?: ContextLayer;
46
+ };
47
+ }
48
+ /** One building as the district holds it: its outline in feet in the district's frame (x to the right, y down the page), its height and the rise of its roof in feet. */
49
+ export interface ContextBlock {
50
+ /** A name for the building that is the same on every run: the area's key and its place in the area's list. */
51
+ id: string;
52
+ ring: Pt[];
53
+ /** The height of its top above the ground, in feet. */
54
+ height: number;
55
+ /** How far a pitched roof rises over its eaves, in feet: 0 for a flat roof. */
56
+ rise: number;
57
+ /** Its ground area, in square feet. */
58
+ area: number;
59
+ /** A venue stands in this building (Korakuen Hall's building, which would hide it): it is drawn as a ghost. */
60
+ ghost: boolean;
61
+ /** The middle of what is seen through a see-through building (the room or rooms raised in it), in feet: each wall gives way to the eye when the eye is on its side of that middle. Set only by `shellBlock`. */
62
+ xray?: Pt;
63
+ /** Whether the building lies wholly inside the plan's frame: only such a building stands up in the 3D view, one the frame cuts is a part of the floor. */
64
+ whole: boolean;
65
+ }
66
+ /** One street of the `streets` layer as the district holds it: its kind (0 a carriageway at street level, 1 a carriageway on a viaduct or a bridge, 2 a railway, 3 a sidewalk or a path), its width in feet and its line in feet in the district's frame. */
67
+ export interface ContextStreet {
68
+ kind: 0 | 1 | 2 | 3;
69
+ width: number;
70
+ points: Pt[];
71
+ }
72
+ /** A water, green or wood outline in feet in the district's frame (a hole is an outline of its own). */
73
+ export interface ContextShape {
74
+ /** 0 water, 1 green, 2 wood. */
75
+ cls: number;
76
+ ring: Pt[];
77
+ }
78
+ /** A tree in feet in the district's frame, its crown's radius in feet. */
79
+ export interface ContextTree {
80
+ at: Pt;
81
+ r: number;
82
+ }
83
+ /** What a district's context is: the buildings kept, and how many were left out because they are a venue's own. */
84
+ export interface ContextPlan {
85
+ blocks: ContextBlock[];
86
+ /** The streets, from a `streets` layer: drawn flat on the floor, under the buildings. */
87
+ streets?: ContextStreet[];
88
+ /** The water, green and wood outlines of a `landCover` layer that reach the frame: drawn flat, under the streets. */
89
+ land?: ContextShape[];
90
+ /** The trees of a `trees` layer inside the frame: drawn as dots, over the streets and under the buildings. */
91
+ trees?: ContextTree[];
92
+ /** The buildings of the model that are a venue (the Dome's own parts), left out: the venue draws them. */
93
+ owned: number;
94
+ /** The credits of the areas used, in each language. */
95
+ attribution: {
96
+ en: string[];
97
+ ja: string[];
98
+ };
99
+ }
100
+ /** How many buildings are raised as blocks in the 3D view (the biggest and the tallest of those on the plan): the rest are the floor's own, flat. Every block is a few elements of the page. */
101
+ export declare const CONTEXT_BLOCKS = 130;
102
+ /** Feet of the plan's frame beyond the venues' buildings that a district with context shows, so that the streets round them are in it: 150 metres. */
103
+ export declare const CONTEXT_REACH: number;
104
+ /** A building this tall, in metres, is a landmark: the plan's frame grows to take it in when it stands within `CONTEXT_LANDMARK_REACH` metres of the venues' buildings (Tokyo Dome Hotel, 155 metres, 370 metres from Korakuen Hall). */
105
+ export declare const CONTEXT_LANDMARK = 100;
106
+ export declare const CONTEXT_LANDMARK_REACH = 400;
107
+ /** The heights, in metres, that divide the buildings into four tones: low, middle, high and tall. */
108
+ export declare const CONTEXT_TONES: readonly number[];
109
+ /** A building of an area, decoded: its outline in metres east and north of the area's origin, its height and the rise of its roof in metres. */
110
+ export declare function decodeBuilding(area: Pick<ContextArea, "grid">, row: readonly number[]): {
111
+ ring: Pt[];
112
+ height: number;
113
+ rise: number;
114
+ };
115
+ /** A street of a `streets` layer, decoded: its line in metres east and north of the area's origin. The row is the kind, the width in grid steps, then the first point and each step to the next in grid steps. */
116
+ export declare function decodeStreet(area: Pick<ContextArea, "grid">, row: readonly number[]): Pt[];
117
+ /**
118
+ * The box a district with context is framed to, in feet in the district's frame: the venues' box widened by `CONTEXT_REACH` on every side, and grown to take in every landmark (a building of `CONTEXT_LANDMARK` metres or more) that stands within
119
+ * `CONTEXT_LANDMARK_REACH` metres of the venues' box, with a margin of its own. `box` is the venues' own.
120
+ */
121
+ export declare function contextFrame(areas: readonly ContextArea[], origin: LonLat, turn: number, box: {
122
+ x0: number;
123
+ y0: number;
124
+ x1: number;
125
+ y1: number;
126
+ }): {
127
+ x0: number;
128
+ y0: number;
129
+ x1: number;
130
+ y1: number;
131
+ };
132
+ /** A venue as the context needs it: its building's outline in the district's frame. */
133
+ export interface ContextVenue {
134
+ footprint: readonly Pt[];
135
+ /** The footprint is a building the venue draws itself (the Kodokan's, with its dojo in the air in it): the building of the model that is the same is the venue's, left out. */
136
+ shell?: boolean;
137
+ }
138
+ /**
139
+ * The buildings of some areas placed in a district: turned as the plan is, kept where they stand in the frame, and sorted into the venues' own (a building most of which lies inside a venue's outline is the venue's: it is left out, the
140
+ * venue draws it) and the ghosts (a building that holds a venue inside it). `origin` is the district's own origin (`[longitude, latitude]`), `turn` the degrees the map is turned, `frame` the plan's box in feet.
141
+ */
142
+ export declare function placeContext(areas: readonly ContextArea[], origin: LonLat, turn: number, frame: {
143
+ x: number;
144
+ y: number;
145
+ w: number;
146
+ h: number;
147
+ }, venues: readonly ContextVenue[]): ContextPlan;
148
+ /** The tone (0 low to 3 tall) of a building of `feet` height. */
149
+ export declare const toneOf: (feet: number) => number;
150
+ /** 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. */
151
+ export declare function streetsFloor(streets: readonly ContextStreet[]): string;
152
+ /** Water, green and wood flat on the floor, the lowest of what a context draws: one path for each class, filled even-odd so that a hole is a hole. */
153
+ export declare function landFloor(land: readonly ContextShape[]): string;
154
+ /** Trees as dots: one path for each of three sizes (the crown's diameter on the floor, in feet), a dot being a round cap on a line of no length, so that a thousand trees are three elements. */
155
+ export declare function treesFloor(trees: readonly ContextTree[]): string;
156
+ /**
157
+ * What lies flat on the floor of a district under the venues, from the lowest up: water, green and wood, the streets, the trees as dots, and the buildings in four tones by height with the ghosts. All of it faint, so that a crowd
158
+ * reads on it. Empty for a district with no context.
159
+ */
160
+ export declare function contextFloor(plan: ContextPlan): string;
161
+ /** One building as the planes of a block: its walls up to the eaves, and over them a flat roof, or a hip roof where it has a pitched one. A ghost's planes are always mostly cut away, so that a venue inside it shows. */
162
+ export declare function blockPlanes(b: ContextBlock, prefix: string): Plane[];
163
+ /**
164
+ * A floor of a building as a slab: a top face at `level` and sides down `thickness` below it, shaded by the way each side faces, so that it is a solid piece with an edge from every side and never a sheet one pixel thick.
165
+ * `look`: `chosen` is picked out (its top is the blue plate, drawn under whatever stands on it), `faint` is a floor of the building that is not the one chosen, `plain` has no top (a floor that is drawn by something else, a dojo's mats).
166
+ * `top` and `side` are the ids of the planes (a side's has its place in the ring after it).
167
+ */
168
+ export declare function slabPlanes(ring: readonly Pt[], level: number, thickness: number, ids: {
169
+ top: string;
170
+ side: string;
171
+ }, look: "chosen" | "faint" | "plain"): Plane[];
172
+ /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
173
+ export declare function contextUprights(plan: ContextPlan, most?: number): Upright[];
174
+ /** The building a raised venue stands in as a block: its outline in feet, its height, `xray` the middle of what is seen through its walls (none for a solid one). */
175
+ export declare function shellBlock(key: string, ring: readonly Pt[], height: number, xray: Pt | undefined): ContextBlock;
@@ -0,0 +1,369 @@
1
+ // The buildings round a district, drawn low in detail so that a district stands in its streets and not on a bare grid: each is the outline of its roof edge, a height and, for some, a pitched roof, cut down at build time from
2
+ // Project PLATEAU's 3D city model (`scripts/build-plateau.mjs`, `plateau.data.ts`). Nothing here reads a tile or makes a request: an area is plain data a caller passes in (`context: [TOKYO_DOME_CITY]`), so a page that does not
3
+ // ask for one carries none. This file places an area's buildings in a district's frame, takes out the ones that are a venue's own (a venue is drawn from its own outline and shell), and draws the rest: flat on the district's
4
+ // floor, shaded by height, and the biggest and tallest as blocks standing on it in the 3D view. Pure: data in, markup and planes out.
5
+ import { FEET_PER_METRE } from "./geometry.js";
6
+ import { boxOf, hullOf, inside, metresFrom } from "./venues.geo.js";
7
+ import { n, shy, wall } from "./turn.kit.js";
8
+ import { pointList, raked } from "./venue.geom.js";
9
+ /** How many buildings are raised as blocks in the 3D view (the biggest and the tallest of those on the plan): the rest are the floor's own, flat. Every block is a few elements of the page. */
10
+ export const CONTEXT_BLOCKS = 130;
11
+ /** Feet of the plan's frame beyond the venues' buildings that a district with context shows, so that the streets round them are in it: 150 metres. */
12
+ export const CONTEXT_REACH = 150 * FEET_PER_METRE;
13
+ /** A building this tall, in metres, is a landmark: the plan's frame grows to take it in when it stands within `CONTEXT_LANDMARK_REACH` metres of the venues' buildings (Tokyo Dome Hotel, 155 metres, 370 metres from Korakuen Hall). */
14
+ export const CONTEXT_LANDMARK = 100;
15
+ export const CONTEXT_LANDMARK_REACH = 400;
16
+ /** The heights, in metres, that divide the buildings into four tones: low, middle, high and tall. */
17
+ export const CONTEXT_TONES = [15, 45, 100];
18
+ const RAD = Math.PI / 180;
19
+ const rotate = ([x, y], degrees) => [x * Math.cos(degrees * RAD) - y * Math.sin(degrees * RAD), x * Math.sin(degrees * RAD) + y * Math.cos(degrees * RAD)];
20
+ const centroid = (ring) => [ring.reduce((s, p) => s + p[0], 0) / ring.length, ring.reduce((s, p) => s + p[1], 0) / ring.length];
21
+ /** The signed area of a ring: positive going clockwise on a page with y down. */
22
+ const signedArea = (ring) => ring.reduce((s, [x, y], i) => s + (x * ring[(i + 1) % ring.length][1] - ring[(i + 1) % ring.length][0] * y), 0) / 2;
23
+ /** A building of an area, decoded: its outline in metres east and north of the area's origin, its height and the rise of its roof in metres. */
24
+ export function decodeBuilding(area, row) {
25
+ const g = area.grid;
26
+ const ring = [];
27
+ let [x, y] = [row[2], row[3]];
28
+ ring.push([x * g, y * g]);
29
+ for (let i = 4; i + 1 < row.length; i += 2) {
30
+ x += row[i];
31
+ y += row[i + 1];
32
+ ring.push([x * g, y * g]);
33
+ }
34
+ return { ring, height: row[0] * g, rise: row[1] * g };
35
+ }
36
+ /** A street of a `streets` layer, decoded: its line in metres east and north of the area's origin. The row is the kind, the width in grid steps, then the first point and each step to the next in grid steps. */
37
+ export function decodeStreet(area, row) {
38
+ const g = area.grid;
39
+ const line = [];
40
+ let [x, y] = [row[2], row[3]];
41
+ line.push([x * g, y * g]);
42
+ for (let i = 4; i + 1 < row.length; i += 2) {
43
+ x += row[i];
44
+ y += row[i + 1];
45
+ line.push([x * g, y * g]);
46
+ }
47
+ return line;
48
+ }
49
+ /**
50
+ * The box a district with context is framed to, in feet in the district's frame: the venues' box widened by `CONTEXT_REACH` on every side, and grown to take in every landmark (a building of `CONTEXT_LANDMARK` metres or more) that stands within
51
+ * `CONTEXT_LANDMARK_REACH` metres of the venues' box, with a margin of its own. `box` is the venues' own.
52
+ */
53
+ export function contextFrame(areas, origin, turn, box) {
54
+ let frame = { x0: box.x0 - CONTEXT_REACH, y0: box.y0 - CONTEXT_REACH, x1: box.x1 + CONTEXT_REACH, y1: box.y1 + CONTEXT_REACH };
55
+ const far = CONTEXT_LANDMARK_REACH * FEET_PER_METRE;
56
+ const near = { x0: box.x0 - far, y0: box.y0 - far, x1: box.x1 + far, y1: box.y1 + far };
57
+ for (const area of areas) {
58
+ const [east, north] = metresFrom(origin, [area.origin])[0];
59
+ for (const row of area.layers.buildings?.rows ?? []) {
60
+ if (row[0] * area.grid < (area.landmark ?? CONTEXT_LANDMARK))
61
+ continue;
62
+ const b = decodeBuilding(area, row);
63
+ const ring = b.ring.map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
64
+ const at = boxOf(ring);
65
+ if (at.x1 < near.x0 || at.x0 > near.x1 || at.y1 < near.y0 || at.y0 > near.y1)
66
+ continue;
67
+ const m = 30 * FEET_PER_METRE;
68
+ frame = { x0: Math.min(frame.x0, at.x0 - m), y0: Math.min(frame.y0, at.y0 - m), x1: Math.max(frame.x1, at.x1 + m), y1: Math.max(frame.y1, at.y1 + m) };
69
+ }
70
+ }
71
+ return frame;
72
+ }
73
+ /**
74
+ * The buildings of some areas placed in a district: turned as the plan is, kept where they stand in the frame, and sorted into the venues' own (a building most of which lies inside a venue's outline is the venue's: it is left out, the
75
+ * venue draws it) and the ghosts (a building that holds a venue inside it). `origin` is the district's own origin (`[longitude, latitude]`), `turn` the degrees the map is turned, `frame` the plan's box in feet.
76
+ */
77
+ export function placeContext(areas, origin, turn, frame, venues) {
78
+ const blocks = [];
79
+ let owned = 0;
80
+ const hulls = venues.map((v) => hullOf([...v.footprint]));
81
+ const [fx0, fy0, fx1, fy1] = [frame.x, frame.y, frame.x + frame.w, frame.y + frame.h];
82
+ for (const area of areas) {
83
+ const [east, north] = metresFrom(origin, [area.origin])[0];
84
+ (area.layers.buildings?.rows ?? []).forEach((row, index) => {
85
+ const b = decodeBuilding(area, row);
86
+ const ring = b.ring.map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
87
+ const box = boxOf(ring);
88
+ // Wholly outside the frame: not drawn at all.
89
+ if (box.x1 < fx0 || box.x0 > fx1 || box.y1 < fy0 || box.y0 > fy1)
90
+ return;
91
+ const middle = centroid(ring);
92
+ const share = (poly) => ring.filter((p) => inside(poly, p)).length / ring.length;
93
+ // A venue inside the building (its middle and at least half of its outline's points): the building is a ghost round it.
94
+ const holds = venues.some((v) => v.shell !== true && inside(ring, centroid(v.footprint)) && v.footprint.filter((p) => inside(ring, p)).length * 2 >= v.footprint.length);
95
+ if (!holds && hulls.some((hull) => inside(hull, middle) || share(hull) >= 0.7)) {
96
+ owned += 1;
97
+ return;
98
+ }
99
+ blocks.push({ id: `${area.key}-${index}`, ring, height: b.height * FEET_PER_METRE, rise: b.rise * FEET_PER_METRE, area: Math.abs(signedArea(ring)), ghost: holds, whole: box.x0 >= fx0 && box.x1 <= fx1 && box.y0 >= fy0 && box.y1 <= fy1 });
100
+ });
101
+ }
102
+ // The streets of the areas: each a line with a width, kept where it reaches into the frame.
103
+ const streets = [];
104
+ for (const area of areas) {
105
+ const [east, north] = metresFrom(origin, [area.origin])[0];
106
+ for (const row of area.layers.streets?.rows ?? []) {
107
+ const points = decodeStreet(area, row).map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
108
+ const box = boxOf(points);
109
+ if (box.x1 < fx0 || box.x0 > fx1 || box.y1 < fy0 || box.y0 > fy1)
110
+ continue;
111
+ streets.push({ kind: row[0], width: row[1] * area.grid * FEET_PER_METRE, points });
112
+ }
113
+ }
114
+ // The water, green and wood of the areas, and their trees.
115
+ const land = [];
116
+ const trees = [];
117
+ for (const area of areas) {
118
+ const [east, north] = metresFrom(origin, [area.origin])[0];
119
+ const g = area.grid;
120
+ const put = (x, y) => rotate([(x * g + east) * FEET_PER_METRE, -(y * g + north) * FEET_PER_METRE], turn);
121
+ for (const row of area.layers.landCover?.rows ?? []) {
122
+ const ring = [];
123
+ let [x, y] = [row[1], row[2]];
124
+ ring.push(put(x, y));
125
+ for (let i = 3; i + 1 < row.length; i += 2) {
126
+ x += row[i];
127
+ y += row[i + 1];
128
+ ring.push(put(x, y));
129
+ }
130
+ const box = boxOf(ring);
131
+ if (!(box.x1 < fx0 || box.x0 > fx1 || box.y1 < fy0 || box.y0 > fy1))
132
+ land.push({ cls: row[0], ring });
133
+ }
134
+ for (const row of area.layers.trees?.rows ?? []) {
135
+ let [x, y] = [row[0], row[1]];
136
+ for (let i = 0; i + 2 < row.length; i += 3) {
137
+ if (i > 0) {
138
+ x += row[i];
139
+ y += row[i + 1];
140
+ }
141
+ const at = put(x, y);
142
+ if (at[0] >= fx0 && at[0] <= fx1 && at[1] >= fy0 && at[1] <= fy1)
143
+ trees.push({ at, r: row[i + 2] * g * FEET_PER_METRE });
144
+ }
145
+ }
146
+ }
147
+ // An empty credit (a layer whose credit another layer of the area has said) adds nothing.
148
+ const layers = areas.flatMap((a) => Object.values(a.layers));
149
+ const credits = (language) => [...new Set(layers.map((l) => l.attribution[language]).filter((text) => text !== ""))];
150
+ return { blocks, streets, land, trees, owned, attribution: { en: credits("en"), ja: credits("ja") } };
151
+ }
152
+ /** The tone (0 low to 3 tall) of a building of `feet` height. */
153
+ export const toneOf = (feet) => CONTEXT_TONES.filter((m) => feet / FEET_PER_METRE >= m).length;
154
+ const num = (value) => String(Math.round(value * 100) / 100);
155
+ /** 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. */
156
+ export function streetsFloor(streets) {
157
+ if (streets.length === 0)
158
+ return "";
159
+ const groups = new Map();
160
+ for (const s of streets) {
161
+ const key = `${s.kind} ${Math.max(1, Math.round(s.width))}`;
162
+ const list = groups.get(key) ?? [];
163
+ list.push(`M${s.points.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}`);
164
+ groups.set(key, list);
165
+ }
166
+ // Under: street-level carriageways, then railways, then sidewalks and paths, then viaducts (which cross over the rest).
167
+ const order = [0, 2, 3, 1];
168
+ const keys = [...groups.keys()].sort((a, b) => order.indexOf(Number(a.split(" ")[0])) - order.indexOf(Number(b.split(" ")[0])) || Number(b.split(" ")[1]) - Number(a.split(" ")[1]));
169
+ return `<g class="ky-dist-streets" aria-hidden="true" fill="none">${keys.map((key) => `<path class="ky-dist-street ky-dist-street-${key.split(" ")[0]}" stroke-width="${key.split(" ")[1]}" d="${groups.get(key).join("")}"/>`).join("")}</g>`;
170
+ }
171
+ const one = (value) => String(Math.round(value * 10) / 10);
172
+ /** Water, green and wood flat on the floor, the lowest of what a context draws: one path for each class, filled even-odd so that a hole is a hole. */
173
+ export function landFloor(land) {
174
+ const classes = ["ky-dist-water", "ky-dist-green", "ky-dist-wood"];
175
+ return classes.map((cls, i) => {
176
+ const rings = land.filter((s) => s.cls === i);
177
+ return rings.length === 0 ? "" : `<path class="ky-dist-land ${cls}" fill-rule="evenodd" d="${rings.map((s) => `M${s.ring.map(([x, y]) => `${one(x)} ${one(y)}`).join("L")}Z`).join("")}"/>`;
178
+ }).join("");
179
+ }
180
+ /** Trees as dots: one path for each of three sizes (the crown's diameter on the floor, in feet), a dot being a round cap on a line of no length, so that a thousand trees are three elements. */
181
+ export function treesFloor(trees) {
182
+ const sizes = [[0, 11, 9], [11, 17, 14], [17, Infinity, 20]];
183
+ return sizes.map(([from, to, width]) => {
184
+ const dots = trees.filter((t) => t.r * 2 >= from && t.r * 2 < to);
185
+ return dots.length === 0 ? "" : `<path class="ky-dist-tree" stroke-width="${width}" d="${dots.map((t) => `M${one(t.at[0])} ${one(t.at[1])}h0`).join("")}"/>`;
186
+ }).join("");
187
+ }
188
+ /**
189
+ * What lies flat on the floor of a district under the venues, from the lowest up: water, green and wood, the streets, the trees as dots, and the buildings in four tones by height with the ghosts. All of it faint, so that a crowd
190
+ * reads on it. Empty for a district with no context.
191
+ */
192
+ export function contextFloor(plan) {
193
+ const under = `${landFloor(plan.land ?? [])}${streetsFloor(plan.streets ?? [])}${treesFloor(plan.trees ?? [])}`;
194
+ if (plan.blocks.length === 0)
195
+ return under;
196
+ const paths = [[], [], [], [], []];
197
+ for (const b of plan.blocks)
198
+ paths[b.ghost ? 4 : toneOf(b.height)].push(`M${b.ring.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z`);
199
+ const classes = ["ky-dist-b0", "ky-dist-b1", "ky-dist-b2", "ky-dist-b3", "ky-dist-ghost"];
200
+ return `${under}<g class="ky-dist-context" aria-hidden="true">${paths.map((d, i) => (d.length === 0 ? "" : `<path class="ky-dist-block ${classes[i]}" d="${d.join("")}"/>`)).join("")}</g>`;
201
+ }
202
+ // ---- The blocks of the 3D view. ----
203
+ /** How much a building matters to the view: tall and wide first. */
204
+ const weight = (b) => b.height * Math.sqrt(b.area);
205
+ /** The principal axis of a ring, as a unit direction, and the extent of the ring along it and across it, from its middle. */
206
+ function axisOf(ring) {
207
+ const [mx, my] = centroid(ring);
208
+ let [sxx, sxy, syy] = [0, 0, 0];
209
+ for (const [x, y] of ring) {
210
+ sxx += (x - mx) ** 2;
211
+ sxy += (x - mx) * (y - my);
212
+ syy += (y - my) ** 2;
213
+ }
214
+ const angle = 0.5 * Math.atan2(2 * sxy, sxx - syy);
215
+ const along = [Math.cos(angle), Math.sin(angle)];
216
+ const across = [-along[1], along[0]];
217
+ const ts = ring.map(([x, y]) => (x - mx) * along[0] + (y - my) * along[1]);
218
+ const ss = ring.map(([x, y]) => (x - mx) * across[0] + (y - my) * across[1]);
219
+ return { along, t0: Math.min(...ts), t1: Math.max(...ts), s0: Math.min(...ss), s1: Math.max(...ss) };
220
+ }
221
+ /** Whether a ring is convex (either way round). */
222
+ const convex = (ring) => {
223
+ let sign = 0;
224
+ for (let i = 0; i < ring.length; i += 1) {
225
+ const [a, b, c] = [ring[i], ring[(i + 1) % ring.length], ring[(i + 2) % ring.length]];
226
+ const k = (b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0]);
227
+ if (Math.abs(k) < 1e-9)
228
+ continue;
229
+ if (sign === 0)
230
+ sign = Math.sign(k);
231
+ else if (Math.sign(k) !== sign)
232
+ return false;
233
+ }
234
+ return true;
235
+ };
236
+ /** The plane a set of points with heights lies nearest to (least squares), as a slope. */
237
+ function slopeThrough(points) {
238
+ const m = points.length;
239
+ const [mx, my, mz] = [points.reduce((s, p) => s + p[0], 0) / m, points.reduce((s, p) => s + p[1], 0) / m, points.reduce((s, p) => s + p[2], 0) / m];
240
+ let [sxx, sxy, syy, sxz, syz] = [0, 0, 0, 0, 0];
241
+ for (const [x, y, z] of points) {
242
+ sxx += (x - mx) ** 2;
243
+ sxy += (x - mx) * (y - my);
244
+ syy += (y - my) ** 2;
245
+ sxz += (x - mx) * (z - mz);
246
+ syz += (y - my) * (z - mz);
247
+ }
248
+ const det = sxx * syy - sxy * sxy;
249
+ const [gx, gy] = Math.abs(det) < 1e-9 ? [0, 0] : [(sxz * syy - syz * sxy) / det, (syz * sxx - sxz * sxy) / det];
250
+ return { x0: mx, y0: my, z0: mz, gx, gy };
251
+ }
252
+ /** The faces of a hip roof over a ring: from each edge of the ring (at the eaves) up to a ridge along the ring's long axis (at the top). A face is a quad or a triangle. */
253
+ function hipFaces(ring, eave, top) {
254
+ const { along, t0, t1, s0, s1 } = axisOf(ring);
255
+ const [mx, my] = centroid(ring);
256
+ const across = [-along[1], along[0]];
257
+ const half = (s1 - s0) / 2;
258
+ const sm = (s0 + s1) / 2;
259
+ const tm = (t0 + t1) / 2;
260
+ // The ridge runs between t0 + half and t1 - half (a point at the middle where the ring is no longer than it is wide).
261
+ const [r0, r1] = t1 - t0 > 2 * half ? [t0 + half, t1 - half] : [tm, tm];
262
+ const at = (t) => [mx + along[0] * t + across[0] * sm, my + along[1] * t + across[1] * sm];
263
+ const nearest = (p) => at(Math.max(r0, Math.min(r1, (p[0] - mx) * along[0] + (p[1] - my) * along[1])));
264
+ const faces = [];
265
+ ring.forEach((a, i) => {
266
+ const b = ring[(i + 1) % ring.length];
267
+ const [ra, rb] = [nearest(a), nearest(b)];
268
+ const poly = [a, b];
269
+ const heights = [eave, eave];
270
+ if (Math.hypot(rb[0] - b[0], rb[1] - b[1]) > 1e-6) {
271
+ poly.push(rb);
272
+ heights.push(top);
273
+ }
274
+ if (Math.hypot(ra[0] - rb[0], ra[1] - rb[1]) > 1e-6) {
275
+ poly.push(ra);
276
+ heights.push(top);
277
+ }
278
+ if (poly.length >= 3)
279
+ faces.push({ poly, heights });
280
+ });
281
+ return faces;
282
+ }
283
+ /** The light on a wall by the way it faces on the district's plan: three tones, from the side the light comes from (up the page and to the left) to the side in shadow. */
284
+ const sideOf = (a, b, wound) => {
285
+ const [dx, dy] = [b[0] - a[0], b[1] - a[1]];
286
+ const long = Math.hypot(dx, dy) || 1;
287
+ // On a page with y down, the outward normal of a ring wound clockwise is to the left of the walk from a to b, of one wound counter-clockwise to the right.
288
+ const [nx, ny] = wound > 0 ? [dy / long, -dx / long] : [-dy / long, dx / long];
289
+ const lit = nx * -0.45 + ny * -0.55;
290
+ return lit > 0.25 ? 0 : lit > -0.2 ? 1 : 2;
291
+ };
292
+ const SIDES = ["u-block-a", "u-block-b", "u-block-c"];
293
+ /** One building as the planes of a block: its walls up to the eaves, and over them a flat roof, or a hip roof where it has a pitched one. A ghost's planes are always mostly cut away, so that a venue inside it shows. */
294
+ export function blockPlanes(b, prefix) {
295
+ const planes = [];
296
+ const wound = signedArea(b.ring);
297
+ const pitched = b.rise > 3 && b.ring.length <= 8 && convex(b.ring);
298
+ const eave = pitched ? Math.max(b.height - b.rise, 3) : b.height;
299
+ const ghost = (p) => (b.ghost ? { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.16 } : p);
300
+ // 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
+ const xray = b.xray;
302
+ const wall1 = (p) => (xray === undefined ? ghost(p) : { ...shy(p, xray), fadeTo: 0.06, fadeSide: true });
303
+ const roof1 = (p) => (xray === undefined ? ghost(p) : { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.1 });
304
+ b.ring.forEach((p, i) => {
305
+ const q = b.ring[(i + 1) % b.ring.length];
306
+ const long = Math.hypot(q[0] - p[0], q[1] - p[1]);
307
+ if (long < 3)
308
+ return;
309
+ planes.push(wall1(wall(`${prefix}w${i}`, "block", p[0], p[1], q[0], q[1], 0, eave, `<rect class="u-block ${SIDES[sideOf(p, q, wound)]}" width="${n(long)}" height="${n(eave)}"/>`)));
310
+ });
311
+ const roof = (id, poly, slope, along) => {
312
+ const p = raked(id, "block", poly, slope, along, (corners) => `<polygon class="u-block-roof" points="${pointList(corners)}"/>`);
313
+ if (p !== undefined)
314
+ planes.push(roof1(p));
315
+ };
316
+ if (!pitched) {
317
+ roof(`${prefix}top`, b.ring, { x0: b.ring[0][0], y0: b.ring[0][1], z0: b.height, gx: 0, gy: 0 }, [1, 0]);
318
+ return planes;
319
+ }
320
+ hipFaces(b.ring, eave, b.height).forEach((face, i) => {
321
+ const [p, q] = face.poly;
322
+ const long = Math.hypot(q[0] - p[0], q[1] - p[1]) || 1;
323
+ roof(`${prefix}r${i}`, face.poly, slopeThrough(face.poly.map((pt, k) => [pt[0], pt[1], face.heights[k]])), [(q[0] - p[0]) / long, (q[1] - p[1]) / long]);
324
+ });
325
+ return planes;
326
+ }
327
+ const SLAB_SIDES = ["u-slab-a", "u-slab-b", "u-slab-c"];
328
+ /**
329
+ * A floor of a building as a slab: a top face at `level` and sides down `thickness` below it, shaded by the way each side faces, so that it is a solid piece with an edge from every side and never a sheet one pixel thick.
330
+ * `look`: `chosen` is picked out (its top is the blue plate, drawn under whatever stands on it), `faint` is a floor of the building that is not the one chosen, `plain` has no top (a floor that is drawn by something else, a dojo's mats).
331
+ * `top` and `side` are the ids of the planes (a side's has its place in the ring after it).
332
+ */
333
+ export function slabPlanes(ring, level, thickness, ids, look) {
334
+ const wound = signedArea(ring);
335
+ const planes = [];
336
+ const faint = (p) => (look === "faint" ? { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.18 } : p);
337
+ ring.forEach((p, i) => {
338
+ const q = ring[(i + 1) % ring.length];
339
+ const long = Math.hypot(q[0] - p[0], q[1] - p[1]);
340
+ if (long < 1)
341
+ return;
342
+ planes.push(faint(wall(`${ids.side}${i}`, "floor", p[0], p[1], q[0], q[1], level - thickness, level, `<rect class="u-slab ${SLAB_SIDES[sideOf(p, q, wound)]}" width="${n(long)}" height="${n(thickness)}"/>`)));
343
+ });
344
+ if (look !== "plain") {
345
+ const top = raked(ids.top, "floor", ring, { x0: ring[0][0], y0: ring[0][1], z0: level, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="${look === "chosen" ? "u-floorplate" : "u-slab-top"}" points="${pointList(corners)}"/>`);
346
+ if (top !== undefined)
347
+ planes.push(faint(look === "chosen" ? { ...top, under: true } : top));
348
+ }
349
+ return planes;
350
+ }
351
+ /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
352
+ export function contextUprights(plan, most = CONTEXT_BLOCKS) {
353
+ const wanted = new Set(plan.blocks.filter((b) => b.whole).sort((a, b) => weight(b) - weight(a) || (a.id < b.id ? -1 : 1)).slice(0, Math.max(0, most)).map((b) => b.id));
354
+ const items = [];
355
+ for (const b of plan.blocks) {
356
+ if (!wanted.has(b.id))
357
+ continue;
358
+ const planes = blockPlanes(b, `c-${b.id}-`);
359
+ if (planes.length === 0)
360
+ continue;
361
+ const [x, y] = centroid(b.ring);
362
+ items.push({ id: `c-${b.id}`, kind: "blocks", at: [x, y], height: b.height, planes });
363
+ }
364
+ return items;
365
+ }
366
+ /** The building a raised venue stands in as a block: its outline in feet, its height, `xray` the middle of what is seen through its walls (none for a solid one). */
367
+ export function shellBlock(key, ring, height, xray) {
368
+ return { id: `shell-${key}`, ring: [...ring], height, rise: 0, area: Math.abs(signedArea(ring)), ghost: false, whole: true, ...(xray === undefined ? {} : { xray }) };
369
+ }
package/dist/crowd.d.ts CHANGED
@@ -151,6 +151,15 @@ export interface RealShell {
151
151
  rise: number;
152
152
  opening: Pt[];
153
153
  };
154
+ /** The height of the building's podium, a lower band of its walls under the concourse levels, in feet; the walls are drawn in two bands where a venue has one (ours where the sources do not say). */
155
+ podium?: number;
156
+ /** A `fixed` roof closed in the middle by a cap with a long skylight along the surface's long side (Rogers Arena's): ours, from the brief. */
157
+ skylight?: boolean;
158
+ /** A `cable` roof's masts round its rim: how many, and how high they rise over the rim, in feet. */
159
+ masts?: {
160
+ count: number;
161
+ height: number;
162
+ };
154
163
  /** The height of the roof's lowest edge above the surface, in feet. */
155
164
  edge: number;
156
165
  }
@@ -1,5 +1,20 @@
1
1
  export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, describeDistrict, districtId, districtMember, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.ts";
2
- export type { DistrictMember, DistrictMode, DistrictOptions, DistrictPlaced, DistrictPlan, DistrictVenueInput } from "./district.ts";
2
+ export type { DistrictMember, DistrictMode, DistrictOptions, DistrictPart, DistrictPlaced, DistrictPlan, DistrictVenueInput, PlacedPart } from "./district.ts";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.ts";
4
4
  export type { DistrictTurnSettings } from "./district.turn.ts";
5
+ export type { ContextArea } from "./context.ts";
6
+ export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_FLOOR_IDS, KODOKAN_FLOORS, KODOKAN_ROOM_HEIGHT, kodokanLevel, kodokanMember } from "./kodokan.ts";
7
+ export type { KodokanFloor, KodokanRoom } from "./kodokan.ts";
5
8
  export { VENUE_ATTRIBUTION } from "./venues.ts";
9
+ export { mountDistrict } from "./district.dom.ts";
10
+ export type { KyogijoDistrict } from "./district.dom.ts";
11
+ export { OUTSIDE_DENSITY, OUTSIDE_LEVELS, OUTSIDE_REACH, OUTSIDE_VENUES, hasOutside, isOutsideLevel, outsideCount, outsideCrowd, outsideLevelOf } from "./outside.ts";
12
+ export type { OutsideCrowd, OutsideLevel, OutsideOptions } from "./outside.ts";
13
+ export { PERSON_METRES, frameOf, rasterOutside } from "./outside.paint.ts";
14
+ export type { OutsideFrame, OutsideStats } from "./outside.paint.ts";
15
+ export { OUTSIDE_ROOM } from "./district.ts";
16
+ export { WALK_CELL, WALK_IDS, walkMask, walkable } from "./walk.ts";
17
+ export type { WalkData, WalkMask } from "./walk.ts";
18
+ export { GATE_FACTS, GATE_VENUES, gateName, venueGates } from "./gates.ts";
19
+ export type { Gate, GateBasis, GateFact } from "./gates.ts";
20
+ export type { DistrictGate } from "./district.ts";
@@ -1,4 +1,13 @@
1
1
  // A district: several real venues of one city on one plan at their real relative positions and orientations (OpenStreetMap, ODbL 1.0), in 2D and in 3D, tight round neighbours or at the real distances of a city.
2
2
  export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, describeDistrict, districtId, districtMember, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.js";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.js";
4
+ export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_FLOOR_IDS, KODOKAN_FLOORS, KODOKAN_ROOM_HEIGHT, kodokanLevel, kodokanMember } from "./kodokan.js";
4
5
  export { VENUE_ATTRIBUTION } from "./venues.js";
6
+ export { mountDistrict } from "./district.dom.js";
7
+ // The crowd outside the venues, on ground people may stand on (OpenStreetMap, ODbL 1.0), at 0, 25, 50, 75 or 100 percent of the seats.
8
+ export { OUTSIDE_DENSITY, OUTSIDE_LEVELS, OUTSIDE_REACH, OUTSIDE_VENUES, hasOutside, isOutsideLevel, outsideCount, outsideCrowd, outsideLevelOf } from "./outside.js";
9
+ export { PERSON_METRES, frameOf, rasterOutside } from "./outside.paint.js";
10
+ export { OUTSIDE_ROOM } from "./district.js";
11
+ export { WALK_CELL, WALK_IDS, walkMask, walkable } from "./walk.js";
12
+ // The gates of a venue: where the crowd goes in, on the building's outline, with the way each faces and how it is known.
13
+ export { GATE_FACTS, GATE_VENUES, gateName, venueGates } from "./gates.js";