@a2f0/skyline 0.0.0-stage → 0.1.1
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/NOTICE.md +20 -0
- package/README.md +52 -2
- package/docs/package.md +222 -0
- package/docs/skyline-geography.md +503 -0
- package/lib/models/aon-center.d.ts +4 -0
- package/lib/models/aon-center.js +60 -0
- package/lib/models/aon-geographic.d.ts +9 -0
- package/lib/models/aon-geographic.js +35 -0
- package/lib/models/aon-tower.d.ts +23 -0
- package/lib/models/aon-tower.js +130 -0
- package/lib/models/blue-cross-geographic.d.ts +14 -0
- package/lib/models/blue-cross-geographic.js +248 -0
- package/lib/models/borg-warner-geographic.d.ts +20 -0
- package/lib/models/borg-warner-geographic.js +143 -0
- package/lib/models/buckingham-geographic.d.ts +10 -0
- package/lib/models/buckingham-geographic.js +121 -0
- package/lib/models/building-kit.d.ts +84 -0
- package/lib/models/building-kit.js +358 -0
- package/lib/models/celebrations.d.ts +39 -0
- package/lib/models/celebrations.js +44 -0
- package/lib/models/chicago-athletic-association-geographic.d.ts +32 -0
- package/lib/models/chicago-athletic-association-geographic.js +173 -0
- package/lib/models/crain-communications.d.ts +5 -0
- package/lib/models/crain-communications.js +75 -0
- package/lib/models/crain-geographic.d.ts +10 -0
- package/lib/models/crain-geographic.js +69 -0
- package/lib/models/crain-tower.d.ts +32 -0
- package/lib/models/crain-tower.js +378 -0
- package/lib/models/facade-grid.d.ts +18 -0
- package/lib/models/facade-grid.js +176 -0
- package/lib/models/gage-geographic.d.ts +19 -0
- package/lib/models/gage-geographic.js +154 -0
- package/lib/models/heritage-at-millennium-park.d.ts +3 -0
- package/lib/models/heritage-at-millennium-park.js +284 -0
- package/lib/models/heritage-geographic.d.ts +11 -0
- package/lib/models/heritage-geographic.js +208 -0
- package/lib/models/hyatt-west-tower-geographic.d.ts +26 -0
- package/lib/models/hyatt-west-tower-geographic.js +86 -0
- package/lib/models/keith-ascher-geographic.d.ts +29 -0
- package/lib/models/keith-ascher-geographic.js +110 -0
- package/lib/models/kemper-geographic.d.ts +8 -0
- package/lib/models/kemper-geographic.js +153 -0
- package/lib/models/kemper.d.ts +2 -0
- package/lib/models/kemper.js +78 -0
- package/lib/models/lake-view-geographic.d.ts +15 -0
- package/lib/models/lake-view-geographic.js +134 -0
- package/lib/models/maclean-center-geographic.d.ts +20 -0
- package/lib/models/maclean-center-geographic.js +162 -0
- package/lib/models/michigan-boulevard-geographic.d.ts +12 -0
- package/lib/models/michigan-boulevard-geographic.js +157 -0
- package/lib/models/michigan-plaza-south-geographic.d.ts +7 -0
- package/lib/models/michigan-plaza-south-geographic.js +84 -0
- package/lib/models/michigan-plaza-south.d.ts +2 -0
- package/lib/models/michigan-plaza-south.js +68 -0
- package/lib/models/millennium-park-plaza-geographic.d.ts +9 -0
- package/lib/models/millennium-park-plaza-geographic.js +103 -0
- package/lib/models/monroe-geographic.d.ts +19 -0
- package/lib/models/monroe-geographic.js +177 -0
- package/lib/models/north-michigan-180-geographic.d.ts +14 -0
- package/lib/models/north-michigan-180-geographic.js +117 -0
- package/lib/models/north-wabash-geographic.d.ts +14 -0
- package/lib/models/north-wabash-geographic.js +122 -0
- package/lib/models/on-the-park-geographic.d.ts +11 -0
- package/lib/models/on-the-park-geographic.js +245 -0
- package/lib/models/one-prudential-geographic.d.ts +12 -0
- package/lib/models/one-prudential-geographic.js +55 -0
- package/lib/models/one-prudential-plaza.d.ts +5 -0
- package/lib/models/one-prudential-plaza.js +72 -0
- package/lib/models/one-prudential-tower.d.ts +45 -0
- package/lib/models/one-prudential-tower.js +246 -0
- package/lib/models/peoples-gas-geographic.d.ts +23 -0
- package/lib/models/peoples-gas-geographic.js +250 -0
- package/lib/models/railway-exchange-geographic.d.ts +18 -0
- package/lib/models/railway-exchange-geographic.js +176 -0
- package/lib/models/river-plaza-geographic.d.ts +16 -0
- package/lib/models/river-plaza-geographic.js +104 -0
- package/lib/models/sheraton-grand-geographic.d.ts +24 -0
- package/lib/models/sheraton-grand-geographic.js +112 -0
- package/lib/models/six-north-michigan-geographic.d.ts +16 -0
- package/lib/models/six-north-michigan-geographic.js +180 -0
- package/lib/models/skyline-geography-data.d.ts +44 -0
- package/lib/models/skyline-geography-data.js +607 -0
- package/lib/models/skyline-geography.d.ts +23 -0
- package/lib/models/skyline-geography.js +322 -0
- package/lib/models/swissotel-geographic.d.ts +24 -0
- package/lib/models/swissotel-geographic.js +79 -0
- package/lib/models/three-illinois-center-geographic.d.ts +26 -0
- package/lib/models/three-illinois-center-geographic.js +86 -0
- package/lib/models/trump-geographic.d.ts +14 -0
- package/lib/models/trump-geographic.js +51 -0
- package/lib/models/trump-international-tower.d.ts +4 -0
- package/lib/models/trump-international-tower.js +84 -0
- package/lib/models/trump-tower.d.ts +43 -0
- package/lib/models/trump-tower.js +229 -0
- package/lib/models/two-illinois-center-geographic.d.ts +26 -0
- package/lib/models/two-illinois-center-geographic.js +85 -0
- package/lib/models/two-prudential-geographic.d.ts +25 -0
- package/lib/models/two-prudential-geographic.js +47 -0
- package/lib/models/two-prudential-plaza.d.ts +8 -0
- package/lib/models/two-prudential-plaza.js +55 -0
- package/lib/models/two-prudential-tower.d.ts +53 -0
- package/lib/models/two-prudential-tower.js +441 -0
- package/lib/models/university-club-geographic.d.ts +24 -0
- package/lib/models/university-club-geographic.js +217 -0
- package/lib/models/willoughby-tower-geographic.d.ts +17 -0
- package/lib/models/willoughby-tower-geographic.js +195 -0
- package/lib/models/window-illumination.d.ts +14 -0
- package/lib/models/window-illumination.js +64 -0
- package/lib/package-assets.d.ts +5 -0
- package/lib/package-assets.js +9 -0
- package/lib/skyline-package.d.ts +23 -0
- package/lib/skyline-package.js +25 -0
- package/lib/skyline-scene.d.ts +25 -0
- package/lib/skyline-scene.js +86 -0
- package/lib/study-types.d.ts +49 -0
- package/lib/study-types.js +1 -0
- package/lib/vendor/three-r186.d.ts +1 -0
- package/lib/vendor/three-r186.js +1 -0
- package/package.json +64 -4
- package/site/building-detail.html +54 -0
- package/site/building-detail.js +84 -0
- package/site/building-study.html +58 -0
- package/site/building-study.js +3 -0
- package/site/index.html +407 -0
- package/site/models/aon-center.js +60 -0
- package/site/models/aon-geographic.js +35 -0
- package/site/models/aon-tower.js +130 -0
- package/site/models/blue-cross-geographic.js +248 -0
- package/site/models/borg-warner-geographic.js +143 -0
- package/site/models/buckingham-geographic.js +121 -0
- package/site/models/building-kit.js +358 -0
- package/site/models/celebrations.js +44 -0
- package/site/models/chicago-athletic-association-geographic.js +173 -0
- package/site/models/crain-communications.js +75 -0
- package/site/models/crain-geographic.js +69 -0
- package/site/models/crain-reference.svg +10 -0
- package/site/models/crain-tower.js +378 -0
- package/site/models/facade-grid.js +176 -0
- package/site/models/gage-geographic.js +154 -0
- package/site/models/heritage-at-millennium-park.js +284 -0
- package/site/models/heritage-geographic.js +208 -0
- package/site/models/hyatt-west-tower-geographic.js +86 -0
- package/site/models/keith-ascher-geographic.js +110 -0
- package/site/models/kemper-geographic.js +153 -0
- package/site/models/kemper.js +78 -0
- package/site/models/lake-view-geographic.js +134 -0
- package/site/models/maclean-center-geographic.js +162 -0
- package/site/models/michigan-boulevard-geographic.js +157 -0
- package/site/models/michigan-plaza-south-geographic.js +84 -0
- package/site/models/michigan-plaza-south.js +68 -0
- package/site/models/millennium-park-plaza-geographic.js +103 -0
- package/site/models/monroe-geographic.js +177 -0
- package/site/models/north-michigan-180-geographic.js +117 -0
- package/site/models/north-wabash-geographic.js +122 -0
- package/site/models/on-the-park-geographic.js +245 -0
- package/site/models/one-prudential-geographic.js +55 -0
- package/site/models/one-prudential-plaza.js +72 -0
- package/site/models/one-prudential-tower.js +246 -0
- package/site/models/peoples-gas-geographic.js +250 -0
- package/site/models/railway-exchange-geographic.js +176 -0
- package/site/models/river-plaza-geographic.js +104 -0
- package/site/models/sheraton-grand-geographic.js +112 -0
- package/site/models/six-north-michigan-geographic.js +180 -0
- package/site/models/skyline-geography-data.js +607 -0
- package/site/models/skyline-geography.js +322 -0
- package/site/models/skyline-panorama.svg +3491 -0
- package/site/models/skyline-reference.svg +3090 -0
- package/site/models/swissotel-geographic.js +79 -0
- package/site/models/three-illinois-center-geographic.js +86 -0
- package/site/models/trump-geographic.js +51 -0
- package/site/models/trump-international-tower.js +84 -0
- package/site/models/trump-tower.js +229 -0
- package/site/models/two-illinois-center-geographic.js +85 -0
- package/site/models/two-prudential-geographic.js +47 -0
- package/site/models/two-prudential-plaza.js +55 -0
- package/site/models/two-prudential-tower.js +441 -0
- package/site/models/university-club-geographic.js +217 -0
- package/site/models/willoughby-tower-geographic.js +195 -0
- package/site/models/window-illumination.js +64 -0
- package/site/skyline-3d.html +92 -0
- package/site/skyline-3d.js +333 -0
- package/site/skyline-animated.svg +18795 -0
- package/site/skyline-comparison.js +97 -0
- package/site/skyline-original-fit.svg +18312 -0
- package/site/skyline-scene.js +86 -0
- package/site/skyline-study.html +83 -0
- package/site/skyline-study.js +109 -0
- package/site/skyline-webgl.html +667 -0
- package/site/stars.svg +176 -0
- package/site/study-loader.js +20 -0
- package/site/study-types.js +1 -0
- package/site/study-viewer.js +605 -0
- package/site/study.css +204 -0
- package/site/vendor/THREE-LICENSE.txt +21 -0
- package/site/vendor/three-r186.js +4228 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { geographicBuildings } from "./skyline-geography-data.js";
|
|
2
|
+
import { projectGround } from "./skyline-geography.js";
|
|
3
|
+
import { trumpNotch, trumpSpire } from "./trump-geographic.js";
|
|
4
|
+
import { buildTrumpTower, crownFoot, floorLines, mullionPoints, topOutlines, trumpLevels } from "./trump-tower.js";
|
|
5
|
+
// Trump International Hotel and Tower on its mapped plan, in real meters, for the skyline
|
|
6
|
+
// study's original layout, which places this copy in skyline-study.ts. trump-tower.ts builds
|
|
7
|
+
// it, and the geographic model on the same parts. The tower's rounded and bevelled shaft has
|
|
8
|
+
// no simpler published plan than the map's, so the copy keeps the mapped shaft and crown,
|
|
9
|
+
// shifted to put the shaft's centre at the origin. Its shaft runs straight down to the
|
|
10
|
+
// drawing's datum: the drawing hides everything below One Prudential's roof, and the
|
|
11
|
+
// original layout stands Trump 65 m behind One Prudential and just in front of Two
|
|
12
|
+
// Prudential, where the mapped setbacks would pass through both. See docs/trump-reference.md.
|
|
13
|
+
// Units are meters; +x is east and +z is south.
|
|
14
|
+
const record = geographicBuildings.find((entry) => entry.id === "building-trump-tower-only");
|
|
15
|
+
const mapped = (coordinates) => coordinates.map((corner) => { const [east, north] = projectGround(corner); return [east, -north]; });
|
|
16
|
+
const part = (way) => mapped(record.parts.find((entry) => entry.way === way).coordinates);
|
|
17
|
+
// The shaft's mapped bounds' centre.
|
|
18
|
+
const shaftCorners = part(188338548);
|
|
19
|
+
const origin = [(Math.min(...shaftCorners.map(([x]) => x)) + Math.max(...shaftCorners.map(([x]) => x))) / 2, (Math.min(...shaftCorners.map(([, z]) => z)) + Math.max(...shaftCorners.map(([, z]) => z))) / 2];
|
|
20
|
+
const local = ([x, z]) => [x - origin[0], z - origin[1]];
|
|
21
|
+
const shift = (corners) => corners.map(local);
|
|
22
|
+
const shaft = shift(shaftCorners);
|
|
23
|
+
const notch = shaft.reduce((best, p) => (Math.hypot(p[0] - local(trumpNotch)[0], p[1] - local(trumpNotch)[1]) < Math.hypot(best[0] - local(trumpNotch)[0], best[1] - local(trumpNotch)[1]) ? p : best));
|
|
24
|
+
const crown = shift(part(188338859));
|
|
25
|
+
const spire = local(trumpSpire);
|
|
26
|
+
// The skyline drawing shows the tower above One Prudential, and its platform datum crosses
|
|
27
|
+
// the tower above the street: fitted with the placement in skyline-study.ts, which scales,
|
|
28
|
+
// turns, and places this copy. Its y = 0 is that datum.
|
|
29
|
+
export const trumpSkylineBase = 40.7;
|
|
30
|
+
export function createTrumpInternationalTowerBuilding() {
|
|
31
|
+
return buildTrumpTower({
|
|
32
|
+
name: "Trump International Hotel and Tower",
|
|
33
|
+
id: "building-trump-tower-only",
|
|
34
|
+
tiers: [{ outline: shaft, top: trumpLevels.roof }],
|
|
35
|
+
step: notch[1],
|
|
36
|
+
crown,
|
|
37
|
+
spire,
|
|
38
|
+
base: trumpSkylineBase,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Features the skyline test projects against the drawing, in this copy's coordinates.
|
|
42
|
+
const h = trumpLevels;
|
|
43
|
+
const at = ([x, z], real) => [x, real - trumpSkylineBase, z];
|
|
44
|
+
const vertex = (corners, near) => corners.reduce((best, p) => (Math.hypot(p[0] - near[0], p[1] - near[1]) < Math.hypot(best[0] - near[0], best[1] - near[1]) ? p : best));
|
|
45
|
+
// Points along the bevelled south-east face, from its south corner.
|
|
46
|
+
const bevel = [vertex(shaft, local([-130.2, -417.4])), vertex(shaft, local([-114.5, -432.7]))];
|
|
47
|
+
// The bevel faces south-east, so outward is to the left walking from its south corner.
|
|
48
|
+
const alongBevel = (s, depth) => {
|
|
49
|
+
const [a, b] = bevel, l = Math.hypot(b[0] - a[0], b[1] - a[1]), t = [(b[0] - a[0]) / l, (b[1] - a[1]) / l];
|
|
50
|
+
return [a[0] + t[0] * s - t[1] * depth, a[1] + t[1] * s + t[0] * depth];
|
|
51
|
+
};
|
|
52
|
+
// Every mullion of the shaft and of the crown, in plan, the same placement the model uses.
|
|
53
|
+
const tops = topOutlines(shaft, notch[1], crown);
|
|
54
|
+
const shaftMullions = mullionPoints(tops.shaft, () => 0), crownMullions = mullionPoints(tops.crown, crownFoot(tops.cap));
|
|
55
|
+
// A mullion's point halfway out to its face, at a height.
|
|
56
|
+
const mullion = (list, index, real) => { const { at: [x, z], normal: [nx, nz] } = list[index]; return at([x + nx * 0.115, z + nz * 0.115], real); };
|
|
57
|
+
export const trumpFeatures = {
|
|
58
|
+
// The roof's south-west corner and its east end at the bevel, where the drawn roof turns.
|
|
59
|
+
trumpTowerWestRoof: at(vertex(shaft, local([-146.1, -422.9])), h.roof),
|
|
60
|
+
trumpTowerEastStart: at(vertex(shaft, local([-111.7, -435.3])), h.roof),
|
|
61
|
+
// The step: the notch's corner at the roof and at the shoulder, and the shoulder's far end.
|
|
62
|
+
trumpRoofStepTop: at(notch, h.roof),
|
|
63
|
+
trumpRoofStepBottom: at(notch, h.shoulder),
|
|
64
|
+
trumpEastShoulder: at(vertex(shaft, local([-115.6, -464.9])), h.shoulder),
|
|
65
|
+
trumpCrownWest: at(vertex(crown, local([-133.4, -443.3])), h.crownTop),
|
|
66
|
+
trumpCrownEast: at(vertex(crown, local([-116.4, -463.7])), h.crownTop),
|
|
67
|
+
trumpSpireTip: at(spire, h.tip),
|
|
68
|
+
trumpSpireUpperJoint: at(spire, h.joints[1]),
|
|
69
|
+
trumpSpireLowerJoint: at(spire, h.joints[0]),
|
|
70
|
+
// A window on the short south face, above One Prudential and well left of its mast, for hover.
|
|
71
|
+
trumpFacadeProbe: at(local([-135.75, -416.3 + 0.07]), floorLines[90] + 1.6),
|
|
72
|
+
// Behind One Prudential in the drawing.
|
|
73
|
+
trumpBehindPrudential: at(alongBevel(10, 0.07), floorLines[30] + 1.6),
|
|
74
|
+
// The drawn floor rows: spandrel centres 2 m along the bevel, on the drawing's sampled rows.
|
|
75
|
+
trumpFloorBands: [0, 7, 14, 21, 28, 35, 40].map((row) => at(alongBevel(2, 0.07), h.floorLine + row * h.pitch + 0.1)),
|
|
76
|
+
// The drawing's mullion lines, each checked against the model's nearest mullion, counted
|
|
77
|
+
// round the shaft's walls and the crown's from the chain each starts. The drawing spaces
|
|
78
|
+
// its lines unevenly: it doubles some where the mullions bunch round a curve, and skips
|
|
79
|
+
// most of the east face's. The shaft's lines were measured 289 m up, the crown's at its top.
|
|
80
|
+
trumpFrontMullions: [19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34].map((i) => mullion(shaftMullions, i, 289)),
|
|
81
|
+
trumpCornerMullions: [35, 37, 38].map((i) => mullion(shaftMullions, i, 289)),
|
|
82
|
+
trumpEastMullions: [41, 43, 45, 46, 52, 53, 54, 56, 57, 59].map((i) => mullion(shaftMullions, i, 289)),
|
|
83
|
+
trumpCrownMullions: [26, 27, 27, 28, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 39, 40, 0, 1, 2, 3].map((i) => mullion(crownMullions, i, 364.5)),
|
|
84
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { BuildingModel, Run, Vec2 } from "./building-kit.js";
|
|
2
|
+
export interface TrumpForm {
|
|
3
|
+
name: string;
|
|
4
|
+
id: string;
|
|
5
|
+
tiers: {
|
|
6
|
+
outline: Vec2[];
|
|
7
|
+
top: number;
|
|
8
|
+
}[];
|
|
9
|
+
step: number;
|
|
10
|
+
crown: Vec2[];
|
|
11
|
+
spire: Vec2;
|
|
12
|
+
base: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const trumpLevels: Readonly<{
|
|
15
|
+
podium: 60;
|
|
16
|
+
setbacks: readonly [120, 200];
|
|
17
|
+
roof: 354.4;
|
|
18
|
+
shoulder: 347.3;
|
|
19
|
+
crownTop: 364.9;
|
|
20
|
+
joints: readonly [377.4, 402.2];
|
|
21
|
+
tip: 423.2;
|
|
22
|
+
floorLine: 187.01;
|
|
23
|
+
pitch: 3.214;
|
|
24
|
+
lobbyTop: 10.5;
|
|
25
|
+
module: 1.8288;
|
|
26
|
+
}>;
|
|
27
|
+
export declare const floorLines: number[];
|
|
28
|
+
export declare function mullionStations(length: number): number[];
|
|
29
|
+
export declare function mullionPoints(corners: Vec2[], runFrom: (run: Run) => number): {
|
|
30
|
+
at: Vec2;
|
|
31
|
+
normal: Vec2;
|
|
32
|
+
}[];
|
|
33
|
+
export declare function splitAt(corners: Vec2[], z: number): {
|
|
34
|
+
outline: Vec2[];
|
|
35
|
+
south: Vec2[];
|
|
36
|
+
};
|
|
37
|
+
export declare function topOutlines(shaftCorners: Vec2[], step: number, crownCorners: Vec2[]): {
|
|
38
|
+
shaft: Vec2[];
|
|
39
|
+
cap: Vec2[];
|
|
40
|
+
crown: Vec2[];
|
|
41
|
+
};
|
|
42
|
+
export declare const crownFoot: (cap: Vec2[]) => (run: Run) => 347.3 | 354.4;
|
|
43
|
+
export declare function buildTrumpTower(form: TrumpForm): BuildingModel;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import { createBuilder, inside } from "./building-kit.js";
|
|
3
|
+
import { chainSkin, chainsOf, jointTrim, orient, planOf, turnsAt } from "./facade-grid.js";
|
|
4
|
+
// Heights above the street. The tip is published; the setbacks are the mapped parts'; the
|
|
5
|
+
// roof, the shoulder, the crown, the spire's joints, and the floor rows are measured on the
|
|
6
|
+
// photograph the drawing was traced from, down from the tip. See docs/trump-reference.md.
|
|
7
|
+
export const trumpLevels = Object.freeze({
|
|
8
|
+
podium: 60,
|
|
9
|
+
setbacks: [120, 200],
|
|
10
|
+
roof: 354.4,
|
|
11
|
+
shoulder: 347.3,
|
|
12
|
+
crownTop: 364.9,
|
|
13
|
+
joints: [377.4, 402.2],
|
|
14
|
+
tip: 423.2,
|
|
15
|
+
// Floor lines: one on the drawn rows, then every 3.214 m, from a tall ground storey up.
|
|
16
|
+
floorLine: 187.01,
|
|
17
|
+
pitch: 3.214,
|
|
18
|
+
lobbyTop: 10.5,
|
|
19
|
+
// The curtain wall's 6 ft units.
|
|
20
|
+
module: 1.8288,
|
|
21
|
+
});
|
|
22
|
+
const h = trumpLevels;
|
|
23
|
+
// Every floor line from the lobby's top to the crown's top: the crown's mechanical floors
|
|
24
|
+
// keep the tower's pitch.
|
|
25
|
+
export const floorLines = (() => {
|
|
26
|
+
const lines = [];
|
|
27
|
+
let first = h.floorLine;
|
|
28
|
+
while (first - h.pitch >= h.lobbyTop - 1e-6)
|
|
29
|
+
first -= h.pitch;
|
|
30
|
+
for (let level = first; level < h.crownTop; level += h.pitch)
|
|
31
|
+
lines.push(level);
|
|
32
|
+
return lines;
|
|
33
|
+
})();
|
|
34
|
+
// Each floor's brushed stainless spandrel straddles its slab; the glass fills the rest.
|
|
35
|
+
const spandrelBelow = 0.35, spandrelAbove = 0.55;
|
|
36
|
+
// Mullions stand one module apart along each wall, a curved run of the trace following on
|
|
37
|
+
// from the last, so they keep their rhythm round the rounded corners. A turn of 40° or more
|
|
38
|
+
// starts a new wall: the bevels and the notches.
|
|
39
|
+
const bend = 40 * Math.PI / 180;
|
|
40
|
+
export function mullionStations(length) {
|
|
41
|
+
const count = Math.max(1, Math.round(length / h.module));
|
|
42
|
+
return Array.from({ length: count + 1 }, (_, i) => length * i / count);
|
|
43
|
+
}
|
|
44
|
+
const mullionHalf = 0.06, mullionDepth = 0.23;
|
|
45
|
+
// How far a wall's glass and mullions stop short of a stretch something else stands on.
|
|
46
|
+
const clearance = 0.2;
|
|
47
|
+
// Where a chain's mullions stand: one per station, whole on one run, nudged clear of the
|
|
48
|
+
// joints and held back from the wall's ends by its own depth, so it meets neither a
|
|
49
|
+
// neighbouring wall's mullion nor the end of a run's glass. Each is its run's index in the
|
|
50
|
+
// chain and the distance along that run.
|
|
51
|
+
function mullionsOn(plan, chain, froms) {
|
|
52
|
+
const placed = [];
|
|
53
|
+
for (const station of mullionStations(chain.length)) {
|
|
54
|
+
const k = chain.starts.findIndex((start, i) => station < start + plan[chain.runs[i]].length + 1e-9);
|
|
55
|
+
if (k < 0)
|
|
56
|
+
continue;
|
|
57
|
+
const run = plan[chain.runs[k]], start = chain.starts[k];
|
|
58
|
+
const clear = (other) => (other < 0 || other >= chain.runs.length ? mullionDepth + 0.02 : froms[other] > froms[k] ? clearance : 0.03) + mullionHalf;
|
|
59
|
+
const lo = clear(k - 1), hi = run.length - clear(k + 1);
|
|
60
|
+
if (hi >= lo)
|
|
61
|
+
placed.push({ k, s: Math.min(Math.max(station - start, lo), hi) });
|
|
62
|
+
}
|
|
63
|
+
return placed;
|
|
64
|
+
}
|
|
65
|
+
// Every mullion of an outline's curtain wall, in plan, with the wall's outward normal, for
|
|
66
|
+
// features: the same placement the model is built with, given what each run stands on.
|
|
67
|
+
export function mullionPoints(corners, runFrom) {
|
|
68
|
+
const plan = planOf(orient(corners));
|
|
69
|
+
return chainsOf(plan, bend).flatMap((chain) => {
|
|
70
|
+
const froms = chain.runs.map((index) => runFrom(plan[index]));
|
|
71
|
+
return mullionsOn(plan, chain, froms).map(({ k, s }) => { const run = plan[chain.runs[k]]; return { at: run.at(s), normal: run.normal(0) }; });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const color = (hex) => new THREE.Color(hex);
|
|
75
|
+
const glassTones = [0x2a2a2a, 0x2e2e2e, 0x333333, 0x272727].map(color);
|
|
76
|
+
const litGlass = color(0x7c7c7c), dimGlass = color(0x505050);
|
|
77
|
+
const spandrel = color(0x474747), crownGlass = color(0x232323), crownBand = color(0x3c3c3c);
|
|
78
|
+
function paneColor(row, bay, wall) {
|
|
79
|
+
const hash = (Math.imul(row + 11, 0x9e3779b1) ^ Math.imul(bay + 29, 0x85ebca77) ^ Math.imul(wall + 3, 0xc2b2ae3d)) >>> 0;
|
|
80
|
+
const value = hash % 101;
|
|
81
|
+
if (value < 3)
|
|
82
|
+
return litGlass;
|
|
83
|
+
if (value < 6)
|
|
84
|
+
return dimGlass;
|
|
85
|
+
return glassTones[value % glassTones.length];
|
|
86
|
+
}
|
|
87
|
+
// An outline with the crossings of a line of constant z inserted as vertices, and the part
|
|
88
|
+
// of it south of that line.
|
|
89
|
+
export function splitAt(corners, z) {
|
|
90
|
+
const outline = [], south = [];
|
|
91
|
+
corners.forEach((a, i) => {
|
|
92
|
+
const b = corners[(i + 1) % corners.length];
|
|
93
|
+
outline.push(a);
|
|
94
|
+
if (a[1] >= z - 1e-9)
|
|
95
|
+
south.push(a);
|
|
96
|
+
if ((a[1] - z) * (b[1] - z) < -1e-12) {
|
|
97
|
+
const t = (z - a[1]) / (b[1] - a[1]), cross = [a[0] + (b[0] - a[0]) * t, z];
|
|
98
|
+
outline.push(cross);
|
|
99
|
+
south.push(cross);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return { outline, south };
|
|
103
|
+
}
|
|
104
|
+
// The shaft's outline broken at the step's line; the cap, its part south of the line, which
|
|
105
|
+
// stands on to the roof; and the crown's outline broken there too. The crown crosses the
|
|
106
|
+
// step's line, and its crossings break the step's wall, so the stretch the crown stands on
|
|
107
|
+
// carries no facade.
|
|
108
|
+
export function topOutlines(shaftCorners, step, crownCorners) {
|
|
109
|
+
const split = splitAt(orient(shaftCorners), step);
|
|
110
|
+
const crown = splitAt(orient(crownCorners), step).outline;
|
|
111
|
+
const crossings = crown.filter(([, z]) => Math.abs(z - step) < 1e-9);
|
|
112
|
+
const cap = orient(split.south).flatMap((corner, i, corners) => {
|
|
113
|
+
const next = corners[(i + 1) % corners.length];
|
|
114
|
+
if (Math.abs(corner[1] - step) > 1e-9 || Math.abs(next[1] - step) > 1e-9)
|
|
115
|
+
return [corner];
|
|
116
|
+
const between = crossings.filter(([x]) => (x - corner[0]) * (x - next[0]) < -1e-9).sort((a, b) => Math.abs(a[0] - corner[0]) - Math.abs(b[0] - corner[0]));
|
|
117
|
+
return [corner, ...between];
|
|
118
|
+
});
|
|
119
|
+
return { shaft: split.outline, cap, crown };
|
|
120
|
+
}
|
|
121
|
+
// What a crown wall stands on: the roof where it rises from the cap, the shoulder elsewhere.
|
|
122
|
+
export const crownFoot = (cap) => (run) => (inside(cap, run.at(run.length / 2, 0.3)) ? h.roof : h.shoulder);
|
|
123
|
+
export function buildTrumpTower(form) {
|
|
124
|
+
const kit = createBuilder(form.name, form.id);
|
|
125
|
+
const shell = kit.batch("Trump · tower shell", kit.material(0x2c2c2c));
|
|
126
|
+
const glazing = kit.batch("Trump · glass and spandrels", kit.material(0xffffff, { vertexColors: true }));
|
|
127
|
+
const mullions = kit.batch("Trump · stainless mullions", kit.material(0x9a9a9a));
|
|
128
|
+
const crown = kit.batch("Trump · crown", kit.material(0xffffff, { vertexColors: true }));
|
|
129
|
+
const fins = kit.batch("Trump · crown mullions", kit.material(0x8c8c8c));
|
|
130
|
+
const spire = kit.batch("Trump · spire", kit.material(0xd0d0d0));
|
|
131
|
+
const base = form.base;
|
|
132
|
+
const y = (real) => real - base;
|
|
133
|
+
const clamp = (real) => Math.max(real, base);
|
|
134
|
+
// The tiers, each a closed solid on the one below. The shaft stands to the shoulder all
|
|
135
|
+
// round, and its south part carries on to the roof.
|
|
136
|
+
const shaft = form.tiers.length - 1;
|
|
137
|
+
const bottoms = form.tiers.map((_, i) => (i === 0 ? 0 : form.tiers[i - 1].top));
|
|
138
|
+
const { shaft: shaftOutline, cap, crown: crownOutline } = topOutlines(form.tiers[shaft].outline, form.step, form.crown);
|
|
139
|
+
const outlines = form.tiers.map(({ outline }, i) => (i === shaft ? shaftOutline : orient(outline)));
|
|
140
|
+
const tops = form.tiers.map(({ top }, i) => (i === shaft ? h.shoulder : top));
|
|
141
|
+
const capPlan = planOf(cap);
|
|
142
|
+
outlines.forEach((outline, i) => { if (tops[i] > base)
|
|
143
|
+
kit.prism(shell, planOf(outline), [y(clamp(bottoms[i])), y(tops[i])]); });
|
|
144
|
+
if (h.roof > base)
|
|
145
|
+
kit.prism(shell, capPlan, [y(clamp(h.shoulder)), y(h.roof)]);
|
|
146
|
+
const wallRows = (from, to) => {
|
|
147
|
+
const rows = [{ lo: 0.4, hi: floorLines[0] - spandrelBelow, glass: true, floor: 0 }];
|
|
148
|
+
floorLines.forEach((line, i) => {
|
|
149
|
+
const next = floorLines[i + 1];
|
|
150
|
+
rows.push({ lo: line - spandrelBelow, hi: line + spandrelAbove, glass: false, floor: i + 1 });
|
|
151
|
+
rows.push({ lo: line + spandrelAbove, hi: next === undefined ? h.crownTop + 10 : next - spandrelBelow, glass: true, floor: i + 1 });
|
|
152
|
+
});
|
|
153
|
+
if (to <= from)
|
|
154
|
+
return [];
|
|
155
|
+
return rows.filter((row) => row.hi > from + 1e-6 && row.lo < to - 1e-6).map((row) => ({ ...row, lo: Math.max(row.lo, from), hi: Math.min(row.hi, to) }));
|
|
156
|
+
};
|
|
157
|
+
// A curtain wall on every run of an outline from `from` to the run's top: mullions one
|
|
158
|
+
// module apart along each wall, and between them glass and spandrel cells two modules
|
|
159
|
+
// wide, in one mitred skin along each stretch of a wall that starts and stops at the same
|
|
160
|
+
// heights. A skin starts just above its mullions, so their undersides never share a plane.
|
|
161
|
+
const curtainWall = (outline, runFrom, runTop, cells, frames, paint, seed) => {
|
|
162
|
+
const plan = planOf(outline);
|
|
163
|
+
chainsOf(plan, bend).forEach((chain, wall) => {
|
|
164
|
+
const stations = mullionStations(chain.length), columns = stations.filter((_, i) => i % 2 === 0);
|
|
165
|
+
const froms = chain.runs.map((index) => clamp(runFrom(plan[index])));
|
|
166
|
+
const ceilings = chain.runs.map((index) => runTop(plan[index]));
|
|
167
|
+
for (let k0 = 0; k0 < chain.runs.length;) {
|
|
168
|
+
let k1 = k0;
|
|
169
|
+
while (k1 + 1 < chain.runs.length && froms[k1 + 1] === froms[k0] && ceilings[k1 + 1] === ceilings[k0])
|
|
170
|
+
k1 += 1;
|
|
171
|
+
const rows = wallRows(froms[k0] + 0.1, ceilings[k0]);
|
|
172
|
+
// At a wall's ends the glass stops short of a concave corner by the wedge its depth
|
|
173
|
+
// would push into the neighbouring wall's glass, and where a stretch meets one that
|
|
174
|
+
// something stands on, such as the crown on the step, it stops well clear of that.
|
|
175
|
+
const cornerTrim = (turn) => (turn > 0 ? 0.08 * Math.tan(turn / 2) + 0.005 : 0);
|
|
176
|
+
const s0 = chain.starts[k0] + (k0 === 0 ? cornerTrim(turnsAt(plan, chain.runs[0])[0]) : jointTrim(plan, chain, froms, k0, k0 - 1, clearance));
|
|
177
|
+
const s1 = chain.starts[k1] + plan[chain.runs[k1]].length - (k1 === chain.runs.length - 1 ? cornerTrim(turnsAt(plan, chain.runs[k1])[1]) : jointTrim(plan, chain, froms, k1, k1 + 1, clearance));
|
|
178
|
+
if (rows.length && s1 - s0 > 0.05) {
|
|
179
|
+
chainSkin(kit, cells, plan, chain, k0, k1, s0, s1, columns, [rows[0].lo, ...rows.map((row) => row.hi)], 0.02, 0.07, (bay, r) => paint(rows[r], bay, seed + wall), spandrel, y);
|
|
180
|
+
}
|
|
181
|
+
k0 = k1 + 1;
|
|
182
|
+
}
|
|
183
|
+
// Each mullion stands whole on one run and stops short of the glass's top.
|
|
184
|
+
for (const { k, s } of mullionsOn(plan, chain, froms)) {
|
|
185
|
+
const run = plan[chain.runs[k]], from = Math.max(froms[k], 0.3), top = ceilings[k] - 0.1;
|
|
186
|
+
if (from < top)
|
|
187
|
+
kit.box(frames, run.at(s), run.normal(0), mullionHalf, 0, mullionDepth, y(from), y(top));
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const towerPaint = (row, bay, wall) => (row.glass ? paneColor(row.floor, bay, wall) : spandrel);
|
|
192
|
+
outlines.forEach((outline, i) => {
|
|
193
|
+
if (tops[i] <= base)
|
|
194
|
+
return;
|
|
195
|
+
// The shaft's south runs carry on to the roof.
|
|
196
|
+
const southOf = (run) => i === shaft && run.at(run.length / 2)[1] > form.step + 1e-6;
|
|
197
|
+
curtainWall(outline, () => bottoms[i], (run) => (southOf(run) ? h.roof : tops[i]), glazing, mullions, towerPaint, 10 * i);
|
|
198
|
+
});
|
|
199
|
+
// The step's north face, between the shoulder and the roof, is glazed like the rest where
|
|
200
|
+
// the crown does not stand on it; the cap's other walls are the shaft's own, glazed above.
|
|
201
|
+
const onStep = (run) => Math.abs(run.at(0)[1] - form.step) < 1e-6 && Math.abs(run.at(run.length)[1] - form.step) < 1e-6;
|
|
202
|
+
const underCrown = (run) => inside(crownOutline, run.at(run.length / 2, 0.3));
|
|
203
|
+
curtainWall(cap, (run) => (onStep(run) && !underCrown(run) ? h.shoulder : h.roof), () => h.roof, glazing, mullions, towerPaint, 90);
|
|
204
|
+
// The crown: a darker glazed enclosure on the same curtain wall, from whichever roof it
|
|
205
|
+
// stands on, capped flat. Its walls start above the roof around them.
|
|
206
|
+
// The crown's outline breaks at the step too, so each of its walls starts above one roof.
|
|
207
|
+
const crownPlan = planOf(crownOutline);
|
|
208
|
+
// Its solid sinks half a metre into the shaft, clear of the roofs' planes.
|
|
209
|
+
kit.prism(shell, crownPlan, [y(clamp(h.shoulder - 0.5)), y(h.crownTop)]);
|
|
210
|
+
const crownPaint = (row) => (row.glass ? crownGlass : crownBand);
|
|
211
|
+
curtainWall(crownOutline, crownFoot(cap), () => h.crownTop, crown, fins, crownPaint, 100);
|
|
212
|
+
// The spire: three stacked twelve-sided sections, each capped by a fan from its axis, the
|
|
213
|
+
// joints and the tip where the photograph shows them.
|
|
214
|
+
const [sx, sz] = form.spire, sides = 12;
|
|
215
|
+
const section = (radius, lo, hi) => {
|
|
216
|
+
const ring = (v) => Array.from({ length: sides }, (_, i) => { const a = (i + 0.5) * 2 * Math.PI / sides; return [sx + radius * Math.cos(a), y(v), sz - radius * Math.sin(a)]; });
|
|
217
|
+
const [foot, head] = [ring(lo), ring(hi)];
|
|
218
|
+
for (let i = 0; i < sides; i += 1) {
|
|
219
|
+
const j = (i + 1) % sides, a = (i + 1) * 2 * Math.PI / sides, out = [Math.cos(a), 0, -Math.sin(a)];
|
|
220
|
+
kit.quad(spire, [foot[i], foot[j], head[j], head[i]], [out]);
|
|
221
|
+
kit.triangle(spire, [[sx, y(hi), sz], head[i], head[j]], [[0, 1, 0], [0, 1, 0], [0, 1, 0]]);
|
|
222
|
+
kit.triangle(spire, [[sx, y(lo), sz], foot[j], foot[i]], [[0, -1, 0], [0, -1, 0], [0, -1, 0]]);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
section(1.45, h.crownTop, h.joints[0]);
|
|
226
|
+
section(1.05, h.joints[0], h.joints[1]);
|
|
227
|
+
section(0.75, h.joints[1], h.tip);
|
|
228
|
+
return kit.finish({ height: y(h.tip), outlines: [shell, crown], opacity: 0.2 });
|
|
229
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BuildingModel, Plan } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const twoIllinoisLevels: Readonly<{
|
|
4
|
+
lobby: 4.25;
|
|
5
|
+
pitch: 3.55;
|
|
6
|
+
plant: 31;
|
|
7
|
+
roof: 114.3;
|
|
8
|
+
module: 1.524;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const twoIllinoisPalette: Readonly<{
|
|
11
|
+
glass: number[];
|
|
12
|
+
lit: 8026746;
|
|
13
|
+
dim: 4868682;
|
|
14
|
+
mullion: 3355443;
|
|
15
|
+
spandrel: 2829099;
|
|
16
|
+
plant: 3158064;
|
|
17
|
+
}>;
|
|
18
|
+
type Row = {
|
|
19
|
+
lo: number;
|
|
20
|
+
hi: number;
|
|
21
|
+
kind: "spandrel" | "glass" | "plant";
|
|
22
|
+
floor: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const twoIllinoisRows: Row[];
|
|
25
|
+
export declare function createTwoIllinoisCenterGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import { createBuilder, polygonOf } from "./building-kit.js";
|
|
3
|
+
import { chainSkin, chainsOf, orient, paintedSlab, planOf, turnsAt } from "./facade-grid.js";
|
|
4
|
+
// Two Illinois Center, 233 North Michigan Avenue (Fujikawa Johnson & Associates, after Mies
|
|
5
|
+
// van der Rohe's One Illinois Center, 1972): the geographic layout's model, on the mapped
|
|
6
|
+
// outline 236770799. The original layout has no model of it. From the photograph's camera it
|
|
7
|
+
// stands between Two Prudential Plaza and Aon Center, in front of River Plaza, and its lit
|
|
8
|
+
// floors fill the lower part of the gap the drawing's `office-west-of-aon` group paints.
|
|
9
|
+
//
|
|
10
|
+
// A dark slab of 32 storeys: a curtain wall of mullions on a 5 ft module, glass over a
|
|
11
|
+
// spandrel on every floor, and the top two floors' mechanical band, dark panels in place of
|
|
12
|
+
// glass, under a flat roof at the published 114.3 m. The floor pitch is the photograph's; see
|
|
13
|
+
// docs/river-plaza-two-illinois-center-reference.md. Units are meters; +x is east, +z is
|
|
14
|
+
// south.
|
|
15
|
+
export const twoIllinoisLevels = Object.freeze({
|
|
16
|
+
lobby: 4.25, // the lobby's height, what 31 floors at the pitch leave under the roof
|
|
17
|
+
pitch: 3.55, // each floor above it, measured on the photograph
|
|
18
|
+
plant: 31, // the first of the two mechanical floors
|
|
19
|
+
roof: 114.3, // the published height, the Skyscraper Center's 375 ft
|
|
20
|
+
module: 1.524, // the curtain wall's 5 ft module
|
|
21
|
+
});
|
|
22
|
+
const h = twoIllinoisLevels;
|
|
23
|
+
const floor = (n) => (n < 2 ? 0 : h.lobby + (n - 2) * h.pitch);
|
|
24
|
+
// The skins' colours: the window tones, and the mullions, spandrels and mechanical panels,
|
|
25
|
+
// which no window shares.
|
|
26
|
+
export const twoIllinoisPalette = Object.freeze({ glass: [0x1f1f1f, 0x232323, 0x272727, 0x1c1c1c], lit: 0x7a7a7a, dim: 0x4a4a4a, mullion: 0x333333, spandrel: 0x2b2b2b, plant: 0x303030 });
|
|
27
|
+
const palette = twoIllinoisPalette;
|
|
28
|
+
const color = (hex) => new THREE.Color(hex);
|
|
29
|
+
const glassTones = palette.glass.map(color);
|
|
30
|
+
const litGlass = color(palette.lit), dimGlass = color(palette.dim);
|
|
31
|
+
const mullion = color(palette.mullion), spandrel = color(palette.spandrel), plant = color(palette.plant);
|
|
32
|
+
const core = color(0x1a1a1a), roofing = color(0x303030);
|
|
33
|
+
function paneColor(row, bay, wall) {
|
|
34
|
+
const hash = (Math.imul(row + 53, 0x9e3779b1) ^ Math.imul(bay + 23, 0x85ebca77) ^ Math.imul(wall + 71, 0xc2b2ae3d)) >>> 0;
|
|
35
|
+
const value = hash % 101;
|
|
36
|
+
if (value < 9)
|
|
37
|
+
return litGlass;
|
|
38
|
+
if (value < 14)
|
|
39
|
+
return dimGlass;
|
|
40
|
+
return glassTones[value % glassTones.length];
|
|
41
|
+
}
|
|
42
|
+
export const twoIllinoisRows = [{ lo: 0.25, hi: h.lobby, kind: "glass", floor: 1 }];
|
|
43
|
+
for (let n = 2; n < h.plant; n += 1) {
|
|
44
|
+
twoIllinoisRows.push({ lo: floor(n), hi: floor(n) + 0.9, kind: "spandrel", floor: n });
|
|
45
|
+
twoIllinoisRows.push({ lo: floor(n) + 0.9, hi: floor(n + 1), kind: "glass", floor: n });
|
|
46
|
+
}
|
|
47
|
+
twoIllinoisRows.push({ lo: floor(h.plant), hi: h.roof, kind: "plant", floor: h.plant });
|
|
48
|
+
const rows = twoIllinoisRows;
|
|
49
|
+
export function createTwoIllinoisCenterGeographicBuilding(record, projectPlan, offset) {
|
|
50
|
+
const kit = createBuilder(record.name, record.id);
|
|
51
|
+
const shell = kit.batch("Two Illinois Center · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
52
|
+
const wall = kit.batch("Two Illinois Center · curtain wall", kit.material(0xffffff, { vertexColors: true }));
|
|
53
|
+
const lot = orient(polygonOf(projectPlan(record.footprint.coordinates)));
|
|
54
|
+
planOf(lot).forEach((run) => {
|
|
55
|
+
const a = run.at(0), b = run.at(run.length), n = run.normal(0);
|
|
56
|
+
kit.quad(shell, [[a[0], 0, a[1]], [b[0], 0, b[1]], [b[0], h.roof, b[1]], [a[0], h.roof, a[1]]], [[n[0], 0, n[1]]], core);
|
|
57
|
+
});
|
|
58
|
+
paintedSlab(kit, shell, lot, 0, false, roofing);
|
|
59
|
+
paintedSlab(kit, shell, lot, h.roof, true, roofing);
|
|
60
|
+
// Every face's curtain wall, one box to a chain, held 2 cm clear of its ends or 9 cm where
|
|
61
|
+
// the outline turns in: a 10 cm mullion on each 5 ft module, from the chain's middle out,
|
|
62
|
+
// with glass or spandrel between.
|
|
63
|
+
const plan = planOf(lot), heights = [rows[0].lo, ...rows.map((row) => row.hi)];
|
|
64
|
+
chainsOf(plan).forEach((chain, index) => {
|
|
65
|
+
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
66
|
+
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
67
|
+
const count = Math.floor(chain.length / 2 / h.module);
|
|
68
|
+
const lines = Array.from({ length: 2 * count + 1 }, (_, k) => chain.length / 2 + (k - count) * h.module);
|
|
69
|
+
const cuts = lines.flatMap((c) => [c - 0.05, c + 0.05]).filter((s) => s > s0 + 0.01 && s < s1 - 0.01);
|
|
70
|
+
const columns = [s0, ...cuts, s1];
|
|
71
|
+
chainSkin(kit, wall, plan, chain, 0, chain.runs.length - 1, s0, s1, columns, heights, 0.02, 0.08, (bay, r) => {
|
|
72
|
+
const row = rows[r], middle = (columns[bay - 1] + columns[bay]) / 2;
|
|
73
|
+
if (lines.some((c) => Math.abs(middle - c) < 0.05))
|
|
74
|
+
return mullion;
|
|
75
|
+
if (row.kind === "plant")
|
|
76
|
+
return plant;
|
|
77
|
+
return row.kind === "spandrel" ? spandrel : paneColor(row.floor, bay, index);
|
|
78
|
+
}, mullion);
|
|
79
|
+
});
|
|
80
|
+
const model = kit.finish({ height: h.roof, outlines: [shell], opacity: 0.16 });
|
|
81
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
82
|
+
model.building.userData["geography"] = record;
|
|
83
|
+
model.building.userData["geographicDetail"] = { levels: h, source: "docs/river-plaza-two-illinois-center-reference.md" };
|
|
84
|
+
return model;
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BuildingModel, Plan, Vec2 } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const twoPrudentialGeographicLevels: Readonly<{
|
|
4
|
+
pitch: 3.96;
|
|
5
|
+
lobbyTop: 11.52;
|
|
6
|
+
sill: 0.9;
|
|
7
|
+
head: 2.9;
|
|
8
|
+
lowerShoulder: 162;
|
|
9
|
+
lowerPeak: number;
|
|
10
|
+
middleShoulder: 193.68;
|
|
11
|
+
middlePeak: number;
|
|
12
|
+
eave: 229.32;
|
|
13
|
+
peak: 256;
|
|
14
|
+
apex: 280.2;
|
|
15
|
+
shaftTop: 295.5;
|
|
16
|
+
tip: 303.3;
|
|
17
|
+
floors: 64;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function areaCentroid(outline: Vec2[]): Vec2;
|
|
20
|
+
export declare function twoPrudentialFrame(ground: Plan): {
|
|
21
|
+
centre: Vec2;
|
|
22
|
+
along: Vec2;
|
|
23
|
+
fronts: [number, number];
|
|
24
|
+
};
|
|
25
|
+
export declare function createTwoPrudentialGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { polygonOf } from "./building-kit.js";
|
|
2
|
+
import { chainsOf } from "./facade-grid.js";
|
|
3
|
+
import { buildTwoPrudentialTower, twoPrudentialCore, twoPrudentialLevels } from "./two-prudential-tower.js";
|
|
4
|
+
// Two Prudential Plaza: the geographic layout's model, on the mapped outline 64388666. The
|
|
5
|
+
// tower is the shared generator in two-prudential-tower.ts, the same one the original
|
|
6
|
+
// layout's copy uses. Its core stands on the outline's area centroid, square to the mapped
|
|
7
|
+
// south wall; the lobby fills the mapped outline to the first office floor, and the tiers
|
|
8
|
+
// fill the depth between the core and the mapped north and south walls. See
|
|
9
|
+
// docs/two-prudential-reference.md. Units are meters; +x is east, +z is south.
|
|
10
|
+
export const twoPrudentialGeographicLevels = twoPrudentialLevels;
|
|
11
|
+
// The area centroid, not the mean of the traced vertices: this outline carries three extra
|
|
12
|
+
// points down its west wall, which would pull a vertex mean 4.9 m west and 2.7 m south.
|
|
13
|
+
export function areaCentroid(outline) {
|
|
14
|
+
let twice = 0, x = 0, z = 0;
|
|
15
|
+
outline.forEach((a, i) => {
|
|
16
|
+
const b = outline[(i + 1) % outline.length], cross = a[0] * b[1] - b[0] * a[1];
|
|
17
|
+
twice += cross;
|
|
18
|
+
x += (a[0] + b[0]) * cross;
|
|
19
|
+
z += (a[1] + b[1]) * cross;
|
|
20
|
+
});
|
|
21
|
+
return [x / (3 * twice), z / (3 * twice)];
|
|
22
|
+
}
|
|
23
|
+
// The core's frame on the mapped outline: its centre, the unit vector along the mapped south
|
|
24
|
+
// wall, and how far the mapped south and north walls stand from the centre along its normal.
|
|
25
|
+
export function twoPrudentialFrame(ground) {
|
|
26
|
+
const outline = polygonOf(ground), centre = areaCentroid(outline);
|
|
27
|
+
const chains = chainsOf(ground).map((chain) => ({ chain, normal: ground[chain.runs[0]].normal(0) }));
|
|
28
|
+
const wall = (sign) => chains.find(({ normal }) => normal[1] * sign > 0.9).chain;
|
|
29
|
+
const ends = (chain) => chain.runs.flatMap((index) => [ground[index].at(0), ground[index].at(ground[index].length)]);
|
|
30
|
+
const [start, end] = [ends(wall(1))[0], ends(wall(1)).at(-1)];
|
|
31
|
+
const length = Math.hypot(end[0] - start[0], end[1] - start[1]);
|
|
32
|
+
const along = [(end[0] - start[0]) / length, (end[1] - start[1]) / length], south = [-along[1], along[0]];
|
|
33
|
+
const depth = (p) => (p[0] - centre[0]) * south[0] + (p[1] - centre[1]) * south[1];
|
|
34
|
+
// The nearest point of each wall, less 2 cm, so each lower tier's front stays within the
|
|
35
|
+
// mapped outline across the whole of its width.
|
|
36
|
+
const fronts = [Math.min(...ends(wall(1)).map(depth)) - 0.02, Math.min(...ends(wall(-1)).map((p) => -depth(p))) - 0.02];
|
|
37
|
+
return { centre, along, fronts };
|
|
38
|
+
}
|
|
39
|
+
export function createTwoPrudentialGeographicBuilding(record, projectPlan, offset) {
|
|
40
|
+
const ground = projectPlan(record.footprint.coordinates);
|
|
41
|
+
const { centre, along, fronts } = twoPrudentialFrame(ground);
|
|
42
|
+
const model = buildTwoPrudentialTower({ name: record.name, id: record.id, centre, along, fronts, base: 0, lobby: ground });
|
|
43
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
44
|
+
model.building.userData["geography"] = record;
|
|
45
|
+
model.building.userData["geographicDetail"] = { levels: twoPrudentialGeographicLevels, core: twoPrudentialCore, floors: twoPrudentialLevels.floors, source: "docs/two-prudential-reference.md" };
|
|
46
|
+
return model;
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuildingModel, Vec3 } from "./building-kit.js";
|
|
2
|
+
export declare const twoPrudentialSkylineBase = 35.4;
|
|
3
|
+
export declare const twoPrudentialDrawnTierPeaks: Readonly<{
|
|
4
|
+
lower: 181.9;
|
|
5
|
+
middle: 217.2;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function createTwoPrudentialPlazaBuilding(): BuildingModel;
|
|
8
|
+
export declare const twoPrudentialFeatures: Record<string, Vec3 | Vec3[]>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { buildTwoPrudentialTower, pierStations, twoPrudentialCore, twoPrudentialGables, twoPrudentialLevels } from "./two-prudential-tower.js";
|
|
2
|
+
// Two Prudential Plaza on a clean version of its plan, in real meters, for the skyline
|
|
3
|
+
// study's original layout, which places this copy in skyline-study.ts. two-prudential-tower.ts
|
|
4
|
+
// builds it, and the geographic model on the mapped outline. The plan is the mapped one
|
|
5
|
+
// squared: the 40.8 x 37.5 m core, with each pair of tiers 4.5 m deep, so the lower tiers'
|
|
6
|
+
// fronts stand 27.75 m either side of its centre and the tower keeps the mapped 55.5 m
|
|
7
|
+
// depth. See docs/two-prudential-reference.md. Units are meters; +x is east and +z is south.
|
|
8
|
+
const front = 27.75;
|
|
9
|
+
// The skyline drawing shows the tower above One Prudential's podium, so its platform datum
|
|
10
|
+
// crosses the tower above the street: fitted with the placement in skyline-study.ts,
|
|
11
|
+
// which scales, turns, and places this copy. Its y = 0 is that datum.
|
|
12
|
+
export const twoPrudentialSkylineBase = 35.4;
|
|
13
|
+
// The drawing draws each tier's gable as one slope from its shoulders to a point about 2 m
|
|
14
|
+
// above the line its steps climb: in the night photograph it traces, each tier's dark glass
|
|
15
|
+
// head runs on into the dark strip of the wall behind it. This copy keeps the drawn points.
|
|
16
|
+
export const twoPrudentialDrawnTierPeaks = Object.freeze({ lower: 181.9, middle: 217.2 });
|
|
17
|
+
export function createTwoPrudentialPlazaBuilding() {
|
|
18
|
+
return buildTwoPrudentialTower({ name: "Two Prudential Plaza", id: "building-two-prudential-plaza", centre: [0, 0], along: [1, 0], fronts: [front, front], base: twoPrudentialSkylineBase, tierPeaks: twoPrudentialDrawnTierPeaks });
|
|
19
|
+
}
|
|
20
|
+
// Features the skyline test projects against the drawing, in this copy's coordinates.
|
|
21
|
+
const h = twoPrudentialLevels, g = twoPrudentialGables, [coreA, coreB] = twoPrudentialCore, drawn = twoPrudentialDrawnTierPeaks;
|
|
22
|
+
const at = (x, real, z) => [x, real - twoPrudentialSkylineBase, z];
|
|
23
|
+
const middleFront = coreB + (front - coreB) / 2;
|
|
24
|
+
// A pier's point halfway out to its face, so a present pier is met a little before it.
|
|
25
|
+
const halfPier = 0.175;
|
|
26
|
+
// Every pier across a gabled face, west to east, from its middle.
|
|
27
|
+
const across = (gable) => { const stations = pierStations(gable); return [...stations.map((r) => -r), ...[...stations].reverse()]; };
|
|
28
|
+
export const twoPrudentialFeatures = {
|
|
29
|
+
// The core's corners at the eave, and the points of its south and east gables.
|
|
30
|
+
twoEaveWest: at(-coreA, h.eave, coreB),
|
|
31
|
+
twoEaveNear: at(coreA, h.eave, coreB),
|
|
32
|
+
twoEaveEast: at(coreA, h.eave, -coreB),
|
|
33
|
+
twoSouthChevron: at(0, h.peak, coreB + 0.03),
|
|
34
|
+
twoEastChevron: at(coreA + 0.03, h.peak, 0),
|
|
35
|
+
twoPyramid: at(0, h.apex, 0),
|
|
36
|
+
twoSpire: at(0, h.tip, 0),
|
|
37
|
+
// The tiers' points and their shoulders at their fronts' corners.
|
|
38
|
+
twoMiddleChevron: at(0, drawn.middle, middleFront),
|
|
39
|
+
twoLowerChevron: at(0, drawn.lower, front),
|
|
40
|
+
twoMiddleWest: at(-g.middle.half, h.middleShoulder, middleFront),
|
|
41
|
+
twoMiddleEast: at(g.middle.half, h.middleShoulder, middleFront),
|
|
42
|
+
twoLowerWest: at(-g.lower.half, h.lowerShoulder, front),
|
|
43
|
+
twoLowerEast: at(g.lower.half, h.lowerShoulder, front),
|
|
44
|
+
// The core's south piers above the middle tier, all twelve.
|
|
45
|
+
twoSouthPiers: across(g.front).map((x) => at(x, 222, coreB + halfPier)),
|
|
46
|
+
// The east face's piers from its north corner, where the face's x runs north to south
|
|
47
|
+
// as z does: every one but the first south of the strip, which the drawing leaves out.
|
|
48
|
+
twoEastPiers: across(g.side).filter((_, index) => index !== 6).map((z) => at(coreA + halfPier, 204, z)),
|
|
49
|
+
// The lower tier's eight piers.
|
|
50
|
+
twoLowerPiers: across(g.lower).map((x) => at(x, 135, front + halfPier)),
|
|
51
|
+
// A window on the east face, clear of every neighbour, for hover.
|
|
52
|
+
twoFacade: at(coreA + 0.02, 180, -10.7),
|
|
53
|
+
// On the lower tier's front below the podium roof, which hides it: for scene raycasts.
|
|
54
|
+
twoBehindPodium: at(-10, 45, front),
|
|
55
|
+
};
|