@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.
Files changed (195) hide show
  1. package/NOTICE.md +20 -0
  2. package/README.md +52 -2
  3. package/docs/package.md +222 -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,180 @@
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
+ // Six North Michigan, the Montgomery Ward Building (Schmidt, Garden & Martin, 1899; four
5
+ // floors added by Holabird & Roche, 1923): the geographic layout's model, on the mapped
6
+ // outline 126982631. The original layout has no model of it; the drawing shows it right of
7
+ // Willoughby Tower, outside the excerpt it is fitted to.
8
+ //
9
+ // A sixteen-storey block, 27.5 m on Michigan by 49.6 m on Madison, under a projecting
10
+ // cornice. The tower, built in the middle of the Michigan front rather than on the corner
11
+ // first planned, rises flush with it: a floor above the block, a band, an arched stage of
12
+ // three tall windows to a face under a cornice, and a top stage of panels under a cap at
13
+ // 86 m, where the pyramid and statue came off in 1947. Heights are measured on the drawing
14
+ // down from that top; see docs/six-north-michigan-reference.md. Units are meters; +x is
15
+ // east, +z is south.
16
+ export const sixNorthLevels = Object.freeze({
17
+ block: 62.1, // the block's walls, under its cornice
18
+ cornice: 63.6, // the block's cornice and roof
19
+ band: 65, // the tower's band, over the floor above the block
20
+ bandTop: 66.9,
21
+ arches: 75.6, // the arched stage, under the tower's cornice
22
+ towerCornice: 78, // the tower's cornice, under the top stage
23
+ stage: 84.5, // the top stage, under its cap
24
+ top: 86, // the cap, the published height
25
+ towerSouth: 7.5, // the tower's south wall, north of Madison
26
+ towerNorth: 20, // its north wall
27
+ towerDepth: 12, // west of the Michigan front
28
+ });
29
+ const h = sixNorthLevels;
30
+ // Each floor's level: a 6 m ground floor, then 3.74 m floors to the block's cornice.
31
+ const floor = (n) => (n < 2 ? 0 : 6 + (n - 2) * 3.74);
32
+ const color = (hex) => new THREE.Color(hex);
33
+ const glassTones = [0x4c4c4c, 0x505050, 0x545454, 0x585858].map(color);
34
+ const litGlass = color(0x8a8a8a), dimGlass = color(0x5e5e5e);
35
+ const brick = color(0x6e6e6e), panel = color(0x9c9c9c), core = color(0x2e2e2e), roofing = color(0x484848);
36
+ function paneColor(row, bay, wall) {
37
+ const hash = (Math.imul(row + 31, 0x9e3779b1) ^ Math.imul(bay + 57, 0x85ebca77) ^ Math.imul(wall + 43, 0xc2b2ae3d)) >>> 0;
38
+ const value = hash % 101;
39
+ if (value < 4)
40
+ return litGlass;
41
+ if (value < 8)
42
+ return dimGlass;
43
+ return glassTones[value % glassTones.length];
44
+ }
45
+ const blockRows = [
46
+ { lo: 0.25, hi: 1, kind: "brick", floor: 1 },
47
+ { lo: 1, hi: 5.2, kind: "glass", floor: 1 },
48
+ { lo: 5.2, hi: floor(2) - 0.35, kind: "brick", floor: 1 },
49
+ ];
50
+ for (let n = 2; n <= 16; n += 1) {
51
+ blockRows.push({ lo: floor(n) - 0.35, hi: floor(n) + 1.25, kind: "brick", floor: n });
52
+ blockRows.push({ lo: floor(n) + 1.25, hi: floor(n + 1) - 0.35, kind: "glass", floor: n });
53
+ }
54
+ blockRows.push({ lo: floor(17) - 0.35, hi: h.block, kind: "brick", floor: 17 });
55
+ // The tower's rows over the block: the seventeenth floor's windows under the band; then the
56
+ // arched stage's small windows over the band and its tall windows, their heads narrowing
57
+ // for the last half-metre; then the top stage's panel.
58
+ const towerRows = [
59
+ { lo: h.block, hi: 62.95, kind: "brick", floor: 17 },
60
+ { lo: 62.95, hi: 64.77, kind: "floor17", floor: 17 },
61
+ { lo: 64.77, hi: h.band, kind: "brick", floor: 17 },
62
+ { lo: h.bandTop, hi: 68.2, kind: "small", floor: 18 },
63
+ { lo: 68.2, hi: 70.7, kind: "brick", floor: 18 },
64
+ { lo: 70.7, hi: 73.7, kind: "arch", floor: 19 },
65
+ { lo: 73.7, hi: 74.2, kind: "head", floor: 19 },
66
+ { lo: 74.2, hi: h.arches, kind: "brick", floor: 20 },
67
+ { lo: h.towerCornice, hi: 83.2, kind: "panel", floor: 21 },
68
+ { lo: 83.2, hi: h.stage, kind: "brick", floor: 21 },
69
+ ];
70
+ const rowsBetween = (rows, lo, hi) => rows
71
+ .map((row) => ({ ...row, lo: Math.max(row.lo, lo), hi: Math.min(row.hi, hi) }))
72
+ .filter((row) => row.hi - row.lo > 0.05);
73
+ export function createSixNorthMichiganGeographicBuilding(record, projectPlan, offset) {
74
+ const kit = createBuilder(record.name, record.id);
75
+ const shell = kit.batch("Six North Michigan · shell", kit.material(0xffffff, { vertexColors: true }));
76
+ const wall = kit.batch("Six North Michigan · brick and windows", kit.material(0xffffff, { vertexColors: true }));
77
+ const cornices = kit.batch("Six North Michigan · cornices", kit.material(0x747474));
78
+ const lot = orient(polygonOf(projectPlan(record.footprint.coordinates)));
79
+ // The lot's south-east corner, and its Madison and Michigan fronts running from it.
80
+ const corner = lot.reduce((best, p) => (p[0] + p[1] > best[0] + best[1] ? p : best));
81
+ const neighbours = [lot[(lot.indexOf(corner) + 1) % lot.length], lot[(lot.indexOf(corner) + lot.length - 1) % lot.length]];
82
+ const unit = (p) => { const d = Math.hypot(p[0] - corner[0], p[1] - corner[1]); return [(p[0] - corner[0]) / d, (p[1] - corner[1]) / d]; };
83
+ const [west, north] = neighbours[0][0] < neighbours[1][0] ? [unit(neighbours[0]), unit(neighbours[1])] : [unit(neighbours[1]), unit(neighbours[0])];
84
+ const at = (a, b) => [corner[0] + west[0] * a + north[0] * b, corner[1] + west[1] * a + north[1] * b];
85
+ // The inverse of `at`: a point's distances along Madison and Michigan, whose fronts meet
86
+ // a little off square.
87
+ const skew = west[0] * north[1] - west[1] * north[0];
88
+ const inLot = (p) => {
89
+ const [x, z] = [p[0] - corner[0], p[1] - corner[1]];
90
+ return [(x * north[1] - z * north[0]) / skew, (west[0] * z - west[1] * x) / skew];
91
+ };
92
+ const rect = (a0, a1, b0, b1) => orient([at(a0, b0), at(a1, b0), at(a1, b1), at(a0, b1)]);
93
+ const [depth, front] = [Math.max(...lot.map((p) => inLot(p)[0])), Math.max(...lot.map((p) => inLot(p)[1]))];
94
+ const tower = (grow) => rect(-grow, h.towerDepth + grow, h.towerSouth - grow, h.towerNorth + grow);
95
+ // A solid on an outline between two heights.
96
+ const solid = (corners, lo, hi) => {
97
+ planOf(corners).forEach((run) => {
98
+ const a = run.at(0), b = run.at(run.length), n = run.normal(0);
99
+ kit.quad(shell, [[a[0], lo, a[1]], [b[0], lo, b[1]], [b[0], hi, b[1]], [a[0], hi, a[1]]], [[n[0], 0, n[1]]], core);
100
+ });
101
+ paintedSlab(kit, shell, corners, lo, false, roofing);
102
+ paintedSlab(kit, shell, corners, hi, true, roofing);
103
+ };
104
+ solid(lot, 0, h.block);
105
+ solid(tower(0), h.block, h.arches);
106
+ solid(tower(-0.8), h.towerCornice, h.stage);
107
+ // The block's cornice overhangs its walls by a metre, stopping either side of the tower,
108
+ // which rises through it, 2 cm clear of its walls so that the two undersides do not share
109
+ // an edge; the tower's band, cornice and cap are its outline grown or shrunk.
110
+ const [notchNorth, notchSouth, notchBack] = [h.towerNorth + 0.02, h.towerSouth - 0.02, h.towerDepth + 0.02];
111
+ const corniceOutline = orient([at(-1, -1), at(depth + 1, -1), at(depth + 1, front + 1), at(-1, front + 1), at(-1, notchNorth), at(notchBack, notchNorth), at(notchBack, notchSouth), at(-1, notchSouth)]);
112
+ kit.prism(cornices, planOf(corniceOutline), [h.block, h.cornice]);
113
+ kit.prism(cornices, planOf(tower(0.5)), [h.band, h.bandTop]);
114
+ kit.prism(cornices, planOf(tower(1.2)), [h.arches, h.towerCornice]);
115
+ kit.prism(cornices, planOf(tower(-0.3)), [h.stage, h.top]);
116
+ // A wall's skin, one box to a chain the `keep` test accepts, held 2 cm clear of its ends
117
+ // or 9 cm where the outline turns in. `openings` lays a wall's columns out across it,
118
+ // from its start to its end, and says how far each reaches into a window: 0 is brick; a
119
+ // floor's window takes any other; the tower's tall windows 1 and over, its seventeenth
120
+ // floor's 2, its small windows 3, and their heads and the top stage's panel 4. Floors
121
+ // below `fromFloor` stay brick.
122
+ const glassFrom = { brick: 99, glass: 1, floor17: 2, small: 3, arch: 1, head: 4, panel: 4 };
123
+ const skin = (corners, rows, lo, hi, seed, keep, openings, fromFloor = 1) => {
124
+ const plan = planOf(corners), cut = rowsBetween(rows, lo, hi), heights = [cut[0].lo, ...cut.map((row) => row.hi)];
125
+ chainsOf(plan).forEach((chain, index) => {
126
+ if (!chain.runs.every((run) => keep(plan[run])))
127
+ return;
128
+ const first = chain.runs[0], last = chain.runs.at(-1);
129
+ const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
130
+ const [columns, reach] = openings(chain.length, plan[first].at(0), plan[last].at(plan[last].length));
131
+ chainSkin(kit, wall, plan, chain, 0, chain.runs.length - 1, s0, s1, columns, heights, 0.02, 0.07, (bay, r) => {
132
+ const row = cut[r], into = reach(bay);
133
+ if (row.kind === "panel")
134
+ return into >= 4 ? panel : brick;
135
+ if (row.floor < fromFloor)
136
+ return brick;
137
+ return into >= glassFrom[row.kind] ? paneColor(row.floor, bay, seed * 8 + index) : brick;
138
+ }, brick);
139
+ });
140
+ };
141
+ // Windows centred at `centres`, `width` wide, along a wall from its start.
142
+ const windows = (length, centres, width) => {
143
+ const inside = centres.filter((c) => c - width / 2 > 0.1 && c + width / 2 < length - 0.1);
144
+ return [[0, ...inside.flatMap((c) => [c - width / 2, c + width / 2]), length], (bay) => (bay % 2 === 0 && bay <= 2 * inside.length ? 1 : 0)];
145
+ };
146
+ // A position along a wall from its start, given in lot coordinates across it.
147
+ const along = (length, start, end, target, axis) => length * (target - inLot(start)[axis]) / (inLot(end)[axis] - inLot(start)[axis]);
148
+ const michigan = (run) => run.normal(0)[0] > 0.9, party = (run) => run.normal(0)[1] < -0.9;
149
+ // The Michigan front: three windows to each wing either side of the tower, and three to
150
+ // the tower between them, 3 m apart on the centres of the windows above.
151
+ const michiganFront = (length, start, end) => windows(length, [1.6, 3.75, 5.9, 10.75, 13.75, 16.75, 21.6, 23.75, 25.9].map((b) => along(length, start, end, b, 1)), 1.3);
152
+ // Madison, the alley and the north wall: a window every 2.75 m. The north wall is shared
153
+ // with 20 North Michigan, eight storeys high, and stays blank below its ninth floor.
154
+ const punched = (length) => { const bays = Math.round(length / 2.75); return windows(length, Array.from({ length: bays }, (_, i) => length * (i + 0.5) / bays), 1.1); };
155
+ skin(lot, blockRows, 0.25, h.block, 0, michigan, michiganFront);
156
+ skin(lot, blockRows, 0.25, h.block, 1, (run) => !michigan(run) && !party(run), punched);
157
+ skin(lot, blockRows, 0.25, h.block, 6, party, punched, 9);
158
+ // The tower's faces: three windows to a face, centred 3 m apart. A tall window is 2 m
159
+ // wide, its head the middle metre; the seventeenth floor's are 1.3 m and the small ones
160
+ // 1.1 m, all on the same centres.
161
+ const towerFace = (length) => {
162
+ const steps = [-1, -0.65, -0.55, -0.5, 0.5, 0.55, 0.65, 1];
163
+ const columns = [0, ...[-3, 0, 3].flatMap((d) => steps.map((s) => length / 2 + d + s)), length];
164
+ // Bays count from one: a pier, then 1, 2, 3, 4, 3, 2, 1 across each window.
165
+ return [columns, (bay) => (bay > 24 || (bay - 1) % 8 === 0 ? 0 : [0, 1, 2, 3, 4, 3, 2, 1][(bay - 1) % 8])];
166
+ };
167
+ // The top stage's panel, 7.2 m wide in the middle of each face.
168
+ const panelled = (length) => [[0, length / 2 - 3.6, length / 2 + 3.6, length], (bay) => (bay === 2 ? 4 : 0)];
169
+ // Over the block, the tower's Michigan face shows from the block's walls; the cornice
170
+ // hides its other faces' foot.
171
+ skin(tower(0), towerRows, h.block, h.band, 2, michigan, towerFace);
172
+ skin(tower(0), towerRows, h.cornice, h.band, 3, (run) => !michigan(run), towerFace);
173
+ skin(tower(0), towerRows, h.bandTop, h.arches, 4, () => true, towerFace);
174
+ skin(tower(-0.8), towerRows, h.towerCornice, h.stage, 5, () => true, panelled);
175
+ const model = kit.finish({ height: h.top, outlines: [shell, cornices], opacity: 0.16 });
176
+ model.building.position.set(offset[0], 0, offset[1]);
177
+ model.building.userData["geography"] = record;
178
+ model.building.userData["geographicDetail"] = { levels: h, source: "docs/six-north-michigan-reference.md" };
179
+ return model;
180
+ }