@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.
Files changed (195) hide show
  1. package/NOTICE.md +20 -0
  2. package/README.md +52 -2
  3. package/docs/package.md +224 -0
  4. package/docs/skyline-geography.md +503 -0
  5. package/lib/models/aon-center.d.ts +4 -0
  6. package/lib/models/aon-center.js +60 -0
  7. package/lib/models/aon-geographic.d.ts +9 -0
  8. package/lib/models/aon-geographic.js +35 -0
  9. package/lib/models/aon-tower.d.ts +23 -0
  10. package/lib/models/aon-tower.js +130 -0
  11. package/lib/models/blue-cross-geographic.d.ts +14 -0
  12. package/lib/models/blue-cross-geographic.js +248 -0
  13. package/lib/models/borg-warner-geographic.d.ts +20 -0
  14. package/lib/models/borg-warner-geographic.js +143 -0
  15. package/lib/models/buckingham-geographic.d.ts +10 -0
  16. package/lib/models/buckingham-geographic.js +121 -0
  17. package/lib/models/building-kit.d.ts +84 -0
  18. package/lib/models/building-kit.js +358 -0
  19. package/lib/models/celebrations.d.ts +39 -0
  20. package/lib/models/celebrations.js +44 -0
  21. package/lib/models/chicago-athletic-association-geographic.d.ts +32 -0
  22. package/lib/models/chicago-athletic-association-geographic.js +173 -0
  23. package/lib/models/crain-communications.d.ts +5 -0
  24. package/lib/models/crain-communications.js +75 -0
  25. package/lib/models/crain-geographic.d.ts +10 -0
  26. package/lib/models/crain-geographic.js +69 -0
  27. package/lib/models/crain-tower.d.ts +32 -0
  28. package/lib/models/crain-tower.js +378 -0
  29. package/lib/models/facade-grid.d.ts +18 -0
  30. package/lib/models/facade-grid.js +176 -0
  31. package/lib/models/gage-geographic.d.ts +19 -0
  32. package/lib/models/gage-geographic.js +154 -0
  33. package/lib/models/heritage-at-millennium-park.d.ts +3 -0
  34. package/lib/models/heritage-at-millennium-park.js +284 -0
  35. package/lib/models/heritage-geographic.d.ts +11 -0
  36. package/lib/models/heritage-geographic.js +208 -0
  37. package/lib/models/hyatt-west-tower-geographic.d.ts +26 -0
  38. package/lib/models/hyatt-west-tower-geographic.js +86 -0
  39. package/lib/models/keith-ascher-geographic.d.ts +29 -0
  40. package/lib/models/keith-ascher-geographic.js +110 -0
  41. package/lib/models/kemper-geographic.d.ts +8 -0
  42. package/lib/models/kemper-geographic.js +153 -0
  43. package/lib/models/kemper.d.ts +2 -0
  44. package/lib/models/kemper.js +78 -0
  45. package/lib/models/lake-view-geographic.d.ts +15 -0
  46. package/lib/models/lake-view-geographic.js +134 -0
  47. package/lib/models/maclean-center-geographic.d.ts +20 -0
  48. package/lib/models/maclean-center-geographic.js +162 -0
  49. package/lib/models/michigan-boulevard-geographic.d.ts +12 -0
  50. package/lib/models/michigan-boulevard-geographic.js +157 -0
  51. package/lib/models/michigan-plaza-south-geographic.d.ts +7 -0
  52. package/lib/models/michigan-plaza-south-geographic.js +84 -0
  53. package/lib/models/michigan-plaza-south.d.ts +2 -0
  54. package/lib/models/michigan-plaza-south.js +68 -0
  55. package/lib/models/millennium-park-plaza-geographic.d.ts +9 -0
  56. package/lib/models/millennium-park-plaza-geographic.js +103 -0
  57. package/lib/models/monroe-geographic.d.ts +19 -0
  58. package/lib/models/monroe-geographic.js +177 -0
  59. package/lib/models/north-michigan-180-geographic.d.ts +14 -0
  60. package/lib/models/north-michigan-180-geographic.js +117 -0
  61. package/lib/models/north-wabash-geographic.d.ts +14 -0
  62. package/lib/models/north-wabash-geographic.js +122 -0
  63. package/lib/models/on-the-park-geographic.d.ts +11 -0
  64. package/lib/models/on-the-park-geographic.js +245 -0
  65. package/lib/models/one-prudential-geographic.d.ts +12 -0
  66. package/lib/models/one-prudential-geographic.js +55 -0
  67. package/lib/models/one-prudential-plaza.d.ts +5 -0
  68. package/lib/models/one-prudential-plaza.js +72 -0
  69. package/lib/models/one-prudential-tower.d.ts +45 -0
  70. package/lib/models/one-prudential-tower.js +246 -0
  71. package/lib/models/peoples-gas-geographic.d.ts +23 -0
  72. package/lib/models/peoples-gas-geographic.js +250 -0
  73. package/lib/models/railway-exchange-geographic.d.ts +18 -0
  74. package/lib/models/railway-exchange-geographic.js +176 -0
  75. package/lib/models/river-plaza-geographic.d.ts +16 -0
  76. package/lib/models/river-plaza-geographic.js +104 -0
  77. package/lib/models/sheraton-grand-geographic.d.ts +24 -0
  78. package/lib/models/sheraton-grand-geographic.js +112 -0
  79. package/lib/models/six-north-michigan-geographic.d.ts +16 -0
  80. package/lib/models/six-north-michigan-geographic.js +180 -0
  81. package/lib/models/skyline-geography-data.d.ts +44 -0
  82. package/lib/models/skyline-geography-data.js +607 -0
  83. package/lib/models/skyline-geography.d.ts +23 -0
  84. package/lib/models/skyline-geography.js +322 -0
  85. package/lib/models/swissotel-geographic.d.ts +24 -0
  86. package/lib/models/swissotel-geographic.js +79 -0
  87. package/lib/models/three-illinois-center-geographic.d.ts +26 -0
  88. package/lib/models/three-illinois-center-geographic.js +86 -0
  89. package/lib/models/trump-geographic.d.ts +14 -0
  90. package/lib/models/trump-geographic.js +51 -0
  91. package/lib/models/trump-international-tower.d.ts +4 -0
  92. package/lib/models/trump-international-tower.js +84 -0
  93. package/lib/models/trump-tower.d.ts +43 -0
  94. package/lib/models/trump-tower.js +229 -0
  95. package/lib/models/two-illinois-center-geographic.d.ts +26 -0
  96. package/lib/models/two-illinois-center-geographic.js +85 -0
  97. package/lib/models/two-prudential-geographic.d.ts +25 -0
  98. package/lib/models/two-prudential-geographic.js +47 -0
  99. package/lib/models/two-prudential-plaza.d.ts +8 -0
  100. package/lib/models/two-prudential-plaza.js +55 -0
  101. package/lib/models/two-prudential-tower.d.ts +53 -0
  102. package/lib/models/two-prudential-tower.js +441 -0
  103. package/lib/models/university-club-geographic.d.ts +24 -0
  104. package/lib/models/university-club-geographic.js +217 -0
  105. package/lib/models/willoughby-tower-geographic.d.ts +17 -0
  106. package/lib/models/willoughby-tower-geographic.js +195 -0
  107. package/lib/models/window-illumination.d.ts +14 -0
  108. package/lib/models/window-illumination.js +64 -0
  109. package/lib/package-assets.d.ts +5 -0
  110. package/lib/package-assets.js +9 -0
  111. package/lib/skyline-package.d.ts +23 -0
  112. package/lib/skyline-package.js +25 -0
  113. package/lib/skyline-scene.d.ts +25 -0
  114. package/lib/skyline-scene.js +86 -0
  115. package/lib/study-types.d.ts +49 -0
  116. package/lib/study-types.js +1 -0
  117. package/lib/vendor/three-r186.d.ts +1 -0
  118. package/lib/vendor/three-r186.js +1 -0
  119. package/package.json +64 -4
  120. package/site/building-detail.html +54 -0
  121. package/site/building-detail.js +84 -0
  122. package/site/building-study.html +58 -0
  123. package/site/building-study.js +3 -0
  124. package/site/index.html +407 -0
  125. package/site/models/aon-center.js +60 -0
  126. package/site/models/aon-geographic.js +35 -0
  127. package/site/models/aon-tower.js +130 -0
  128. package/site/models/blue-cross-geographic.js +248 -0
  129. package/site/models/borg-warner-geographic.js +143 -0
  130. package/site/models/buckingham-geographic.js +121 -0
  131. package/site/models/building-kit.js +358 -0
  132. package/site/models/celebrations.js +44 -0
  133. package/site/models/chicago-athletic-association-geographic.js +173 -0
  134. package/site/models/crain-communications.js +75 -0
  135. package/site/models/crain-geographic.js +69 -0
  136. package/site/models/crain-reference.svg +10 -0
  137. package/site/models/crain-tower.js +378 -0
  138. package/site/models/facade-grid.js +176 -0
  139. package/site/models/gage-geographic.js +154 -0
  140. package/site/models/heritage-at-millennium-park.js +284 -0
  141. package/site/models/heritage-geographic.js +208 -0
  142. package/site/models/hyatt-west-tower-geographic.js +86 -0
  143. package/site/models/keith-ascher-geographic.js +110 -0
  144. package/site/models/kemper-geographic.js +153 -0
  145. package/site/models/kemper.js +78 -0
  146. package/site/models/lake-view-geographic.js +134 -0
  147. package/site/models/maclean-center-geographic.js +162 -0
  148. package/site/models/michigan-boulevard-geographic.js +157 -0
  149. package/site/models/michigan-plaza-south-geographic.js +84 -0
  150. package/site/models/michigan-plaza-south.js +68 -0
  151. package/site/models/millennium-park-plaza-geographic.js +103 -0
  152. package/site/models/monroe-geographic.js +177 -0
  153. package/site/models/north-michigan-180-geographic.js +117 -0
  154. package/site/models/north-wabash-geographic.js +122 -0
  155. package/site/models/on-the-park-geographic.js +245 -0
  156. package/site/models/one-prudential-geographic.js +55 -0
  157. package/site/models/one-prudential-plaza.js +72 -0
  158. package/site/models/one-prudential-tower.js +246 -0
  159. package/site/models/peoples-gas-geographic.js +250 -0
  160. package/site/models/railway-exchange-geographic.js +176 -0
  161. package/site/models/river-plaza-geographic.js +104 -0
  162. package/site/models/sheraton-grand-geographic.js +112 -0
  163. package/site/models/six-north-michigan-geographic.js +180 -0
  164. package/site/models/skyline-geography-data.js +607 -0
  165. package/site/models/skyline-geography.js +322 -0
  166. package/site/models/skyline-panorama.svg +3491 -0
  167. package/site/models/skyline-reference.svg +3090 -0
  168. package/site/models/swissotel-geographic.js +79 -0
  169. package/site/models/three-illinois-center-geographic.js +86 -0
  170. package/site/models/trump-geographic.js +51 -0
  171. package/site/models/trump-international-tower.js +84 -0
  172. package/site/models/trump-tower.js +229 -0
  173. package/site/models/two-illinois-center-geographic.js +85 -0
  174. package/site/models/two-prudential-geographic.js +47 -0
  175. package/site/models/two-prudential-plaza.js +55 -0
  176. package/site/models/two-prudential-tower.js +441 -0
  177. package/site/models/university-club-geographic.js +217 -0
  178. package/site/models/willoughby-tower-geographic.js +195 -0
  179. package/site/models/window-illumination.js +64 -0
  180. package/site/skyline-3d.html +92 -0
  181. package/site/skyline-3d.js +333 -0
  182. package/site/skyline-animated.svg +18795 -0
  183. package/site/skyline-comparison.js +97 -0
  184. package/site/skyline-original-fit.svg +18312 -0
  185. package/site/skyline-scene.js +86 -0
  186. package/site/skyline-study.html +83 -0
  187. package/site/skyline-study.js +109 -0
  188. package/site/skyline-webgl.html +667 -0
  189. package/site/stars.svg +176 -0
  190. package/site/study-loader.js +20 -0
  191. package/site/study-types.js +1 -0
  192. package/site/study-viewer.js +605 -0
  193. package/site/study.css +204 -0
  194. package/site/vendor/THREE-LICENSE.txt +21 -0
  195. 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
+ }