@a2f0/skyline 0.0.0-stage → 0.1.2
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 +224 -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,79 @@
|
|
|
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
|
+
// Swissôtel Chicago, 323 East Wacker Drive (Harry Weese, 1989): the geographic layout's model,
|
|
5
|
+
// on the hotel's mapped tower part 641288601. The original layout has no model of it. From
|
|
6
|
+
// the photograph's camera it stands behind Three Illinois Center and The Buckingham, and fills
|
|
7
|
+
// the seam where their corners meet.
|
|
8
|
+
//
|
|
9
|
+
// A triangular tower of reflective glass: a flush grid of thin mullions about 1.5 m apart and
|
|
10
|
+
// a transom at each floor, a panel to a floor, under a flat roof at the published 139.3 m. The
|
|
11
|
+
// floors divide that height by OpenStreetMap's 45 levels. See docs/swissotel-reference.md.
|
|
12
|
+
// Units are meters; +x is east, +z is south.
|
|
13
|
+
export const swissotelLevels = Object.freeze({
|
|
14
|
+
levels: 45, // OpenStreetMap's count, a panel to each
|
|
15
|
+
roof: 139.3, // the published height, the Skyscraper Center's 457 ft
|
|
16
|
+
module: 1.5, // the mullions' spacing, read on the photographs
|
|
17
|
+
frame: 0.06, // each mullion's and transom's width
|
|
18
|
+
coping: 0.1, // the roof's coping over the top panel
|
|
19
|
+
});
|
|
20
|
+
const h = swissotelLevels;
|
|
21
|
+
const pitch = h.roof / h.levels;
|
|
22
|
+
// The skins' colours: the window tones, and the frame, which no window shares.
|
|
23
|
+
export const swissotelPalette = Object.freeze({ glass: [0x2d2d2d, 0x313131, 0x353535, 0x292929], lit: 0x878787, dim: 0x515151, frame: 0x414141 });
|
|
24
|
+
const palette = swissotelPalette;
|
|
25
|
+
const color = (hex) => new THREE.Color(hex);
|
|
26
|
+
const glassTones = palette.glass.map(color);
|
|
27
|
+
const litGlass = color(palette.lit), dimGlass = color(palette.dim), frame = color(palette.frame);
|
|
28
|
+
const core = color(0x1e1e1e), roofing = color(0x323232);
|
|
29
|
+
function paneColor(row, bay, wall) {
|
|
30
|
+
const hash = (Math.imul(row + 71, 0x9e3779b1) ^ Math.imul(bay + 13, 0x85ebca77) ^ Math.imul(wall + 47, 0xc2b2ae3d)) >>> 0;
|
|
31
|
+
const value = hash % 101;
|
|
32
|
+
if (value < 12)
|
|
33
|
+
return litGlass;
|
|
34
|
+
if (value < 18)
|
|
35
|
+
return dimGlass;
|
|
36
|
+
return glassTones[value % glassTones.length];
|
|
37
|
+
}
|
|
38
|
+
export const swissotelRows = [];
|
|
39
|
+
for (let n = 1; n <= h.levels; n += 1) {
|
|
40
|
+
const lo = n === 1 ? 0.25 : (n - 1) * pitch + h.frame / 2, hi = n === h.levels ? h.roof - h.coping : n * pitch - h.frame / 2;
|
|
41
|
+
swissotelRows.push({ lo, hi, kind: "glass", floor: n });
|
|
42
|
+
swissotelRows.push({ lo: hi, hi: n === h.levels ? h.roof : n * pitch + h.frame / 2, kind: "frame", floor: n });
|
|
43
|
+
}
|
|
44
|
+
const rows = swissotelRows;
|
|
45
|
+
export function createSwissotelGeographicBuilding(record, projectPlan, offset) {
|
|
46
|
+
const kit = createBuilder(record.name, record.id);
|
|
47
|
+
const shell = kit.batch("Swissôtel · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
48
|
+
const wall = kit.batch("Swissôtel · curtain wall", kit.material(0xffffff, { vertexColors: true }));
|
|
49
|
+
const lot = orient(polygonOf(projectPlan(record.footprint.coordinates)));
|
|
50
|
+
planOf(lot).forEach((run) => {
|
|
51
|
+
const a = run.at(0), b = run.at(run.length), n = run.normal(0);
|
|
52
|
+
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);
|
|
53
|
+
});
|
|
54
|
+
paintedSlab(kit, shell, lot, 0, false, roofing);
|
|
55
|
+
paintedSlab(kit, shell, lot, h.roof, true, roofing);
|
|
56
|
+
// Every face's curtain wall, one box to a chain, held 2 cm clear of its ends or 9 cm where
|
|
57
|
+
// the outline turns in: a mullion on each module, from the chain's middle out, and the
|
|
58
|
+
// panels between.
|
|
59
|
+
const plan = planOf(lot), heights = [rows[0].lo, ...rows.map((row) => row.hi)];
|
|
60
|
+
chainsOf(plan).forEach((chain, index) => {
|
|
61
|
+
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
62
|
+
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
63
|
+
const count = Math.floor(chain.length / 2 / h.module);
|
|
64
|
+
const lines = Array.from({ length: 2 * count + 1 }, (_, k) => chain.length / 2 + (k - count) * h.module);
|
|
65
|
+
const cuts = lines.flatMap((c) => [c - h.frame / 2, c + h.frame / 2]).filter((s) => s > s0 + 0.01 && s < s1 - 0.01);
|
|
66
|
+
const columns = [s0, ...cuts, s1];
|
|
67
|
+
chainSkin(kit, wall, plan, chain, 0, chain.runs.length - 1, s0, s1, columns, heights, 0.02, 0.06, (bay, r) => {
|
|
68
|
+
const row = rows[r], middle = (columns[bay - 1] + columns[bay]) / 2;
|
|
69
|
+
if (row.kind === "frame" || lines.some((c) => Math.abs(middle - c) < h.frame / 2))
|
|
70
|
+
return frame;
|
|
71
|
+
return paneColor(row.floor, bay, index);
|
|
72
|
+
}, frame);
|
|
73
|
+
});
|
|
74
|
+
const model = kit.finish({ height: h.roof, outlines: [shell], opacity: 0.16 });
|
|
75
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
76
|
+
model.building.userData["geography"] = record;
|
|
77
|
+
model.building.userData["geographicDetail"] = { levels: h, source: "docs/swissotel-reference.md" };
|
|
78
|
+
return model;
|
|
79
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
// Three Illinois Center, 303 East Wacker Drive (Fujikawa Conterato Lohan and Associates,
|
|
5
|
+
// 1979): the geographic layout's model, on the mapped outline 95486958. The original layout
|
|
6
|
+
// has no model of it. From the photograph's camera its east end stands in the gap between
|
|
7
|
+
// 340 on the Park and The Buckingham, where the drawing's `buckingham-west` group paints an
|
|
8
|
+
// office tower of ribbon windows under a plain top.
|
|
9
|
+
//
|
|
10
|
+
// A dark bronze box after One and Two Illinois Center, of 30 storeys: a curtain wall of
|
|
11
|
+
// mullions on a 5 ft module, glass over a spandrel on each floor at the published 11.5 ft
|
|
12
|
+
// slab to slab, over the lobby's glass, and the top two floors' windowless mechanical
|
|
13
|
+
// penthouse, the spandrels' bronze with the mullions carried through, under a flat roof at
|
|
14
|
+
// the published 106.7 m. The penthouse was glazed around 2019; the model keeps it as the
|
|
15
|
+
// 2013 photograph shows it. See docs/three-illinois-center-reference.md. Units are meters;
|
|
16
|
+
// +x is east, +z is south.
|
|
17
|
+
export const threeIllinoisLevels = Object.freeze({
|
|
18
|
+
pitch: 3.505, // the published 11.5 ft slab to slab
|
|
19
|
+
lobby: 106.7 - 29 * 3.505, // the lobby's height, what the 29 floors over it leave under the roof
|
|
20
|
+
fascia: 1.26, // the lobby's fascia, the second floor's spandrel carried down
|
|
21
|
+
penthouse: 29, // the first of the two mechanical floors
|
|
22
|
+
roof: 106.7, // the published height, the Skyscraper Center's and Emporis's 350 ft
|
|
23
|
+
module: 1.524, // the curtain wall's 5 ft module
|
|
24
|
+
});
|
|
25
|
+
const h = threeIllinoisLevels;
|
|
26
|
+
const floor = (n) => (n < 2 ? 0 : h.lobby + (n - 2) * h.pitch);
|
|
27
|
+
// The skins' colours: the window tones, and the mullions and spandrels, which no window
|
|
28
|
+
// shares.
|
|
29
|
+
export const threeIllinoisPalette = Object.freeze({ glass: [0x1b1b1b, 0x1f1f1f, 0x232323, 0x181818], lit: 0x707070, dim: 0x464646, mullion: 0x323232, spandrel: 0x282828 });
|
|
30
|
+
const palette = threeIllinoisPalette;
|
|
31
|
+
const color = (hex) => new THREE.Color(hex);
|
|
32
|
+
const glassTones = palette.glass.map(color);
|
|
33
|
+
const litGlass = color(palette.lit), dimGlass = color(palette.dim);
|
|
34
|
+
const mullion = color(palette.mullion), spandrel = color(palette.spandrel);
|
|
35
|
+
const core = color(0x171717), roofing = color(0x2b2b2b);
|
|
36
|
+
function paneColor(row, bay, wall) {
|
|
37
|
+
const hash = (Math.imul(row + 61, 0x9e3779b1) ^ Math.imul(bay + 29, 0x85ebca77) ^ Math.imul(wall + 83, 0xc2b2ae3d)) >>> 0;
|
|
38
|
+
const value = hash % 101;
|
|
39
|
+
if (value < 9)
|
|
40
|
+
return litGlass;
|
|
41
|
+
if (value < 14)
|
|
42
|
+
return dimGlass;
|
|
43
|
+
return glassTones[value % glassTones.length];
|
|
44
|
+
}
|
|
45
|
+
export const threeIllinoisRows = [{ lo: 0.25, hi: h.lobby + 0.9 - h.fascia, kind: "glass", floor: 1 }];
|
|
46
|
+
for (let n = 2; n < h.penthouse; n += 1) {
|
|
47
|
+
threeIllinoisRows.push({ lo: n === 2 ? h.lobby + 0.9 - h.fascia : floor(n), hi: floor(n) + 0.9, kind: "spandrel", floor: n });
|
|
48
|
+
threeIllinoisRows.push({ lo: floor(n) + 0.9, hi: floor(n + 1), kind: "glass", floor: n });
|
|
49
|
+
}
|
|
50
|
+
threeIllinoisRows.push({ lo: floor(h.penthouse), hi: h.roof, kind: "penthouse", floor: h.penthouse });
|
|
51
|
+
const rows = threeIllinoisRows;
|
|
52
|
+
export function createThreeIllinoisCenterGeographicBuilding(record, projectPlan, offset) {
|
|
53
|
+
const kit = createBuilder(record.name, record.id);
|
|
54
|
+
const shell = kit.batch("Three Illinois Center · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
55
|
+
const wall = kit.batch("Three Illinois Center · curtain wall", kit.material(0xffffff, { vertexColors: true }));
|
|
56
|
+
const lot = orient(polygonOf(projectPlan(record.footprint.coordinates)));
|
|
57
|
+
planOf(lot).forEach((run) => {
|
|
58
|
+
const a = run.at(0), b = run.at(run.length), n = run.normal(0);
|
|
59
|
+
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);
|
|
60
|
+
});
|
|
61
|
+
paintedSlab(kit, shell, lot, 0, false, roofing);
|
|
62
|
+
paintedSlab(kit, shell, lot, h.roof, true, roofing);
|
|
63
|
+
// Every face's curtain wall, one box to a chain, held 2 cm clear of its ends or 9 cm where
|
|
64
|
+
// the outline turns in: a 10 cm mullion on each 5 ft module, from the chain's middle out,
|
|
65
|
+
// carried up through the penthouse, with glass or spandrel between.
|
|
66
|
+
const plan = planOf(lot), heights = [rows[0].lo, ...rows.map((row) => row.hi)];
|
|
67
|
+
chainsOf(plan).forEach((chain, index) => {
|
|
68
|
+
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
69
|
+
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
70
|
+
const count = Math.floor(chain.length / 2 / h.module);
|
|
71
|
+
const lines = Array.from({ length: 2 * count + 1 }, (_, k) => chain.length / 2 + (k - count) * h.module);
|
|
72
|
+
const cuts = lines.flatMap((c) => [c - 0.05, c + 0.05]).filter((s) => s > s0 + 0.01 && s < s1 - 0.01);
|
|
73
|
+
const columns = [s0, ...cuts, s1];
|
|
74
|
+
chainSkin(kit, wall, plan, chain, 0, chain.runs.length - 1, s0, s1, columns, heights, 0.02, 0.08, (bay, r) => {
|
|
75
|
+
const row = rows[r], middle = (columns[bay - 1] + columns[bay]) / 2;
|
|
76
|
+
if (lines.some((c) => Math.abs(middle - c) < 0.05))
|
|
77
|
+
return mullion;
|
|
78
|
+
return row.kind === "glass" ? paneColor(row.floor, bay, index) : spandrel;
|
|
79
|
+
}, mullion);
|
|
80
|
+
});
|
|
81
|
+
const model = kit.finish({ height: h.roof, outlines: [shell], opacity: 0.16 });
|
|
82
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
83
|
+
model.building.userData["geography"] = record;
|
|
84
|
+
model.building.userData["geographicDetail"] = { levels: h, source: "docs/three-illinois-center-reference.md" };
|
|
85
|
+
return model;
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { polygonOf } from "./building-kit.js";
|
|
2
|
+
import { buildTrumpTower, trumpLevels } from "./trump-tower.js";
|
|
3
|
+
// Trump International Hotel and Tower: the geographic layout's model, on the mapped podium
|
|
4
|
+
// 64594680, tiers 188338549 and 188338550, shaft 188338548 and crown 188338859, and the
|
|
5
|
+
// spire above the crown. The tower is the shared generator in
|
|
6
|
+
// trump-tower.ts, the same one the original layout's copy uses. The setbacks keep the mapped
|
|
7
|
+
// parts' 60, 120 and 200 m; the roof, the shoulder, the crown and the spire's joints are
|
|
8
|
+
// measured on the photograph, down from the published tip, where the map's 345 and 357 m
|
|
9
|
+
// run about ten metres low. The spire stands where the photograph shows it. See
|
|
10
|
+
// docs/trump-reference.md. Units are meters; +x is east, +z is south.
|
|
11
|
+
export const trumpGeographicLevels = Object.freeze({
|
|
12
|
+
podium: trumpLevels.podium,
|
|
13
|
+
setbacks: trumpLevels.setbacks,
|
|
14
|
+
roof: trumpLevels.roof,
|
|
15
|
+
shoulder: trumpLevels.shoulder,
|
|
16
|
+
crownTop: trumpLevels.crownTop,
|
|
17
|
+
joints: trumpLevels.joints,
|
|
18
|
+
tip: trumpLevels.tip, // published architectural height
|
|
19
|
+
});
|
|
20
|
+
const h = trumpGeographicLevels;
|
|
21
|
+
// The roof steps down north of the mapped shaft's east-face notch.
|
|
22
|
+
export const trumpNotch = [-113.6, -448.4];
|
|
23
|
+
// The spire stands where the photograph shows it on the crown: 4.05 m west of the mapped
|
|
24
|
+
// spire part's centre across the photograph's line of sight, whose depth it cannot show.
|
|
25
|
+
// The drawn crown matches the mapped crown part to three layer units; the mapped spire
|
|
26
|
+
// stands 24 units right of the drawn one. See docs/trump-reference.md.
|
|
27
|
+
export const trumpSpire = [-124.72, -458.66];
|
|
28
|
+
export function createTrumpGeographicBuilding(record, projectPlan, offset) {
|
|
29
|
+
const outline = (way) => polygonOf(projectPlan(record.parts.find((p) => p.way === way).coordinates));
|
|
30
|
+
const shaft = outline(188338548);
|
|
31
|
+
// The mapped vertex at the notch fixes the step's line.
|
|
32
|
+
const notch = shaft.reduce((best, p) => (Math.hypot(p[0] - trumpNotch[0], p[1] - trumpNotch[1]) < Math.hypot(best[0] - trumpNotch[0], best[1] - trumpNotch[1]) ? p : best));
|
|
33
|
+
const model = buildTrumpTower({
|
|
34
|
+
name: record.name,
|
|
35
|
+
id: record.id,
|
|
36
|
+
tiers: [
|
|
37
|
+
{ outline: polygonOf(projectPlan(record.footprint.coordinates)), top: h.podium },
|
|
38
|
+
{ outline: outline(188338549), top: h.setbacks[0] },
|
|
39
|
+
{ outline: outline(188338550), top: h.setbacks[1] },
|
|
40
|
+
{ outline: shaft, top: h.roof },
|
|
41
|
+
],
|
|
42
|
+
step: notch[1],
|
|
43
|
+
crown: outline(188338859),
|
|
44
|
+
spire: trumpSpire,
|
|
45
|
+
base: 0,
|
|
46
|
+
});
|
|
47
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
48
|
+
model.building.userData["geography"] = record;
|
|
49
|
+
model.building.userData["geographicDetail"] = { levels: h, source: "docs/trump-reference.md" };
|
|
50
|
+
return model;
|
|
51
|
+
}
|
|
@@ -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,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,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
|
+
}
|