@johnmorrisdotca/kyogijo-stadiums 0.46.0 → 0.48.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 +31 -0
- package/NOTICE.md +2 -0
- package/dist/crowd.d.ts +15 -1
- package/dist/draw.js +1 -1
- package/dist/stadiums.d.ts +16 -3
- package/dist/stadiums.data.js +1 -1
- package/dist/stadiums.js +12 -4
- package/dist/stadiums.osm.data.js +1 -1
- package/dist/strings.core.d.ts +1 -0
- package/dist/strings.en.js +1 -1
- package/dist/strings.ja.js +1 -1
- package/dist/turn.style.js +20 -6
- package/dist/venue.real.d.ts +6 -9
- package/dist/venue.real.js +100 -80
- package/dist/venue.ribbed.d.ts +18 -0
- package/dist/venue.ribbed.js +199 -0
- package/dist/venue.saddle.d.ts +48 -0
- package/dist/venue.saddle.js +218 -0
- package/dist/venues/scotiabank-saddledome.data.js +2 -1
- package/dist/venues.d.ts +21 -2
- package/dist/venues.data.js +2 -1
- package/dist/venues.js +38 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/venue.real.js
CHANGED
|
@@ -6,21 +6,13 @@
|
|
|
6
6
|
import { rayAt } from "./crowd.js";
|
|
7
7
|
import { bar, box, n, pole, shy, wall } from "./turn.kit.js";
|
|
8
8
|
import { pointList, raked } from "./venue.geom.js";
|
|
9
|
+
import { ribbedRoof, ribbedWalls } from "./venue.ribbed.js";
|
|
10
|
+
import { SADDLE_CELLS, saddleHeight, saddleRoof, saddleTowers, saddleWalls } from "./venue.saddle.js";
|
|
9
11
|
import { edgeDistance, inside } from "./venues.geo.js";
|
|
10
|
-
/** How far a lid rises from its edge towards the middle,
|
|
12
|
+
/** How far a lid rises from its edge towards the middle, in feet (ours: the sources give a roof's kind, not its height). */
|
|
11
13
|
export const LID_RISE = 22;
|
|
12
|
-
export const SADDLE_RISE = 30;
|
|
13
14
|
/** How far a barrel vault's crown stands over its springing at the long sides, in feet (ours: the article says the roof looks like a Quonset hut, not how high it rises). */
|
|
14
15
|
export const BARREL_RISE = 34;
|
|
15
|
-
/** The saddle's share of its rise at a point (0 at the middle of a side, 1 at the middle of an end): half a saddle's way from one to the other, as a hyperbolic paraboloid (u squared less v squared) lies over the building's box. */
|
|
16
|
-
export function saddleAt(real, [x, y]) {
|
|
17
|
-
const xs = real.footprint.map((p) => p[0]);
|
|
18
|
-
const ys = real.footprint.map((p) => p[1]);
|
|
19
|
-
const [cx, cy] = [(Math.min(...xs) + Math.max(...xs)) / 2, (Math.min(...ys) + Math.max(...ys)) / 2];
|
|
20
|
-
const [hx, hy] = [(Math.max(...xs) - Math.min(...xs)) / 2, (Math.max(...ys) - Math.min(...ys)) / 2];
|
|
21
|
-
const s = ((x - cx) / hx) ** 2 - ((y - cy) / hy) ** 2;
|
|
22
|
-
return (1 + Math.max(-1, Math.min(1, s))) / 2;
|
|
23
|
-
}
|
|
24
16
|
/** The barrel vault's share of its rise at a point (0 at the springing along each long side, 1 over the middle line): a round arch over the building's short axis, the quarter of a circle's rise at each step in from the side. */
|
|
25
17
|
export function barrelAt(real, [, y]) {
|
|
26
18
|
const ys = real.footprint.map((p) => p[1]);
|
|
@@ -29,7 +21,7 @@ export function barrelAt(real, [, y]) {
|
|
|
29
21
|
return Math.sqrt(1 - v * v);
|
|
30
22
|
}
|
|
31
23
|
/** The height of the roof over a point of the building's edge, above the surface, in feet. */
|
|
32
|
-
export const roofAt = (real, p) =>
|
|
24
|
+
export const roofAt = (real, p) => (real.roof === "saddle" && real.saddle !== undefined ? saddleHeight(real.saddle, p) : real.edge + (real.roof === "barrel" ? BARREL_RISE * barrelAt(real, p) : 0));
|
|
33
25
|
/** How near a wall must stand to a canopy's outline to be the wall under it, in feet. */
|
|
34
26
|
const CANOPY_REACH = 50;
|
|
35
27
|
/** The height of the wall of a building under canopies at a point of its rim: where a canopy is over it, the canopy's back edge, which the wall carries up to (the outside of Lumen Field's roofs is one curved face to the ground); elsewhere the rim's own height. */
|
|
@@ -42,17 +34,19 @@ export function wallAt(real, p) {
|
|
|
42
34
|
top = Math.max(top, canopy.low + canopy.rise);
|
|
43
35
|
return top;
|
|
44
36
|
}
|
|
45
|
-
/** How many rings the leaning tower's shaft is cut into,
|
|
37
|
+
/** How many rings the leaning tower's shaft is cut into, and the observatory on its top: its length along the lean, its width and its height, in feet (ours: the sources give the tower's height and its inclination, not these). */
|
|
46
38
|
const TOWER_RINGS = 12;
|
|
47
|
-
const
|
|
48
|
-
|
|
39
|
+
const TOWER_CAP = { long: 84, wide: 72, high: 34 };
|
|
40
|
+
/** How far the shaft's back runs out beyond its foot, as a share of the run from the foot to the top (the back sweeps down to the ground in a long curve), how far its front stands over the top, and how far its back stands behind it, in feet (ours). */
|
|
41
|
+
const TOWER_TAIL = 0.45;
|
|
42
|
+
const TOWER_PRAW = { front: 18, back: 58 };
|
|
49
43
|
/**
|
|
50
|
-
* A tower that leans and curves: a
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
44
|
+
* A tower that leans and curves: a shaft of horizontal sections, a square of rising width at its foot narrowing to its top, that leans over the building. Its foot is at the building's rim (where the mapped axis crosses it, `rim` feet
|
|
45
|
+
* from the top along the axis), its front, the steep face that looks at the building, runs from there to the top, and its back, the face that looks away and up, sweeps down from the top in a long curve that flattens to the ground
|
|
46
|
+
* far behind the foot. On the top stands the observatory, a dark block with a band of glass, and, with `cables`, thin lines run from it to the roof. Every face of a ring is a trapezoid on a plane through its corners, so the faces
|
|
47
|
+
* meet along their edges. The foot, the top and the height are the sources' (the map's funicular line and the article's inclination); the curve, the widths, the slits and the observatory are ours.
|
|
54
48
|
*/
|
|
55
|
-
export function towerPlanes(tower, z, cables = []) {
|
|
49
|
+
export function towerPlanes(tower, z, cables = [], rim) {
|
|
56
50
|
const [bx, by] = tower.base;
|
|
57
51
|
const [tx, ty] = tower.top;
|
|
58
52
|
const run = Math.hypot(tx - bx, ty - by) || 1;
|
|
@@ -60,69 +54,70 @@ export function towerPlanes(tower, z, cables = []) {
|
|
|
60
54
|
const a = [-d[1], d[0]];
|
|
61
55
|
const [w0, w1] = tower.width;
|
|
62
56
|
const H = tower.height - TOWER_CAP.high;
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
57
|
+
// Along the lean (from the foot towards the top) and across it: a point of the shaft at a place `p` along, `q` across and `h` up.
|
|
58
|
+
const at = (p, q, h) => [bx + d[0] * p + a[0] * q, by + d[1] * p + a[1] * q, z + h];
|
|
59
|
+
const front0 = Math.max(0, Math.min(run * 0.9, run - (rim ?? run * 0.5)));
|
|
60
|
+
const frontAt = (h) => front0 + (run + TOWER_PRAW.front - front0) * (h / H) ** 0.92;
|
|
61
|
+
// The back: a quadratic curve from the tail on the ground, flat there, to the top, steep there; its rings are equal steps along the curve.
|
|
62
|
+
const [p0, p1, p2] = [[-TOWER_TAIL * run, 0], [run * 0.34, H * 0.15], [run - TOWER_PRAW.back, H]];
|
|
63
|
+
const curve = (t) => [(1 - t) ** 2 * p0[0] + 2 * t * (1 - t) * p1[0] + t * t * p2[0], (1 - t) ** 2 * p0[1] + 2 * t * (1 - t) * p1[1] + t * t * p2[1]];
|
|
64
|
+
const wide = (h) => w1 + (w0 - w1) * (1 - h / H) ** 1.15;
|
|
65
|
+
const rings = Array.from({ length: TOWER_RINGS + 1 }, (_, i) => {
|
|
66
|
+
const [back, h] = curve(i / TOWER_RINGS);
|
|
67
|
+
return { h, back, front: frameFront(frontAt(h), back), w: wide(h) };
|
|
68
|
+
});
|
|
71
69
|
const planes = [];
|
|
72
|
-
const face = (id, o,
|
|
70
|
+
const face = (id, o, up, along, r, svg) => {
|
|
71
|
+
const [lu, lv] = [Math.hypot(...up), Math.hypot(...along)];
|
|
73
72
|
// (A wall, not a mast: the lower tiers leave out the small parts, and a tower 165 metres high is not one.)
|
|
74
|
-
planes.push({ id, kind: "wall", o, u, v, svg });
|
|
73
|
+
planes.push({ id, kind: "wall", o, u: up, v: along, svg: `<polygon class="u-tower" points="0 0 ${n(lu)} 0 ${n(lu)} ${n(lv * r)} 0 ${n(lv)}"/>${svg(lu)}` });
|
|
75
74
|
};
|
|
75
|
+
const sub = (p, q) => [p[0] - q[0], p[1] - q[1], p[2] - q[2]];
|
|
76
|
+
const scale = (p, k) => [p[0] * k, p[1] * k, p[2] * k];
|
|
76
77
|
for (let i = 0; i < TOWER_RINGS; i += 1) {
|
|
77
|
-
const [
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
// The sloped back (the face away from the lean) has the two long dark slits down it, a few rings clear of the foot and of the top.
|
|
89
|
-
const slit = (f0, f1) => `<polygon class="u-tower-slit" points="0 ${n(f0 * lv)} ${n(lu)} ${n(f0 * lv * r)} ${n(lu)} ${n(f1 * lv * r)} 0 ${n(f1 * lv)}"/>`;
|
|
90
|
-
const slits = k === 0 && i >= 2 && i < TOWER_RINGS - 1 ? `${slit(0.24, 0.31)}${slit(0.62, 0.69)}` : "";
|
|
91
|
-
face(`tower-${i}-${k}`, o, u, v, `<polygon class="u-tower" points="0 0 ${n(lu)} 0 ${n(lu)} ${n(lv * r)} 0 ${n(lv)}"/>${slits}`);
|
|
78
|
+
const [r0, r1] = [rings[i], rings[i + 1]];
|
|
79
|
+
const wr = r1.w / r0.w;
|
|
80
|
+
const lr = (r1.front - r1.back) / (r0.front - r0.back);
|
|
81
|
+
// The front and the back: across the shaft, from the left corner of the ring to the right.
|
|
82
|
+
for (const [name, k0, k1] of [["f", "front", "front"], ["b", "back", "back"]]) {
|
|
83
|
+
const o = at(r0[k0], -r0.w / 2, r0.h);
|
|
84
|
+
const top = at(r1[k1], -r1.w / 2, r1.h);
|
|
85
|
+
// The sloped back carries the two long dark slits down it, a few rings clear of the foot and of the top.
|
|
86
|
+
const slit = (lu, f0, f1) => `<polygon class="u-tower-slit" points="0 ${n(f0 * r0.w)} ${n(lu)} ${n(f0 * r1.w)} ${n(lu)} ${n(f1 * r1.w)} 0 ${n(f1 * r0.w)}"/>`;
|
|
87
|
+
const slits = name === "b" && i >= 2 && i < TOWER_RINGS - 1 ? (lu) => `${slit(lu, 0.24, 0.31)}${slit(lu, 0.62, 0.69)}` : () => "";
|
|
88
|
+
face(`tower-${i}-${name}`, o, sub(top, o), scale([a[0], a[1], 0], r0.w), wr, slits);
|
|
92
89
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const o = ring([0, 0], 0, wp, k);
|
|
99
|
-
const next = ring([0, 0], 0, wp, (k + 1) % 4);
|
|
100
|
-
const v = [next[0] - o[0], next[1] - o[1], 0];
|
|
101
|
-
face(`tower-foot-${k}`, o, [0, 0, TOWER_PLINTH.high], v, `<rect class="u-tower-foot" width="${n(TOWER_PLINTH.high)}" height="${n(Math.hypot(v[0], v[1]))}"/>`);
|
|
90
|
+
// The two flanks: along the lean, from the front to the back.
|
|
91
|
+
for (const [name, side] of [["l", -1], ["r", 1]]) {
|
|
92
|
+
const o = at(r0.front, (side * r0.w) / 2, r0.h);
|
|
93
|
+
const top = at(r1.front, (side * r1.w) / 2, r1.h);
|
|
94
|
+
face(`tower-${i}-${name}`, o, sub(top, o), scale([-d[0], -d[1], 0], r0.front - r0.back), lr, () => "");
|
|
102
95
|
}
|
|
103
|
-
const o = ring([0, 0], TOWER_PLINTH.high, wp, 0);
|
|
104
|
-
const [c1, c3] = [ring([0, 0], TOWER_PLINTH.high, wp, 1), ring([0, 0], TOWER_PLINTH.high, wp, 3)];
|
|
105
|
-
face("tower-foot-top", o, [c1[0] - o[0], c1[1] - o[1], 0], [c3[0] - o[0], c3[1] - o[1], 0], `<rect class="u-tower-foot" width="${n(wp)}" height="${n(wp)}"/>`);
|
|
106
96
|
}
|
|
107
97
|
// The observatory on the top: a block a little wider than the shaft there, its walls dark, with a band of glass round it, and a flat roof.
|
|
108
|
-
const
|
|
98
|
+
const top = rings[TOWER_RINGS];
|
|
99
|
+
const centre = (top.front + top.back) / 2;
|
|
100
|
+
const [hl, hw] = [TOWER_CAP.long / 2, TOWER_CAP.wide / 2];
|
|
101
|
+
const corners = [[hl, -hw], [hl, hw], [-hl, hw], [-hl, -hw]];
|
|
109
102
|
for (let k = 0; k < 4; k += 1) {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const lv = Math.hypot(
|
|
114
|
-
|
|
103
|
+
const [c0, c1] = [corners[k], corners[(k + 1) % 4]];
|
|
104
|
+
const o = at(centre + c0[0], c0[1], top.h);
|
|
105
|
+
const along = sub(at(centre + c1[0], c1[1], top.h), o);
|
|
106
|
+
const lv = Math.hypot(...along);
|
|
107
|
+
planes.push({ id: `tower-cap-${k}`, kind: "wall", o, u: [0, 0, TOWER_CAP.high], v: along, svg: `<rect class="u-tower-cap" width="${n(TOWER_CAP.high)}" height="${n(lv)}"/><rect class="u-tower-glass" x="${n(TOWER_CAP.high * 0.3)}" width="${n(TOWER_CAP.high * 0.45)}" height="${n(lv)}"/>` });
|
|
115
108
|
}
|
|
116
109
|
{
|
|
117
|
-
const o =
|
|
118
|
-
const [c1, c3] = [
|
|
119
|
-
|
|
110
|
+
const o = at(centre + corners[0][0], corners[0][1], top.h + TOWER_CAP.high);
|
|
111
|
+
const [c1, c3] = [at(centre + corners[1][0], corners[1][1], top.h + TOWER_CAP.high), at(centre + corners[3][0], corners[3][1], top.h + TOWER_CAP.high)];
|
|
112
|
+
planes.push({ id: "tower-cap-top", kind: "wall", o, u: sub(c1, o), v: sub(c3, o), svg: `<rect class="u-tower-cap" width="${n(TOWER_CAP.wide)}" height="${n(TOWER_CAP.long)}"/>` });
|
|
120
113
|
}
|
|
121
114
|
// The cables that held the roof, from the observatory to the roof: thin lines.
|
|
122
|
-
const from =
|
|
115
|
+
const from = at(centre, 0, top.h + TOWER_CAP.high * 0.4);
|
|
123
116
|
cables.forEach((to, i) => planes.push(...bar(`tower-cable-${i}`, "mast", from, to, 1.2, "u-tower-cable")));
|
|
124
117
|
return planes;
|
|
125
118
|
}
|
|
119
|
+
/** The front's place along the lean at a height, kept in front of the back (a shaft is never turned inside out where the back's curve comes close to the front's line). */
|
|
120
|
+
const frameFront = (front, back) => Math.max(front, back + 40);
|
|
126
121
|
/** The point where a ray from `from` first meets a polygon, or `undefined`. */
|
|
127
122
|
export function firstHit(poly, from, angle) {
|
|
128
123
|
const [dx, dy] = [Math.cos(angle), Math.sin(angle)];
|
|
@@ -203,16 +198,39 @@ export function mastPlanes(real, c, z, masts) {
|
|
|
203
198
|
export function realBuilding(real, top, centre, lift, steps, open = false, parts = { walls: true, roof: true }) {
|
|
204
199
|
const items = [];
|
|
205
200
|
const c = [centre[0], centre[1]];
|
|
201
|
+
// The Saddledome's building is its own: a round wall that leans out to a beam, under one saddle roof (`venue.saddle.ts`).
|
|
202
|
+
if (real.roof === "saddle" && real.saddle !== undefined) {
|
|
203
|
+
const cells = steps >= 100 ? SADDLE_CELLS.high : steps >= 48 ? SADDLE_CELLS.auto : SADDLE_CELLS.low;
|
|
204
|
+
if (parts.walls) {
|
|
205
|
+
const walls = [...saddleWalls(real.saddle, lift, cells, c), ...saddleTowers(real.saddle, lift)];
|
|
206
|
+
if (walls.length > 0)
|
|
207
|
+
items.push({ id: "shell", kind: "walls", at: centre, height: heightOf(walls), planes: walls });
|
|
208
|
+
}
|
|
209
|
+
if (parts.roof) {
|
|
210
|
+
const roof = saddleRoof(real.saddle, lift, cells);
|
|
211
|
+
if (roof.length > 0)
|
|
212
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(roof), planes: roof });
|
|
213
|
+
}
|
|
214
|
+
return items;
|
|
215
|
+
}
|
|
206
216
|
const rim = Array.from({ length: steps }, (_, k) => rimPoint(real, c, (2 * Math.PI * k) / steps));
|
|
207
217
|
const planes = [];
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
// A ribbed shell (the Olympic Stadium's) is its own: ribs and glazed bays in place of a drum of walls.
|
|
219
|
+
const ribbed = real.roof === "dome" && real.dome?.ribs !== undefined;
|
|
220
|
+
if (ribbed && parts.walls) {
|
|
221
|
+
const mid = [real.dome.rim.reduce((t, p) => t + p[0], 0) / real.dome.rim.length, real.dome.rim.reduce((t, p) => t + p[1], 0) / real.dome.rim.length];
|
|
222
|
+
const shell = ribbedWalls(real, mid, lift, steps);
|
|
223
|
+
items.push({ id: "shell", kind: "walls", at: centre, height: heightOf(shell), planes: shell });
|
|
224
|
+
}
|
|
225
|
+
if (!ribbed)
|
|
226
|
+
rim.forEach((a, i) => {
|
|
227
|
+
const b = rim[(i + 1) % steps];
|
|
228
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
229
|
+
if (long < 1)
|
|
230
|
+
return;
|
|
231
|
+
const high = lift + (real.wall ?? wallAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
|
|
232
|
+
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : real.brick === true ? "u-shell u-brick" : "u-shell"}" width="${n(long)}" height="${n(high)}"/>${real.podium === undefined ? "" : podiumBands(long, high, real.podium)}`), centre), fadeTo: 0.02 });
|
|
233
|
+
});
|
|
216
234
|
if (parts.walls && planes.length > 0)
|
|
217
235
|
items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
|
|
218
236
|
if (parts.walls && real.towers !== undefined) {
|
|
@@ -227,15 +245,17 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
227
245
|
return items;
|
|
228
246
|
}
|
|
229
247
|
if (parts.roof && real.roof === "dome" && real.dome !== undefined) {
|
|
230
|
-
// The cables run from the tower
|
|
248
|
+
// The cables run from the tower in a fan across the roof, from the crown out to either side of it, down to the ring.
|
|
231
249
|
const rim = real.dome.rim;
|
|
232
250
|
const mid = [rim.reduce((t, p) => t + p[0], 0) / rim.length, rim.reduce((t, p) => t + p[1], 0) / rim.length];
|
|
233
|
-
const reach = Math.max(...rim.map((p) => Math.hypot(p[0] - mid[0], p[1] - mid[1]))) /
|
|
251
|
+
const reach = Math.max(...rim.map((p) => Math.hypot(p[0] - mid[0], p[1] - mid[1]))) / 2.4;
|
|
234
252
|
const across = real.tower === undefined ? [0, 0] : [-(real.tower.top[1] - real.tower.base[1]), real.tower.top[0] - real.tower.base[0]];
|
|
235
253
|
const long = Math.hypot(across[0], across[1]) || 1;
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const
|
|
254
|
+
const cables = [-1, -0.5, 0, 0.5, 1].map((f) => [mid[0] + (across[0] / long) * reach * f, mid[1] + (across[1] / long) * reach * f, lift + real.dome.crown - (real.dome.crown - real.dome.low) * 0.9 * Math.abs(f) ** 1.6]);
|
|
255
|
+
// Where the tower's axis meets the building's rim, seen from the top: how far the foot's front is from the top along the axis.
|
|
256
|
+
const hit = real.tower === undefined ? undefined : firstHit(real.footprint, real.tower.top, Math.atan2(real.tower.base[1] - real.tower.top[1], real.tower.base[0] - real.tower.top[0]));
|
|
257
|
+
const rimRun = real.tower === undefined || hit === undefined ? undefined : Math.hypot(hit[0] - real.tower.top[0], hit[1] - real.tower.top[1]);
|
|
258
|
+
const planes = [...(real.dome.ribs === undefined ? domeRoof(real.dome, lift, steps) : ribbedRoof(real, mid, lift, steps)), ...(real.tower === undefined ? [] : towerPlanes(real.tower, lift, cables, rimRun))];
|
|
239
259
|
if (planes.length > 0)
|
|
240
260
|
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
241
261
|
return items;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Pt, RealShell } from "./crowd.ts";
|
|
2
|
+
import { type Plane } from "./turn.kit.ts";
|
|
3
|
+
/** The rib's outer face's share of the way to the middle at a share `t` of the ring's height: a wing that stands out at the foot and sweeps in as it rises. */
|
|
4
|
+
export declare const ribShare: (t: number) => number;
|
|
5
|
+
/** `count` points equally spaced along a closed outline, the first at the point nearest `near` (the tower's side, so that the ribs there begin at the tower). */
|
|
6
|
+
export declare function spaced(outline: readonly Pt[], count: number, near?: Pt): Pt[];
|
|
7
|
+
/** The height of the roof panels over a point that stands the share `s` of the way from the middle to the outline (above the surface, in feet), for a ring at `ring` feet: the panels' surface, which the stands under them must stay below. 0 where the point is outside the wall's sweep. */
|
|
8
|
+
export declare function ribbedSurfaceAt(s: number, ring: number): number;
|
|
9
|
+
/**
|
|
10
|
+
* The shell's walls: the podium and the glazed stack of each bay (one plane), and the ribs between them that rise from the ground and sweep in to the ring. Each rib is its profile on a plane each side of its middle (a curved wing) and its outer
|
|
11
|
+
* face, one trapezoid, which is a small part (`fascia`) that the lower tiers leave out, as they leave out the ring's lip.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ribbedWalls(real: RealShell, c: Pt, lift: number, steps: number): Plane[];
|
|
14
|
+
/**
|
|
15
|
+
* The shell's roof: over each bay two pieces of the white roof panels, rising from the glazed stack to the ring, the ring's lip, and over the ring the membrane in two rings and a flat crown, its pieces crossed by the line of the
|
|
16
|
+
* cable net it is stretched on.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ribbedRoof(real: RealShell, c: Pt, lift: number, steps: number): Plane[];
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { n, plane } from "./turn.kit.js";
|
|
2
|
+
/** How far the ring's outer edge stands in from the outline, as a share of the way to the middle (ours: the sources give the roof's kind and its height, not its profile; the profile is drawn from photographs). */
|
|
3
|
+
const RING = 0.775;
|
|
4
|
+
/** How wide a rib is at its foot and at its top (the width of its outer face), how deep (how far it stands proud of the wall between two ribs) at its foot and at its top, in feet (ours). */
|
|
5
|
+
const RIB = { wide: [48, 24], deep: [34, 14], flare: 0.225, bend: 2.63, side: 12 };
|
|
6
|
+
/** How much of a wall is left where it stands between the eye and the field: the glazed bays give way almost entirely, the ribs stay as a ghost, so that the shell is still known from the near side (ours). */
|
|
7
|
+
const BAY_FADE = 0.12;
|
|
8
|
+
const RIB_FADE = 0.3;
|
|
9
|
+
/** How far the bays are set back from the ribs' outer face at the foot and at the ring, as a share of the way to the middle (ours). */
|
|
10
|
+
const SET_BACK = [0.075, 0.028];
|
|
11
|
+
/** The height of the podium, of the glazed stack's top and of the ring's top, as shares of the ring's height, and of the ring's lip over it (ours, from photographs). */
|
|
12
|
+
const LEVELS = { podium: 0.13, glass: 0.46, ring: 1, lip: 1.05 };
|
|
13
|
+
/** The membrane's ring, from the ring in to the crown's edge, as shares of the way to the middle (ours: one ring of long pieces, a shallow cone, to keep the planes few). */
|
|
14
|
+
const MEMBRANE = [RING, 0.16];
|
|
15
|
+
const scaled = (c, p, s) => [c[0] + (p[0] - c[0]) * s, c[1] + (p[1] - c[1]) * s];
|
|
16
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
17
|
+
/** The rib's outer face's share of the way to the middle at a share `t` of the ring's height: a wing that stands out at the foot and sweeps in as it rises. */
|
|
18
|
+
export const ribShare = (t) => 1 - RIB.flare * Math.min(1, Math.max(0, t)) ** RIB.bend;
|
|
19
|
+
/** `count` points equally spaced along a closed outline, the first at the point nearest `near` (the tower's side, so that the ribs there begin at the tower). */
|
|
20
|
+
export function spaced(outline, count, near) {
|
|
21
|
+
const edges = outline.map((p, i) => [p, outline[(i + 1) % outline.length]]);
|
|
22
|
+
const lengths = edges.map(([a, b]) => Math.hypot(b[0] - a[0], b[1] - a[1]));
|
|
23
|
+
const total = lengths.reduce((s, l) => s + l, 0);
|
|
24
|
+
const at = (distance) => {
|
|
25
|
+
let d = ((distance % total) + total) % total;
|
|
26
|
+
for (let i = 0; i < edges.length; i += 1) {
|
|
27
|
+
if (d <= lengths[i] || i === edges.length - 1) {
|
|
28
|
+
const t = lengths[i] === 0 ? 0 : Math.min(1, d / lengths[i]);
|
|
29
|
+
return [lerp(edges[i][0][0], edges[i][1][0], t), lerp(edges[i][0][1], edges[i][1][1], t)];
|
|
30
|
+
}
|
|
31
|
+
d -= lengths[i];
|
|
32
|
+
}
|
|
33
|
+
return outline[0];
|
|
34
|
+
};
|
|
35
|
+
let start = 0;
|
|
36
|
+
if (near !== undefined) {
|
|
37
|
+
let best = Infinity;
|
|
38
|
+
let run = 0;
|
|
39
|
+
edges.forEach(([a], i) => {
|
|
40
|
+
const gap = Math.hypot(a[0] - near[0], a[1] - near[1]);
|
|
41
|
+
if (gap < best)
|
|
42
|
+
[best, start] = [gap, run];
|
|
43
|
+
run += lengths[i];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return Array.from({ length: count }, (_, k) => at(start + (total * k) / count));
|
|
47
|
+
}
|
|
48
|
+
function cutOf(real, c, steps) {
|
|
49
|
+
const dome = real.dome;
|
|
50
|
+
const count = steps >= 56 ? dome.ribs.count : Math.round(dome.ribs.count / 2);
|
|
51
|
+
// The tower stands on the side its axis comes from: the ribs begin there.
|
|
52
|
+
return { points: spaced(real.footprint, count, real.tower?.base), c, ring: dome.low, crown: dome.crown };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One trapezoid of the shell: between the outline's points `a` and `b`, from the share `s0` of the way to the middle at the height `z0` to the share `s1` at `z1`, drawn in from the rib at each end by `m0` feet at its foot and `m1` at its top
|
|
56
|
+
* (the two chords stay parallel, so it is flat). `paint` draws on it, given its length up the slope, its width at the foot and its top's share of that.
|
|
57
|
+
*/
|
|
58
|
+
function band(id, kind, c, a, b, [s0, z0], [s1, z1], paint, [m0, m1] = [0, 0]) {
|
|
59
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;
|
|
60
|
+
const e = [(b[0] - a[0]) / long, (b[1] - a[1]) / long];
|
|
61
|
+
const [a0, a1] = [scaled(c, a, s0), scaled(c, a, s1)];
|
|
62
|
+
const o = [a0[0] + e[0] * m0, a0[1] + e[1] * m0, z0];
|
|
63
|
+
const top = [a1[0] + e[0] * m1, a1[1] + e[1] * m1, z1];
|
|
64
|
+
const [w0, w1] = [Math.max(1, long * s0 - 2 * m0), Math.max(1, long * s1 - 2 * m1)];
|
|
65
|
+
const v = [e[0] * w0, e[1] * w0, 0];
|
|
66
|
+
const u = [top[0] - o[0], top[1] - o[1], z1 - z0];
|
|
67
|
+
return plane(id, kind, o, u, v, paint(Math.hypot(...u), w0, w1 / w0));
|
|
68
|
+
}
|
|
69
|
+
/** How far a piece between two ribs stands back from the rib's middle, in feet: the rib's side planes stand `RIB.side` out from it, and the piece meets them. */
|
|
70
|
+
const ribMargin = () => RIB.side + 0.4;
|
|
71
|
+
const shape = (cls) => (up, wide, r) => `<polygon class="${cls}" points="0 0 ${n(up)} 0 ${n(up)} ${n(wide * r)} 0 ${n(wide)}"/>`;
|
|
72
|
+
/** The bay's facade: the podium at its foot (a low dark band of openings, up to the share `low` of the plane's height) and over it the glazed stack, three bands of glass between thin ledges. */
|
|
73
|
+
const facade = (low) => (up, wide, r) => {
|
|
74
|
+
const edge = (x) => wide * (1 - (1 - r) * (x / up));
|
|
75
|
+
const band = (f0, f1, m) => {
|
|
76
|
+
const [x0, x1] = [f0 * up, f1 * up];
|
|
77
|
+
return `<polygon class="u-glass" points="${n(x0)} ${n(m * edge(x0))} ${n(x1)} ${n(m * edge(x1))} ${n(x1)} ${n((1 - m) * edge(x1))} ${n(x0)} ${n((1 - m) * edge(x0))}"/>`;
|
|
78
|
+
};
|
|
79
|
+
const stack = [[0.06, 0.30], [0.38, 0.62], [0.70, 0.94]].map(([f0, f1]) => band(low + (1 - low) * f0, low + (1 - low) * f1, 0.03));
|
|
80
|
+
return `${shape("u-bay")(up, wide, r)}${band(0.35 * low, 0.8 * low, 0.04)}${stack.join("")}`;
|
|
81
|
+
};
|
|
82
|
+
/** The share of the way to the middle of the wall between two ribs at a share `t` of the ring's height: the ribs' outer face, set back. */
|
|
83
|
+
const bayShare = (t) => ribShare(t) - lerp(SET_BACK[0], SET_BACK[1], Math.min(1, t));
|
|
84
|
+
/** The height of the roof panels over a point that stands the share `s` of the way from the middle to the outline (above the surface, in feet), for a ring at `ring` feet: the panels' surface, which the stands under them must stay below. 0 where the point is outside the wall's sweep. */
|
|
85
|
+
export function ribbedSurfaceAt(s, ring) {
|
|
86
|
+
let [lo, hi] = [0, 1];
|
|
87
|
+
for (let i = 0; i < 40; i += 1) {
|
|
88
|
+
const mid = (lo + hi) / 2;
|
|
89
|
+
if (bayShare(mid) > s)
|
|
90
|
+
lo = mid;
|
|
91
|
+
else
|
|
92
|
+
hi = mid;
|
|
93
|
+
}
|
|
94
|
+
return ring * LEVELS.lip * lo;
|
|
95
|
+
}
|
|
96
|
+
/** The outward unit direction at a point of the outline, for the planes that give way to the eye. */
|
|
97
|
+
const outward = (c, p) => {
|
|
98
|
+
const d = Math.hypot(p[0] - c[0], p[1] - c[1]) || 1;
|
|
99
|
+
return [(p[0] - c[0]) / d, (p[1] - c[1]) / d];
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The shell's walls: the podium and the glazed stack of each bay (one plane), and the ribs between them that rise from the ground and sweep in to the ring. Each rib is its profile on a plane each side of its middle (a curved wing) and its outer
|
|
103
|
+
* face, one trapezoid, which is a small part (`fascia`) that the lower tiers leave out, as they leave out the ring's lip.
|
|
104
|
+
*/
|
|
105
|
+
export function ribbedWalls(real, c, lift, steps) {
|
|
106
|
+
const cut = cutOf(real, c, steps);
|
|
107
|
+
const planes = [];
|
|
108
|
+
const Z = cut.ring;
|
|
109
|
+
const { points } = cut;
|
|
110
|
+
const t1 = LEVELS.glass;
|
|
111
|
+
const t0 = LEVELS.podium;
|
|
112
|
+
points.forEach((a, k) => {
|
|
113
|
+
const b = points[(k + 1) % points.length];
|
|
114
|
+
const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
115
|
+
const shy = outward(c, mid);
|
|
116
|
+
const [sp0, sg] = [bayShare(0), bayShare(t1 / LEVELS.lip)];
|
|
117
|
+
planes.push({ ...band(`bay-${k}`, "wall", c, a, b, [sp0, lift], [sg, lift + Z * t1], facade(t0 / t1), [ribMargin(), ribMargin()]), shy, fadeTo: BAY_FADE });
|
|
118
|
+
planes.push(...rib(`rib-${k}`, c, a, Z, lift, shy));
|
|
119
|
+
});
|
|
120
|
+
return planes;
|
|
121
|
+
}
|
|
122
|
+
/** One rib at the outline's point `a`: its curved profile on the plane over its radius, and its outer face in two pieces. */
|
|
123
|
+
function rib(id, c, a, Z, lift, shy) {
|
|
124
|
+
const lip = LEVELS.lip;
|
|
125
|
+
const R = Math.hypot(a[0] - c[0], a[1] - c[1]) || 1;
|
|
126
|
+
const [dx, dy] = [(a[0] - c[0]) / R, (a[1] - c[1]) / R];
|
|
127
|
+
// The plane stands over the radius from the ring's share to the foot's, its x running outwards and its y down from the top.
|
|
128
|
+
const [s0, top] = [RING - 0.02, Z * lip];
|
|
129
|
+
const o = [c[0] + dx * R * s0, c[1] + dy * R * s0, lift + top];
|
|
130
|
+
const wide = R * (1 - s0);
|
|
131
|
+
const steps = 8;
|
|
132
|
+
const t = (i) => i / steps;
|
|
133
|
+
const out = Array.from({ length: steps + 1 }, (_, i) => [R * (ribShare(t(i)) - s0), top - Z * lip * t(i)]);
|
|
134
|
+
const inner = out.map(([x, y], i) => [x - lerp(RIB.deep[0], RIB.deep[1], t(i)), y]);
|
|
135
|
+
const outline = [...out, ...inner.reverse()];
|
|
136
|
+
// The two sides of the rib: the same curved profile on a plane each side of its middle, so that the rib is closed from every side and the pieces between ribs meet it.
|
|
137
|
+
const [tx, ty] = [-dy, dx];
|
|
138
|
+
const outlineText = outline.map(([x, y]) => `${n(x)},${n(y)}`).join(" ");
|
|
139
|
+
const planes = [-1, 1].map((side) => {
|
|
140
|
+
const at = [o[0] + tx * RIB.side * side, o[1] + ty * RIB.side * side, o[2]];
|
|
141
|
+
return { ...plane(`${id}${side < 0 ? "-l" : "-r"}`, "wall", at, [dx * wide, dy * wide, 0], [0, 0, -top], `<polygon class="u-rib" points="${outlineText}"/>`), shy, fadeTo: RIB_FADE };
|
|
142
|
+
});
|
|
143
|
+
// The outer face: one trapezoid along the wing's outer edge, from its foot to the ring, as wide as the rib is at each end.
|
|
144
|
+
const at = (tt, side) => {
|
|
145
|
+
const half = lerp(RIB.wide[0], RIB.wide[1], tt) / 2;
|
|
146
|
+
const share = ribShare(tt);
|
|
147
|
+
return [c[0] + dx * R * share + tx * half * side, c[1] + dy * R * share + ty * half * side, lift + Z * lip * tt];
|
|
148
|
+
};
|
|
149
|
+
{
|
|
150
|
+
const [p0, p1, p2] = [at(0, -1), at(1, -1), at(0, 1)];
|
|
151
|
+
const [u, v] = [[p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]], [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]]];
|
|
152
|
+
const [lu, lv] = [Math.hypot(...u), Math.hypot(...v)];
|
|
153
|
+
const r = RIB.wide[1] / RIB.wide[0];
|
|
154
|
+
planes.push({ ...plane(`${id}-f`, "fascia", p0, u, v, `<polygon class="u-rib" points="0 0 ${n(lu)} 0 ${n(lu)} ${n(lv * r)} 0 ${n(lv)}"/>`), shy, fadeTo: RIB_FADE });
|
|
155
|
+
}
|
|
156
|
+
return planes;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The shell's roof: over each bay two pieces of the white roof panels, rising from the glazed stack to the ring, the ring's lip, and over the ring the membrane in two rings and a flat crown, its pieces crossed by the line of the
|
|
160
|
+
* cable net it is stretched on.
|
|
161
|
+
*/
|
|
162
|
+
export function ribbedRoof(real, c, lift, steps) {
|
|
163
|
+
const cut = cutOf(real, c, steps);
|
|
164
|
+
const planes = [];
|
|
165
|
+
const { points, ring: Z, crown } = cut;
|
|
166
|
+
const lip = LEVELS.lip;
|
|
167
|
+
const roofAt = (s) => lift + Z * lip + (crown - Z * lip) * (1 - (s / RING) ** 2);
|
|
168
|
+
const across = (id, a, b, from, to, cls, net, margin, kind = "roof") => {
|
|
169
|
+
const paint = (up, wide, r) => `${shape(cls)(up, wide, r)}${net ? `<path class="u-cable" d="M0 0L${n(up)} ${n(wide * r)}"/>` : ""}`;
|
|
170
|
+
const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
171
|
+
return { ...band(id, kind, c, a, b, from, to, paint, margin), shy: outward(c, mid), fadeTo: 0.2 };
|
|
172
|
+
};
|
|
173
|
+
points.forEach((a, k) => {
|
|
174
|
+
const b = points[(k + 1) % points.length];
|
|
175
|
+
const s = (t) => bayShare(t / lip);
|
|
176
|
+
// The roof panels climb from the glazed stack's top to the ring's outer edge in two pieces; the ring's lip stands over it.
|
|
177
|
+
const mids = [[s(LEVELS.glass), lift + Z * LEVELS.glass], [s(LEVELS.ring), lift + Z * LEVELS.ring], [RING, lift + Z * lip]];
|
|
178
|
+
for (let j = 0; j < 2; j += 1)
|
|
179
|
+
planes.push(across(`roof-p${j}-${k}`, a, b, mids[j], mids[j + 1], j === 1 ? "u-rib" : "u-dome", false, [ribMargin(), ribMargin()], j === 1 ? "fascia" : "roof"));
|
|
180
|
+
// The membrane: rings in from the ring to the crown.
|
|
181
|
+
for (let j = 0; j < MEMBRANE.length - 1; j += 1)
|
|
182
|
+
planes.push(across(`roof-${j}-${k}`, a, b, [MEMBRANE[j], roofAt(MEMBRANE[j])], [MEMBRANE[j + 1], roofAt(MEMBRANE[j + 1])], "u-dome", j === 0, [0, 0]));
|
|
183
|
+
});
|
|
184
|
+
// The crown: a flat cap over the middle.
|
|
185
|
+
const top = points.map((p) => scaled(c, p, MEMBRANE[MEMBRANE.length - 1]));
|
|
186
|
+
const cap = capOver(top, lift + crown);
|
|
187
|
+
if (cap !== undefined)
|
|
188
|
+
planes.push(cap);
|
|
189
|
+
return planes;
|
|
190
|
+
}
|
|
191
|
+
/** A flat polygon at the height `z`, as one plane over its bounding box (the crown). */
|
|
192
|
+
function capOver(poly, z) {
|
|
193
|
+
if (poly.length < 3)
|
|
194
|
+
return undefined;
|
|
195
|
+
const [x0, x1] = [Math.min(...poly.map((p) => p[0])), Math.max(...poly.map((p) => p[0]))];
|
|
196
|
+
const [y0, y1] = [Math.min(...poly.map((p) => p[1])), Math.max(...poly.map((p) => p[1]))];
|
|
197
|
+
const svg = `<polygon class="u-dome" points="${poly.map(([x, y]) => `${n(x - x0)},${n(y - y0)}`).join(" ")}"/>`;
|
|
198
|
+
return { ...plane("roof-crown", "roof", [x0, y0, z], [x1 - x0, 0, 0], [0, y1 - y0, 0], svg), fadeTo: 0.2 };
|
|
199
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Pt, RealShell } from "./crowd.ts";
|
|
2
|
+
import { type Plane, type V3 } from "./turn.kit.ts";
|
|
3
|
+
/** The saddle's measure, in the drawing's frame: its middle, the radius of its round plan, the unit direction of the line through its two high points (and the one across it), and the heights of the high points and of the low rim (feet over the surface). */
|
|
4
|
+
export type Saddle = NonNullable<RealShell["saddle"]>;
|
|
5
|
+
/** The cells to a side of the roof's grid (the plan is 4 times that round the rim), and the rings of the wall, at each detail. */
|
|
6
|
+
export declare const SADDLE_CELLS: {
|
|
7
|
+
readonly low: 8;
|
|
8
|
+
readonly auto: 10;
|
|
9
|
+
readonly high: 16;
|
|
10
|
+
};
|
|
11
|
+
/** How thick the beam round the roof's edge is, how far it overhangs the wall's top, how far the wall leans out over its height (a share of the height of the edge there), in feet or as a share (ours: measured on photographs, a hand's accuracy). */
|
|
12
|
+
export declare const EAVE: {
|
|
13
|
+
readonly depth: 5;
|
|
14
|
+
readonly overhang: 7;
|
|
15
|
+
readonly lean: 0.14;
|
|
16
|
+
};
|
|
17
|
+
/** A pier stands on the wall at every this many facets, and the band of small windows lies between these shares of the wall's height (ours). */
|
|
18
|
+
export declare const PIER_EVERY = 2;
|
|
19
|
+
export declare const WINDOWS: {
|
|
20
|
+
readonly from: 0.4;
|
|
21
|
+
readonly to: 0.44;
|
|
22
|
+
};
|
|
23
|
+
/** The height of the roof over a point of the plan, above the surface, in feet: a hyperbolic paraboloid over the round plan, high along `axis` and low across it. */
|
|
24
|
+
export declare function saddleHeight(s: Saddle, [x, y]: Pt): number;
|
|
25
|
+
/**
|
|
26
|
+
* A quad of four corners (in order round it) as one plane: its first corner, its side to the second and its side to the fourth span the plane; the third corner is put where the plane has it (a quad of a doubly curved surface is not quite
|
|
27
|
+
* flat, and the plane is the nearest flat piece: within a foot or two at every cut). `paint` gets the corners in the plane's own feet, and the plane's own (unit) normal as it lies in the drawing.
|
|
28
|
+
*/
|
|
29
|
+
export declare function quadPlane(id: string, kind: Plane["kind"], corners: readonly [V3, V3, V3, V3], paint: (local: [Pt, Pt, Pt, Pt]) => string): Plane | undefined;
|
|
30
|
+
/** The roof: the net of quads over the round plan, `cells` to a side, with the beam round its edge. Every plane is a roof's, cut away from above and, by its side, from the eye's; the beam is the edge of the roof's own item. */
|
|
31
|
+
export declare function saddleRoof(s: Saddle, lift: number, cells: number): Plane[];
|
|
32
|
+
/** The wall: a facet between each two points of the rim, leaning out from its foot to the beam, with a pier between every `PIER_EVERY` panels, the fine lines of its cladding and a few windows. Each gives way by the eye's side as a wall does. */
|
|
33
|
+
export declare function saddleWalls(s: Saddle, lift: number, cells: number, centre: Pt): Plane[];
|
|
34
|
+
/** The two stair towers at each end of the roof's high line, a round one and a square one, on the wall's foot at either side of the end: how tall each stands (a share of the wall there) and how big it is, in feet (ours: photographs show each, not its size). */
|
|
35
|
+
export declare const TOWERS: {
|
|
36
|
+
readonly round: {
|
|
37
|
+
readonly turn: -24;
|
|
38
|
+
readonly radius: 11;
|
|
39
|
+
readonly share: 0.55;
|
|
40
|
+
};
|
|
41
|
+
readonly square: {
|
|
42
|
+
readonly turn: 24;
|
|
43
|
+
readonly half: 17;
|
|
44
|
+
readonly share: 0.45;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/** The stair towers: at each of the roof's two high points, a round tower and a square one against the wall, taller where the wall is. */
|
|
48
|
+
export declare function saddleTowers(s: Saddle, lift: number): Plane[];
|