@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,32 @@
|
|
|
1
|
+
import type { BuildingModel, Plan } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const chicagoAthleticLevels: Readonly<{
|
|
4
|
+
floors: number[];
|
|
5
|
+
arcade: [number, number];
|
|
6
|
+
cornice: [number, number];
|
|
7
|
+
roundels: number;
|
|
8
|
+
parapet: 45.52;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const chicagoAthleticPalette: Readonly<{
|
|
11
|
+
glass: number[];
|
|
12
|
+
lit: 7237230;
|
|
13
|
+
dim: 3815994;
|
|
14
|
+
stone: 7171437;
|
|
15
|
+
frieze: 8026746;
|
|
16
|
+
brick: 5921370;
|
|
17
|
+
}>;
|
|
18
|
+
type Kind = "stone" | "ground" | "arches" | "great" | "band" | "window" | "arcade" | "frieze" | "brick" | "attic" | "roundel";
|
|
19
|
+
type Row = {
|
|
20
|
+
lo: number;
|
|
21
|
+
hi: number;
|
|
22
|
+
kind: Kind;
|
|
23
|
+
floor: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const chicagoAthleticRows: Row[];
|
|
26
|
+
export declare const chicagoAthleticFront: Readonly<{
|
|
27
|
+
bays: [number, number, number][];
|
|
28
|
+
roundels: number[];
|
|
29
|
+
attic: [number, number];
|
|
30
|
+
}>;
|
|
31
|
+
export declare function createChicagoAthleticAssociationGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
// The Chicago Athletic Association, 12 South Michigan Avenue (Henry Ives Cobb, 1893): the
|
|
5
|
+
// geographic layout's model, on the mapped outline 147476152. The original layout has no
|
|
6
|
+
// model of it; the drawing paints its Michigan front in Willoughby Tower's group, below and
|
|
7
|
+
// left of the tower, right of the Gage Building.
|
|
8
|
+
//
|
|
9
|
+
// A Venetian Gothic front of stone and brick in three bays: an arched ground floor, a row of
|
|
10
|
+
// small arches, a great arcade over three floors, two floors of windows, the eighth floor's
|
|
11
|
+
// traceried arcade, a carved frieze and a projecting cornice, and a top storey of diaper
|
|
12
|
+
// brick pierced by nine roundels. The levels are the Historic American Buildings Survey's
|
|
13
|
+
// measured drawings, HABS IL-1226, from the first floor to the parapet's 149 ft 4 in; see
|
|
14
|
+
// docs/chicago-athletic-association-reference.md. Units are meters; +x is east, +z is south.
|
|
15
|
+
const ft = (feet, inches = 0) => (feet + inches / 12) * 0.3048;
|
|
16
|
+
export const chicagoAthleticLevels = Object.freeze({
|
|
17
|
+
// Each floor's level on the survey's section, the first to the eleventh.
|
|
18
|
+
floors: [0, ft(17, 5.5), ft(33), ft(46, 6.5), ft(62), ft(73, 1.5), ft(83, 5), ft(94, 1), ft(114, 6), ft(127, 3), ft(136, 5)],
|
|
19
|
+
arcade: [ft(93, 1), ft(112, 11)], // the eighth floor's traceried lights, read on the elevation
|
|
20
|
+
cornice: [ft(120, 9.5), ft(126, 11)], // the cornice's profile, read on the section
|
|
21
|
+
roundels: ft(144), // the roundels' centres, read on the elevation
|
|
22
|
+
parapet: 45.52, // the top of the parapet, measured at 149 ft 4 in
|
|
23
|
+
});
|
|
24
|
+
const h = chicagoAthleticLevels;
|
|
25
|
+
const floor = (n) => h.floors[n - 1];
|
|
26
|
+
// The skins' colours: the window tones, and the stone, the carved frieze and the brick, which
|
|
27
|
+
// no window shares.
|
|
28
|
+
export const chicagoAthleticPalette = Object.freeze({ glass: [0x262626, 0x2a2a2a, 0x2e2e2e, 0x323232], lit: 0x6e6e6e, dim: 0x3a3a3a, stone: 0x6d6d6d, frieze: 0x7a7a7a, brick: 0x5a5a5a });
|
|
29
|
+
const palette = chicagoAthleticPalette;
|
|
30
|
+
const color = (hex) => new THREE.Color(hex);
|
|
31
|
+
const glassTones = palette.glass.map(color);
|
|
32
|
+
const litGlass = color(palette.lit), dimGlass = color(palette.dim);
|
|
33
|
+
const stone = color(palette.stone), frieze = color(palette.frieze), brick = color(palette.brick);
|
|
34
|
+
const core = color(0x262626), roofing = color(0x3a3a3a);
|
|
35
|
+
function paneColor(row, bay, wall) {
|
|
36
|
+
const hash = (Math.imul(row + 31, 0x9e3779b1) ^ Math.imul(bay + 79, 0x85ebca77) ^ Math.imul(wall + 61, 0xc2b2ae3d)) >>> 0;
|
|
37
|
+
const value = hash % 101;
|
|
38
|
+
if (value < 4)
|
|
39
|
+
return litGlass;
|
|
40
|
+
if (value < 8)
|
|
41
|
+
return dimGlass;
|
|
42
|
+
return glassTones[value % glassTones.length];
|
|
43
|
+
}
|
|
44
|
+
export const chicagoAthleticRows = [
|
|
45
|
+
{ lo: 0.25, hi: 0.9, kind: "stone", floor: 1 },
|
|
46
|
+
{ lo: 0.9, hi: 4.3, kind: "ground", floor: 1 },
|
|
47
|
+
{ lo: 4.3, hi: floor(2) + 1.2, kind: "stone", floor: 2 },
|
|
48
|
+
{ lo: floor(2) + 1.2, hi: floor(3) - 1, kind: "arches", floor: 2 },
|
|
49
|
+
{ lo: floor(3) - 1, hi: floor(3) + 1, kind: "stone", floor: 3 },
|
|
50
|
+
{ lo: floor(3) + 1, hi: floor(6) - 0.8, kind: "great", floor: 3 },
|
|
51
|
+
{ lo: floor(6) - 0.8, hi: floor(6) + 0.9, kind: "band", floor: 6 },
|
|
52
|
+
{ lo: floor(6) + 0.9, hi: floor(7) - 0.4, kind: "window", floor: 6 },
|
|
53
|
+
{ lo: floor(7) - 0.4, hi: floor(7) + 0.8, kind: "stone", floor: 7 },
|
|
54
|
+
{ lo: floor(7) + 0.8, hi: h.arcade[0] - 0.3, kind: "window", floor: 7 },
|
|
55
|
+
{ lo: h.arcade[0] - 0.3, hi: h.arcade[0], kind: "stone", floor: 8 },
|
|
56
|
+
{ lo: h.arcade[0], hi: h.arcade[1], kind: "arcade", floor: 8 },
|
|
57
|
+
{ lo: h.arcade[1], hi: h.cornice[0], kind: "frieze", floor: 9 },
|
|
58
|
+
{ lo: h.cornice[0], hi: h.cornice[1], kind: "stone", floor: 9 },
|
|
59
|
+
{ lo: h.cornice[1], hi: floor(10) + 0.3, kind: "brick", floor: 10 },
|
|
60
|
+
{ lo: floor(10) + 0.3, hi: floor(10) + 2.2, kind: "attic", floor: 10 },
|
|
61
|
+
{ lo: floor(10) + 2.2, hi: h.roundels - 0.45, kind: "brick", floor: 11 },
|
|
62
|
+
{ lo: h.roundels - 0.45, hi: h.roundels + 0.45, kind: "roundel", floor: 11 },
|
|
63
|
+
{ lo: h.roundels + 0.45, hi: h.parapet, kind: "brick", floor: 11 },
|
|
64
|
+
];
|
|
65
|
+
const rows = chicagoAthleticRows;
|
|
66
|
+
// Along the front, as fractions of it from the south corner, read on the measured elevation:
|
|
67
|
+
// the three bays' openings between the stone quoins, in two, eight and two lights; the nine
|
|
68
|
+
// roundels, 90 cm across; and the top storey's one window, at its north end.
|
|
69
|
+
export const chicagoAthleticFront = Object.freeze({
|
|
70
|
+
bays: [[0.053, 0.195, 2], [0.254, 0.74, 8], [0.799, 0.941, 2]],
|
|
71
|
+
roundels: Array.from({ length: 9 }, (_, k) => 0.0773 + k * 0.1065),
|
|
72
|
+
attic: [0.84, 0.947],
|
|
73
|
+
});
|
|
74
|
+
const front = chicagoAthleticFront;
|
|
75
|
+
export function createChicagoAthleticAssociationGeographicBuilding(record, projectPlan, offset) {
|
|
76
|
+
const kit = createBuilder(record.name, record.id);
|
|
77
|
+
const shell = kit.batch("Chicago Athletic Association · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
78
|
+
const wall = kit.batch("Chicago Athletic Association · stone, brick and windows", kit.material(0xffffff, { vertexColors: true }));
|
|
79
|
+
const cornice = kit.batch("Chicago Athletic Association · cornice", kit.material(0x7a7a7a));
|
|
80
|
+
const lot = orient(polygonOf(projectPlan(record.footprint.coordinates)));
|
|
81
|
+
// The lot's south-east corner, on Michigan against the Gage Building, with the front running
|
|
82
|
+
// north to Willoughby Tower.
|
|
83
|
+
const corner = lot.reduce((best, p) => (p[0] + p[1] > best[0] + best[1] ? p : best));
|
|
84
|
+
const northEast = lot.reduce((best, p) => (p[0] - p[1] > best[0] - best[1] ? p : best));
|
|
85
|
+
const length = Math.hypot(northEast[0] - corner[0], northEast[1] - corner[1]);
|
|
86
|
+
const north = [(northEast[0] - corner[0]) / length, (northEast[1] - corner[1]) / length], west = [north[1], -north[0]];
|
|
87
|
+
const at = (a, b) => [corner[0] + west[0] * a + north[0] * b, corner[1] + west[1] * a + north[1] * b];
|
|
88
|
+
planOf(lot).forEach((run) => {
|
|
89
|
+
const a = run.at(0), b = run.at(run.length), n = run.normal(0);
|
|
90
|
+
kit.quad(shell, [[a[0], 0, a[1]], [b[0], 0, b[1]], [b[0], h.parapet, b[1]], [a[0], h.parapet, a[1]]], [[n[0], 0, n[1]]], core);
|
|
91
|
+
});
|
|
92
|
+
paintedSlab(kit, shell, lot, 0, false, roofing);
|
|
93
|
+
paintedSlab(kit, shell, lot, h.parapet, true, roofing);
|
|
94
|
+
// The cornice projects 88 cm along the front, as the elevation shows it, from 12 cm inside
|
|
95
|
+
// it, stopping 3 cm short of the neighbours' walls.
|
|
96
|
+
kit.prism(cornice, planOf(orient([at(-0.88, 0.03), at(-0.88, length - 0.03), at(0.12, length - 0.03), at(0.12, 0.03)])), [h.cornice[0], h.cornice[1]]);
|
|
97
|
+
// A wall's skin, one box to a chain the `keep` test accepts, held 2 cm clear of its ends
|
|
98
|
+
// or 9 cm where the outline turns in, over `span`; `cells` lays a wall's columns out across
|
|
99
|
+
// it, from its start, and paints a cell by its middle and row.
|
|
100
|
+
const skin = (seed, keep, span, cells) => {
|
|
101
|
+
const plan = planOf(lot), heights = [span[0].lo, ...span.map((row) => row.hi)];
|
|
102
|
+
chainsOf(plan).forEach((chain, index) => {
|
|
103
|
+
if (!chain.runs.every((run) => keep(plan[run])))
|
|
104
|
+
return;
|
|
105
|
+
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
106
|
+
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
107
|
+
const [columns, paint] = cells(chain.length, plan[first].at(0));
|
|
108
|
+
chainSkin(kit, wall, plan, chain, 0, chain.runs.length - 1, s0, s1, columns, heights, 0.02, 0.07, (bay, r) => {
|
|
109
|
+
const row = span[r], color = paint((columns[bay - 1] + columns[bay]) / 2, row, bay);
|
|
110
|
+
return color === glassTones[0] ? paneColor(row.floor, bay, seed * 8 + index) : color;
|
|
111
|
+
}, seed === 0 ? stone : brick);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const facing = (x, z) => (run) => run.normal(0)[0] * x + run.normal(0)[1] * z > 0.9;
|
|
115
|
+
// The front: each bay's lights between 15 cm colonnettes, which the bands and the frieze
|
|
116
|
+
// cross; the top storey's roundels and window in its brick.
|
|
117
|
+
const michigan = (chain, start) => {
|
|
118
|
+
const fromSouth = Math.hypot(start[0] - corner[0], start[1] - corner[1]) < chain / 2;
|
|
119
|
+
const s = (fraction) => (fromSouth ? fraction * chain : chain - fraction * chain);
|
|
120
|
+
const lights = front.bays.flatMap(([a, b, count]) => Array.from({ length: count }, (_, k) => {
|
|
121
|
+
const width = (b - a) * chain, step = width / count;
|
|
122
|
+
return [s(a) + (fromSouth ? 1 : -1) * (k * step + (k > 0 ? 0.075 : 0)), s(a) + (fromSouth ? 1 : -1) * ((k + 1) * step - (k < count - 1 ? 0.075 : 0))].sort((p, q) => p - q);
|
|
123
|
+
}));
|
|
124
|
+
const roundels = front.roundels.map((c) => [s(c) - 0.45, s(c) + 0.45]);
|
|
125
|
+
const attic = front.attic.map(s).sort((p, q) => p - q);
|
|
126
|
+
const cuts = [...lights.flat(), ...roundels.flat(), ...attic].filter((x) => x > 0.1 && x < chain - 0.1);
|
|
127
|
+
const inside = (spans, x) => spans.some(([a, b]) => x > a && x < b);
|
|
128
|
+
return [[0, ...[...new Set(cuts)].sort((p, q) => p - q), chain], (middle, row) => {
|
|
129
|
+
if (row.kind === "frieze")
|
|
130
|
+
return frieze;
|
|
131
|
+
if (row.kind === "band" || row.kind === "stone")
|
|
132
|
+
return stone;
|
|
133
|
+
if (row.kind === "brick")
|
|
134
|
+
return brick;
|
|
135
|
+
if (row.kind === "roundel")
|
|
136
|
+
return inside(roundels, middle) ? glassTones[0] : brick;
|
|
137
|
+
if (row.kind === "attic")
|
|
138
|
+
return inside([attic], middle) ? glassTones[0] : brick;
|
|
139
|
+
return inside(lights, middle) ? glassTones[0] : stone;
|
|
140
|
+
}];
|
|
141
|
+
};
|
|
142
|
+
// The other walls' rows: a window 1.8 m tall on each floor from the second, its sill
|
|
143
|
+
// 90 cm up, to the parapet.
|
|
144
|
+
const sides = [{ lo: 0.25, hi: floor(2) + 0.9, kind: "brick", floor: 1 }];
|
|
145
|
+
for (let n = 2; n <= 11; n += 1) {
|
|
146
|
+
sides.push({ lo: floor(n) + 0.9, hi: floor(n) + 2.7, kind: "window", floor: n });
|
|
147
|
+
sides.push({ lo: floor(n) + 2.7, hi: n < 11 ? floor(n + 1) + 0.9 : h.parapet, kind: "brick", floor: n });
|
|
148
|
+
}
|
|
149
|
+
// The alley and the light court's walls: windows 1.6 m wide, spread evenly about 3.2 m
|
|
150
|
+
// apart.
|
|
151
|
+
const court = (chain) => {
|
|
152
|
+
const count = Math.max(1, Math.round(chain / 3.2)), centres = Array.from({ length: count }, (_, i) => chain * (i + 0.5) / count);
|
|
153
|
+
return [[0, ...centres.flatMap((c) => [c - 0.8, c + 0.8]), chain], (middle, row) => (row.kind === "window" && centres.some((c) => Math.abs(middle - c) < 0.8) ? glassTones[0] : brick)];
|
|
154
|
+
};
|
|
155
|
+
// A run's middle, in metres west of the front and north of the south corner.
|
|
156
|
+
const place = (run) => {
|
|
157
|
+
const mid = run.at(run.length / 2), [x, z] = [mid[0] - corner[0], mid[1] - corner[1]];
|
|
158
|
+
return [x * west[0] + z * west[1], x * north[0] + z * north[1]];
|
|
159
|
+
};
|
|
160
|
+
// The Michigan front, as against the light court's east wall, 39 m back.
|
|
161
|
+
const isMichigan = (run) => facing(1, 0)(run) && place(run)[0] < 1;
|
|
162
|
+
// The party walls keep the bare shell: the Gage to the south, Willoughby Tower and the
|
|
163
|
+
// annex on Madison to the north, all taller, cover them. The light court's north wall,
|
|
164
|
+
// 6.8 m in from the party line, is not one.
|
|
165
|
+
const party = (run) => (facing(0, 1)(run) ? place(run)[1] < 1 : facing(0, -1)(run) && place(run)[1] > length - 3);
|
|
166
|
+
skin(0, isMichigan, rows, michigan);
|
|
167
|
+
skin(1, (run) => !isMichigan(run) && !party(run), sides, court);
|
|
168
|
+
const model = kit.finish({ height: h.parapet, outlines: [shell, cornice], opacity: 0.16 });
|
|
169
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
170
|
+
model.building.userData["geography"] = record;
|
|
171
|
+
model.building.userData["geographicDetail"] = { levels: h, source: "docs/chicago-athletic-association-reference.md" };
|
|
172
|
+
return model;
|
|
173
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BuildingModel, Vec3 } from "./building-kit.js";
|
|
2
|
+
export declare function createCrainBuilding(): BuildingModel;
|
|
3
|
+
export declare const crainSkylineBase = 39.6;
|
|
4
|
+
export declare function createCrainSkylineBuilding(): BuildingModel;
|
|
5
|
+
export declare const crainFeatures: Record<string, Vec3 | Vec3[]>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { buildCrainTower, crainFloors, crainMainRoof } from "./crain-tower.js";
|
|
2
|
+
// The Crain Communications Building on a clean version of its mapped plan, in real meters:
|
|
3
|
+
// the solo study shows it whole, and the skyline study places a copy cut at the drawing's
|
|
4
|
+
// datum. crain-tower.ts builds it, and the geographic model on the mapped parts.
|
|
5
|
+
//
|
|
6
|
+
// The plan is the OpenStreetMap outline squared up. The south-west half is an isosceles
|
|
7
|
+
// right triangle with 38.6 m legs. The north-east half is its twin across the split, its
|
|
8
|
+
// own split line parallel to the first and 4.95 m from it. The two touch along the middle,
|
|
9
|
+
// and a V-notch opens at each end of the split: 7.6 m deep at the south-east corner, and at
|
|
10
|
+
// the north-west, where the gap between them runs 13.4 m inward as the slot. Both roofs
|
|
11
|
+
// peak at the published 177.4 m and fall 1.225 m per metre along the diagonal, about 51°,
|
|
12
|
+
// measured on the photograph the drawing was traced from. OpenStreetMap's roof tags put the
|
|
13
|
+
// two peaks 5 m apart and the slopes near 55°, which the photograph does not show.
|
|
14
|
+
// See docs/crain-reference.md. Units are meters; +x is east and +z is south.
|
|
15
|
+
const halfWidth = 19.3, halfDepth = 22.8;
|
|
16
|
+
const offset = halfDepth - halfWidth;
|
|
17
|
+
const notch = 7.6, slotLength = 13.4;
|
|
18
|
+
const tip = 177.4, fall = 1.225;
|
|
19
|
+
const diagonal = [Math.SQRT1_2, Math.SQRT1_2];
|
|
20
|
+
// The south-west half, from its peak on the west face.
|
|
21
|
+
const peakWest = [-halfWidth, halfDepth - 2 * halfWidth];
|
|
22
|
+
const southWest = [-halfWidth, halfDepth], southEast = [halfWidth, halfDepth];
|
|
23
|
+
// The north-east half, from its peak at the north-west corner.
|
|
24
|
+
const northWest = [-halfWidth, -halfDepth], northEast = [halfWidth, -halfDepth];
|
|
25
|
+
const notchApex = [halfWidth - notch, halfDepth - notch], notchEast = [halfWidth, halfDepth - 2 * notch];
|
|
26
|
+
const slotWest = [peakWest[0] + slotLength * diagonal[0], peakWest[1] + slotLength * diagonal[1]];
|
|
27
|
+
const slotEast = [slotWest[0] + offset, slotWest[1] - offset];
|
|
28
|
+
const notchWest = [peakWest[0] + offset, peakWest[1] - offset];
|
|
29
|
+
const along = (from) => (point) => (point[0] - from[0]) * diagonal[0] + (point[1] - from[1]) * diagonal[1];
|
|
30
|
+
const southWestRoof = (point) => tip - fall * along(peakWest)(point);
|
|
31
|
+
const northEastRoof = (point) => tip - fall * along(northWest)(point);
|
|
32
|
+
function createCrain(base) {
|
|
33
|
+
return buildCrainTower({
|
|
34
|
+
name: "Crain Communications Building",
|
|
35
|
+
id: "building-crain-communications",
|
|
36
|
+
base,
|
|
37
|
+
grid: { origin: southWest, axis: [1, 0] },
|
|
38
|
+
volumes: [
|
|
39
|
+
{ corners: [peakWest, southWest, southEast, notchApex, slotWest], roof: southWestRoof, glazed: true },
|
|
40
|
+
{ corners: [northWest, northEast, notchEast, notchApex, slotWest, slotEast], roof: northEastRoof, glazed: true },
|
|
41
|
+
{ corners: [peakWest, slotWest, slotEast, notchWest], roof: () => crainMainRoof, glazed: false },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// The whole building from the street, for the solo study.
|
|
46
|
+
export function createCrainBuilding() {
|
|
47
|
+
return createCrain(0);
|
|
48
|
+
}
|
|
49
|
+
// The skyline drawing shows the tower from the photograph's treeline up, so its platform
|
|
50
|
+
// datum crosses the tower above the street: 39.6 m, fitted with the placement in
|
|
51
|
+
// skyline-study.ts, which scales and places this copy. Its y = 0 is that datum, and the
|
|
52
|
+
// lowest drawn sill is the twelfth floor's, the first above the lobby being the second.
|
|
53
|
+
export const crainSkylineBase = 39.6;
|
|
54
|
+
export function createCrainSkylineBuilding() {
|
|
55
|
+
return createCrain(crainSkylineBase);
|
|
56
|
+
}
|
|
57
|
+
// Features the skyline test projects against the drawing, in the skyline copy's own
|
|
58
|
+
// coordinates, derived from the geometry above. The drawn sills are the twenty-nine the
|
|
59
|
+
// left face shows below its shoulder, the twelfth floor's to the fortieth's, on the
|
|
60
|
+
// south-west corner the drawing's left edge traces.
|
|
61
|
+
const at = ([x, z], real) => [x, real - crainSkylineBase, z];
|
|
62
|
+
const sill = (floor) => crainFloors.lobbyTop + (floor - 2) * crainFloors.pitch + crainFloors.sill;
|
|
63
|
+
export const crainFeatures = {
|
|
64
|
+
crainSills: Array.from({ length: 29 }, (_, i) => at(southWest, sill(12 + i))),
|
|
65
|
+
// A point on the south face, below the roof and clear of every neighbour, for hover.
|
|
66
|
+
crainFacadeProbe: [0, 60, halfDepth + 0.05],
|
|
67
|
+
crainPeakWest: at(peakWest, southWestRoof(peakWest)),
|
|
68
|
+
crainPeakEast: at(northWest, northEastRoof(northWest)),
|
|
69
|
+
crainShoulderWest: at(southWest, southWestRoof(southWest)),
|
|
70
|
+
crainShoulderEast: at(northEast, northEastRoof(northEast)),
|
|
71
|
+
crainFoot: at(southEast, southWestRoof(southEast)),
|
|
72
|
+
crainStepEast: at(notchEast, northEastRoof(notchEast)),
|
|
73
|
+
crainSlotWest: at(slotWest, southWestRoof(slotWest)),
|
|
74
|
+
crainSlotEast: at(slotEast, northEastRoof(slotEast)),
|
|
75
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BuildingModel, Plan } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const crainGeographicLevels: Readonly<{
|
|
4
|
+
tip: 177.4;
|
|
5
|
+
fall: 1.225;
|
|
6
|
+
bearing: 133;
|
|
7
|
+
mainRoof: 152.5;
|
|
8
|
+
floors: 41;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function createCrainGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { polygonOf } from "./building-kit.js";
|
|
2
|
+
import { buildCrainTower, crainFloors, crainMainRoof } from "./crain-tower.js";
|
|
3
|
+
// Crain Communications Building at 150 North Michigan Avenue: the geographic layout's
|
|
4
|
+
// model, on the three mapped OpenStreetMap parts. Part 284816228 is the south-west half,
|
|
5
|
+
// 284816229 the north-east half, and 284816227 the wedge between them whose 152.5 m top is
|
|
6
|
+
// the slot's floor. The parts' outlines are kept exactly; the tower itself is the shared
|
|
7
|
+
// generator in crain-tower.ts, the same one the solo study's model uses.
|
|
8
|
+
//
|
|
9
|
+
// The roofs keep OSM's 133° downhill bearing. Their heights come from the photograph the
|
|
10
|
+
// drawing was traced from, not from OSM's roof tags. Those put the south-west peak 5 m below
|
|
11
|
+
// the north-east one and fall 75 and 73 m, 54° and 56°. Measured through the solved skyline
|
|
12
|
+
// camera, both peaks stand within half a metre of each other at the published 177.4 m, and
|
|
13
|
+
// both roofs fall 1.225 m per metre, about 51°. Those planes end the north-east half about
|
|
14
|
+
// 6.9 m above the south-west half's foot, where the drawing shows 7.3 m. See
|
|
15
|
+
// docs/crain-reference.md. Units are meters; +x is east, +z is south.
|
|
16
|
+
export const crainGeographicLevels = Object.freeze({
|
|
17
|
+
tip: 177.4,
|
|
18
|
+
fall: 1.225,
|
|
19
|
+
bearing: 133,
|
|
20
|
+
mainRoof: crainMainRoof,
|
|
21
|
+
floors: 41,
|
|
22
|
+
});
|
|
23
|
+
const h = crainGeographicLevels;
|
|
24
|
+
const angle = h.bearing * Math.PI / 180;
|
|
25
|
+
const downhill = [Math.sin(angle), -Math.cos(angle)];
|
|
26
|
+
// One-based facade rows above the lobby, not occupied floor numbers.
|
|
27
|
+
const crownBand = (row) => {
|
|
28
|
+
const level = crainFloors.lobbyTop + (row - 1) * crainFloors.pitch;
|
|
29
|
+
return [level + crainFloors.sill, level + crainFloors.head];
|
|
30
|
+
};
|
|
31
|
+
// Height falls along the downhill bearing from the part's highest corner.
|
|
32
|
+
function roofOf(corners) {
|
|
33
|
+
const along = ([x, z]) => x * downhill[0] + z * downhill[1];
|
|
34
|
+
const top = Math.min(...corners.map(along));
|
|
35
|
+
return (point) => h.tip - h.fall * (along(point) - top);
|
|
36
|
+
}
|
|
37
|
+
export function createCrainGeographicBuilding(record, projectPlan, offset) {
|
|
38
|
+
const corners = (part) => polygonOf(projectPlan(part.coordinates));
|
|
39
|
+
const volumes = record.parts.map((part) => {
|
|
40
|
+
const outline = corners(part);
|
|
41
|
+
return part.roofSlope
|
|
42
|
+
? { corners: outline, roof: roofOf(outline), glazed: true }
|
|
43
|
+
: { corners: outline, roof: () => part.top, glazed: false };
|
|
44
|
+
});
|
|
45
|
+
// Epstein's exterior photographs show downslope mullions and level crossbars,
|
|
46
|
+
// rather than the drawing model's grid aligned with the street axes.
|
|
47
|
+
const south = polygonOf(projectPlan(record.footprint.coordinates));
|
|
48
|
+
const southWest = south.reduce((best, p) => (p[1] - p[0] > best[1] - best[0] ? p : best));
|
|
49
|
+
const model = buildCrainTower({
|
|
50
|
+
name: record.name,
|
|
51
|
+
id: record.id,
|
|
52
|
+
base: 0,
|
|
53
|
+
grid: { origin: southWest, axis: downhill, edgeInset: 0.1 },
|
|
54
|
+
// Two louver bands beneath the slot, three dark crown strips, then a solid
|
|
55
|
+
// aluminum tip. Counts are observed; elevations follow the estimated 3.5 m
|
|
56
|
+
// story rhythm. See the dated evidence and uncertainties in the audit.
|
|
57
|
+
crown: {
|
|
58
|
+
officeTop: crownBand(40)[0],
|
|
59
|
+
louvers: [crownBand(40), crownBand(41)],
|
|
60
|
+
// Continue the rhythm as virtual rows, not additional occupied floors.
|
|
61
|
+
shadowStrips: [crownBand(43), crownBand(44), crownBand(45)],
|
|
62
|
+
},
|
|
63
|
+
volumes,
|
|
64
|
+
});
|
|
65
|
+
model.building.position.set(offset[0], 0, offset[1]);
|
|
66
|
+
model.building.userData["geography"] = record;
|
|
67
|
+
model.building.userData["geographicDetail"] = { levels: h, floors: h.floors, source: "docs/crain-reference.md" };
|
|
68
|
+
return model;
|
|
69
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BuildingModel, Vec2 } from "./building-kit.js";
|
|
2
|
+
export interface CrainVolume {
|
|
3
|
+
corners: Vec2[];
|
|
4
|
+
roof: (point: Vec2) => number;
|
|
5
|
+
glazed: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface CrainForm {
|
|
8
|
+
name: string;
|
|
9
|
+
id: string;
|
|
10
|
+
volumes: CrainVolume[];
|
|
11
|
+
base: number;
|
|
12
|
+
grid: {
|
|
13
|
+
origin: Vec2;
|
|
14
|
+
axis: Vec2;
|
|
15
|
+
edgeInset?: number;
|
|
16
|
+
};
|
|
17
|
+
crown?: {
|
|
18
|
+
officeTop: number;
|
|
19
|
+
louvers: readonly (readonly [number, number])[];
|
|
20
|
+
shadowStrips: readonly (readonly [number, number])[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare const crainFloors: Readonly<{
|
|
24
|
+
lobbyTop: 7;
|
|
25
|
+
pitch: 3.5;
|
|
26
|
+
sill: 0.9;
|
|
27
|
+
head: 2.55;
|
|
28
|
+
module: 1.524;
|
|
29
|
+
lobbyGlass: readonly [0.45, 5.9];
|
|
30
|
+
}>;
|
|
31
|
+
export declare const crainMainRoof = 152.5;
|
|
32
|
+
export declare function buildCrainTower(form: CrainForm): BuildingModel;
|