@a2f0/skyline 0.2.19 → 0.2.20
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.
|
@@ -38,7 +38,8 @@ const messageTopFloors = zones.map(([, , count], zone) => h.floors.slice(0, zone
|
|
|
38
38
|
+ Math.floor((count + 5) / 2) - 1).reverse().slice(0, 3);
|
|
39
39
|
export const blueCrossFloors = [...zones.flatMap(([from, to, count]) => Array.from({ length: count }, (_, i) => from + (to - from) * i / count)), h.occupied];
|
|
40
40
|
const spandrelBelow = 0.3, spandrelAbove = 0.9;
|
|
41
|
-
const
|
|
41
|
+
const infillFront = 0.52, emblemClearance = 0.1;
|
|
42
|
+
const transom = { height: 0.18, back: 0.17, front: 0.22, cornerClearance: 0.25 };
|
|
42
43
|
const color = (hex) => new THREE.Color(hex);
|
|
43
44
|
const glassTones = [0x414141, 0x464646, 0x3c3c3c, 0x4a4a4a].map(color);
|
|
44
45
|
const litGlass = color(0x8a8a8a), dimGlass = color(0x5c5c5c);
|
|
@@ -76,14 +77,29 @@ const rows = [];
|
|
|
76
77
|
push(h.roof, h.top, "screen", -1);
|
|
77
78
|
}
|
|
78
79
|
const rowsBetween = (lo, hi) => rows.filter((row) => row.lo >= lo - 1e-6 && row.hi <= hi + 1e-6);
|
|
80
|
+
// The end wings carry their curtain wall through the lower and upper bands.
|
|
81
|
+
// Only the middle level is an open setback. Subdivide the old band backing into
|
|
82
|
+
// normal glazing/spandrel courses on the wings and exposed short block returns.
|
|
83
|
+
// FID-BCBS-005: 2012 oblique photographs, docs/blue-cross-reference.md.
|
|
84
|
+
const glazedBandRows = (cuts) => cuts.flatMap((row) => {
|
|
85
|
+
if (row.kind !== "band")
|
|
86
|
+
return [row];
|
|
87
|
+
const band = h.bands.find(([, hi]) => hi === row.hi);
|
|
88
|
+
const count = band === h.bands[1] ? 3 : 2;
|
|
89
|
+
return Array.from({ length: count }, (_, i) => {
|
|
90
|
+
const lo = row.lo + (row.hi - row.lo) * i / count;
|
|
91
|
+
const hi = row.lo + (row.hi - row.lo) * (i + 1) / count;
|
|
92
|
+
return [{ lo, hi: lo + 0.8, kind: "spandrel", floor: -1 }, { lo: lo + 0.8, hi, kind: "glass", floor: -1 }];
|
|
93
|
+
}).flat();
|
|
94
|
+
});
|
|
79
95
|
export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
80
96
|
const kit = createBuilder(record.name, record.id);
|
|
81
97
|
const shell = kit.batch("Blue Cross · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
82
98
|
const wall = kit.batch("Blue Cross · glass, spandrels and bands", kit.material(0xffffff, { vertexColors: true }));
|
|
83
99
|
const mullions = kit.batch("Blue Cross · mullions", kit.material(0x2c2c2c));
|
|
84
100
|
const bars = kit.batch("Blue Cross · band columns and emblems", kit.material(0xb4b4b4));
|
|
85
|
-
const infill = kit.batch("Blue Cross ·
|
|
86
|
-
const transoms = kit.batch("Blue Cross ·
|
|
101
|
+
const infill = kit.batch("Blue Cross · band infill", kit.material(0xffffff, { vertexColors: true }));
|
|
102
|
+
const transoms = kit.batch("Blue Cross · transoms", kit.material(0xb4b4b4));
|
|
87
103
|
const aluminium = color(0xb4b4b4);
|
|
88
104
|
const windows = [];
|
|
89
105
|
let windowColumns = 0;
|
|
@@ -134,10 +150,13 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
134
150
|
// every row, and mullions one module apart on the office floors. Each unbroken stretch of
|
|
135
151
|
// kept runs along a chain is one box, held 2 cm clear of its ends.
|
|
136
152
|
const skin = (corners, lo, hi, seed, keep, lift = 0) => {
|
|
137
|
-
const plan = planOf(corners)
|
|
138
|
-
const heights = [cuts[0].lo + lift, ...cuts.map((row) => row.hi)];
|
|
153
|
+
const plan = planOf(corners);
|
|
139
154
|
const top = Math.min(hi, h.roof) - 0.05;
|
|
140
155
|
chainsOf(plan).forEach((chain, index) => {
|
|
156
|
+
const shortFace = Math.abs(plan[chain.runs[0]].normal(0)[0]) > 0.9;
|
|
157
|
+
const sideGlazing = seed !== 0 || shortFace;
|
|
158
|
+
const cuts = sideGlazing ? glazedBandRows(rowsBetween(lo, hi)) : rowsBetween(lo, hi);
|
|
159
|
+
const heights = [cuts[0].lo + lift, ...cuts.map((row) => row.hi)];
|
|
141
160
|
const count = Math.max(1, Math.round(chain.length / h.module));
|
|
142
161
|
const stations = Array.from({ length: count + 1 }, (_, i) => chain.length * i / count);
|
|
143
162
|
const messageFace = seed === 0 && plan[chain.runs[0]].normal(0)[1] > 0.9 && chain.length > 50;
|
|
@@ -164,6 +183,21 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
164
183
|
return screen;
|
|
165
184
|
return spandrel;
|
|
166
185
|
}, spandrel);
|
|
186
|
+
if (sideGlazing) {
|
|
187
|
+
// Close each rail as a thin box following the mapped chain. Keep rails
|
|
188
|
+
// off the skin's top/bottom planes and ahead of its vertical mullions.
|
|
189
|
+
// Leave corner clearance for rails on adjoining/reentrant faces.
|
|
190
|
+
const railStart = s0 + transom.cornerClearance, railEnd = s1 - transom.cornerClearance;
|
|
191
|
+
const railK0 = chain.starts.findLastIndex((s) => s <= railStart);
|
|
192
|
+
const railK1 = chain.starts.findLastIndex((s) => s < railEnd);
|
|
193
|
+
for (const row of cuts.filter(({ kind }) => kind === "glass")) {
|
|
194
|
+
for (const [bottom, top] of [[row.lo - transom.height, row.lo], [row.hi, row.hi + transom.height]]) {
|
|
195
|
+
if (railEnd <= railStart || bottom <= heights[0] || top >= hi)
|
|
196
|
+
continue;
|
|
197
|
+
chainSkin(kit, transoms, plan, chain, railK0, railK1, railStart, railEnd, [], [bottom, top], transom.back, transom.front, () => aluminium, aluminium);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
167
201
|
// Mullions on each office run, whole on one run and held clear of its ends. Their
|
|
168
202
|
// backs stand a centimetre off the wall: on the block's side walls the middle band's
|
|
169
203
|
// mullions start just inside the end bays, whose closing walls lie in that plane and
|
|
@@ -207,7 +241,7 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
207
241
|
const emblem = (centre, t, n, y, shape) => {
|
|
208
242
|
// The corrected screen panels stand ahead of the old backing plane.
|
|
209
243
|
const point = ([u, v], depth) => [centre[0] + t[0] * u + n[0] * depth, y + v, centre[1] + t[1] * u + n[1] * depth];
|
|
210
|
-
const backDepth =
|
|
244
|
+
const backDepth = infillFront + emblemClearance, frontDepth = backDepth + 0.23;
|
|
211
245
|
const out = [n[0], 0, n[1]], back = [-n[0], 0, -n[1]];
|
|
212
246
|
shape.forEach((p, i) => {
|
|
213
247
|
const q = shape[(i + 1) % shape.length];
|
|
@@ -224,8 +258,8 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
224
258
|
const cross = [[0.7, 2.1], [0.7, 0.7], [2.1, 0.7], [2.1, -0.7], [0.7, -0.7], [0.7, -2.1],
|
|
225
259
|
[-0.7, -2.1], [-0.7, -0.7], [-2.1, -0.7], [-2.1, 0.7], [-0.7, 0.7], [-0.7, 2.1]];
|
|
226
260
|
const shield = [[-1.8, 2.1], [1.8, 2.1], [1.8, 0.3], [1.6, -0.7], [1.0, -1.5], [0, -2.1], [-1.0, -1.5], [-1.6, -0.7], [-1.8, 0.3]];
|
|
227
|
-
// The block's columns, exposed in each band and in the screen on its long faces:
|
|
228
|
-
//
|
|
261
|
+
// The block's columns, exposed in each band and in the screen on its long faces:
|
|
262
|
+
// eight south, six north. The emblems stand in the screen's first two south
|
|
229
263
|
// bays at the south face's west end, the cross west of the shield.
|
|
230
264
|
const faces = chainsOf(planOf(block)).filter(({ length }) => length > 50);
|
|
231
265
|
faces.forEach((chain) => {
|
|
@@ -233,16 +267,16 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
233
267
|
const a = run.at(0), b = last.at(last.length), n = run.normal(0), length = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
234
268
|
const t = [(b[0] - a[0]) / length, (b[1] - a[1]) / length];
|
|
235
269
|
const at = (s) => [a[0] + t[0] * s, a[1] + t[1] * s];
|
|
236
|
-
const pitch = length /
|
|
237
|
-
for (let i = 0; i <
|
|
270
|
+
const slotCount = n[1] > 0.9 ? 8 : 6, pitch = length / slotCount;
|
|
271
|
+
for (let i = 0; i < slotCount; i += 1) {
|
|
238
272
|
for (const [lo, hi] of [...h.bands, [h.roof + 0.4, h.top - 0.3]])
|
|
239
273
|
kit.box(bars, at((i + 0.5) * pitch), n, 0.45, 0.07, 0.4, lo + 0.1, hi - 0.1);
|
|
240
274
|
}
|
|
241
|
-
if (n[1] > 0.9) {
|
|
242
|
-
// FID-BCBS-002: the
|
|
275
|
+
if (Math.abs(n[1]) > 0.9) {
|
|
276
|
+
// FID-BCBS-002/005: the long elevations have narrow column slots,
|
|
243
277
|
// with curtain-wall panels between them, not an uninterrupted dark band.
|
|
244
|
-
//
|
|
245
|
-
//
|
|
278
|
+
// The northeast photograph resolves six on the north crown. The five
|
|
279
|
+
// internal north atrium bays alone do not establish facade spacing.
|
|
246
280
|
const panel = (target, s0, s1, levels, columns, back, front, paint, sides) => {
|
|
247
281
|
const k0 = chain.starts.findLastIndex((s) => s <= s0);
|
|
248
282
|
const k1 = chain.starts.findLastIndex((s) => s < s1);
|
|
@@ -270,21 +304,22 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
270
304
|
}
|
|
271
305
|
append(hi - 0.18, crown ? screen : glassTones[1]);
|
|
272
306
|
append(hi - 0.02, aluminium);
|
|
273
|
-
for (let bay = 0; bay <=
|
|
307
|
+
for (let bay = 0; bay <= slotCount; bay += 1) {
|
|
274
308
|
const s0 = bay === 0 ? 0.02 : (bay - 0.5) * pitch + 0.95;
|
|
275
|
-
const s1 = bay ===
|
|
276
|
-
panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2,
|
|
309
|
+
const s1 = bay === slotCount ? chain.length - 0.02 : (bay + 0.5) * pitch - 0.95;
|
|
310
|
+
panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2, infillFront, (cell, row) => cell === 1 || cell === 3 ? aluminium : paints[row], aluminium);
|
|
277
311
|
}
|
|
278
312
|
}
|
|
279
313
|
// Pale rails frame the office ribbons. They sit just ahead of the vertical
|
|
280
314
|
// mullions and never cross the exposed column slots or celebration panes.
|
|
281
315
|
for (const row of rows.filter(({ kind }) => kind === "glass")) {
|
|
282
|
-
for (const [lo, hi] of [[row.lo -
|
|
283
|
-
panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [],
|
|
316
|
+
for (const [lo, hi] of [[row.lo - transom.height, row.lo], [row.hi, row.hi + transom.height]]) {
|
|
317
|
+
panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [], transom.back, transom.front, () => aluminium, aluminium);
|
|
284
318
|
}
|
|
285
319
|
}
|
|
286
320
|
// The south face runs west to east; the emblems stand in its first two bays.
|
|
287
|
-
|
|
321
|
+
if (n[1] > 0.9)
|
|
322
|
+
[cross, shield].forEach((shape, i) => emblem(at((i + 1) * pitch), t, n, h.roof + 6.7, shape));
|
|
288
323
|
}
|
|
289
324
|
});
|
|
290
325
|
const model = kit.finish({ height: h.top, outlines: [shell, mullions], opacity: 0.16 });
|
|
@@ -117,8 +117,8 @@ export const colourMaterials = {
|
|
|
117
117
|
"glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x383838: "opaque spandrel", 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
|
|
118
118
|
mullions: ["dark metal"],
|
|
119
119
|
"band columns and emblems": ["band lights"],
|
|
120
|
-
"
|
|
121
|
-
|
|
120
|
+
"band infill": ["glass", { 0x383838: "opaque spandrel", 0x3e3e3e: "screen", 0xb4b4b4: "aluminium" }],
|
|
121
|
+
transoms: ["aluminium"],
|
|
122
122
|
},
|
|
123
123
|
// Blue-green glass, and the south face's white concrete frame.
|
|
124
124
|
"building-340-on-the-park": {
|
package/package.json
CHANGED
|
@@ -38,7 +38,8 @@ const messageTopFloors = zones.map(([, , count], zone) => h.floors.slice(0, zone
|
|
|
38
38
|
+ Math.floor((count + 5) / 2) - 1).reverse().slice(0, 3);
|
|
39
39
|
export const blueCrossFloors = [...zones.flatMap(([from, to, count]) => Array.from({ length: count }, (_, i) => from + (to - from) * i / count)), h.occupied];
|
|
40
40
|
const spandrelBelow = 0.3, spandrelAbove = 0.9;
|
|
41
|
-
const
|
|
41
|
+
const infillFront = 0.52, emblemClearance = 0.1;
|
|
42
|
+
const transom = { height: 0.18, back: 0.17, front: 0.22, cornerClearance: 0.25 };
|
|
42
43
|
const color = (hex) => new THREE.Color(hex);
|
|
43
44
|
const glassTones = [0x414141, 0x464646, 0x3c3c3c, 0x4a4a4a].map(color);
|
|
44
45
|
const litGlass = color(0x8a8a8a), dimGlass = color(0x5c5c5c);
|
|
@@ -76,14 +77,29 @@ const rows = [];
|
|
|
76
77
|
push(h.roof, h.top, "screen", -1);
|
|
77
78
|
}
|
|
78
79
|
const rowsBetween = (lo, hi) => rows.filter((row) => row.lo >= lo - 1e-6 && row.hi <= hi + 1e-6);
|
|
80
|
+
// The end wings carry their curtain wall through the lower and upper bands.
|
|
81
|
+
// Only the middle level is an open setback. Subdivide the old band backing into
|
|
82
|
+
// normal glazing/spandrel courses on the wings and exposed short block returns.
|
|
83
|
+
// FID-BCBS-005: 2012 oblique photographs, docs/blue-cross-reference.md.
|
|
84
|
+
const glazedBandRows = (cuts) => cuts.flatMap((row) => {
|
|
85
|
+
if (row.kind !== "band")
|
|
86
|
+
return [row];
|
|
87
|
+
const band = h.bands.find(([, hi]) => hi === row.hi);
|
|
88
|
+
const count = band === h.bands[1] ? 3 : 2;
|
|
89
|
+
return Array.from({ length: count }, (_, i) => {
|
|
90
|
+
const lo = row.lo + (row.hi - row.lo) * i / count;
|
|
91
|
+
const hi = row.lo + (row.hi - row.lo) * (i + 1) / count;
|
|
92
|
+
return [{ lo, hi: lo + 0.8, kind: "spandrel", floor: -1 }, { lo: lo + 0.8, hi, kind: "glass", floor: -1 }];
|
|
93
|
+
}).flat();
|
|
94
|
+
});
|
|
79
95
|
export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
80
96
|
const kit = createBuilder(record.name, record.id);
|
|
81
97
|
const shell = kit.batch("Blue Cross · shell", kit.material(0xffffff, { vertexColors: true }));
|
|
82
98
|
const wall = kit.batch("Blue Cross · glass, spandrels and bands", kit.material(0xffffff, { vertexColors: true }));
|
|
83
99
|
const mullions = kit.batch("Blue Cross · mullions", kit.material(0x2c2c2c));
|
|
84
100
|
const bars = kit.batch("Blue Cross · band columns and emblems", kit.material(0xb4b4b4));
|
|
85
|
-
const infill = kit.batch("Blue Cross ·
|
|
86
|
-
const transoms = kit.batch("Blue Cross ·
|
|
101
|
+
const infill = kit.batch("Blue Cross · band infill", kit.material(0xffffff, { vertexColors: true }));
|
|
102
|
+
const transoms = kit.batch("Blue Cross · transoms", kit.material(0xb4b4b4));
|
|
87
103
|
const aluminium = color(0xb4b4b4);
|
|
88
104
|
const windows = [];
|
|
89
105
|
let windowColumns = 0;
|
|
@@ -134,10 +150,13 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
134
150
|
// every row, and mullions one module apart on the office floors. Each unbroken stretch of
|
|
135
151
|
// kept runs along a chain is one box, held 2 cm clear of its ends.
|
|
136
152
|
const skin = (corners, lo, hi, seed, keep, lift = 0) => {
|
|
137
|
-
const plan = planOf(corners)
|
|
138
|
-
const heights = [cuts[0].lo + lift, ...cuts.map((row) => row.hi)];
|
|
153
|
+
const plan = planOf(corners);
|
|
139
154
|
const top = Math.min(hi, h.roof) - 0.05;
|
|
140
155
|
chainsOf(plan).forEach((chain, index) => {
|
|
156
|
+
const shortFace = Math.abs(plan[chain.runs[0]].normal(0)[0]) > 0.9;
|
|
157
|
+
const sideGlazing = seed !== 0 || shortFace;
|
|
158
|
+
const cuts = sideGlazing ? glazedBandRows(rowsBetween(lo, hi)) : rowsBetween(lo, hi);
|
|
159
|
+
const heights = [cuts[0].lo + lift, ...cuts.map((row) => row.hi)];
|
|
141
160
|
const count = Math.max(1, Math.round(chain.length / h.module));
|
|
142
161
|
const stations = Array.from({ length: count + 1 }, (_, i) => chain.length * i / count);
|
|
143
162
|
const messageFace = seed === 0 && plan[chain.runs[0]].normal(0)[1] > 0.9 && chain.length > 50;
|
|
@@ -164,6 +183,21 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
164
183
|
return screen;
|
|
165
184
|
return spandrel;
|
|
166
185
|
}, spandrel);
|
|
186
|
+
if (sideGlazing) {
|
|
187
|
+
// Close each rail as a thin box following the mapped chain. Keep rails
|
|
188
|
+
// off the skin's top/bottom planes and ahead of its vertical mullions.
|
|
189
|
+
// Leave corner clearance for rails on adjoining/reentrant faces.
|
|
190
|
+
const railStart = s0 + transom.cornerClearance, railEnd = s1 - transom.cornerClearance;
|
|
191
|
+
const railK0 = chain.starts.findLastIndex((s) => s <= railStart);
|
|
192
|
+
const railK1 = chain.starts.findLastIndex((s) => s < railEnd);
|
|
193
|
+
for (const row of cuts.filter(({ kind }) => kind === "glass")) {
|
|
194
|
+
for (const [bottom, top] of [[row.lo - transom.height, row.lo], [row.hi, row.hi + transom.height]]) {
|
|
195
|
+
if (railEnd <= railStart || bottom <= heights[0] || top >= hi)
|
|
196
|
+
continue;
|
|
197
|
+
chainSkin(kit, transoms, plan, chain, railK0, railK1, railStart, railEnd, [], [bottom, top], transom.back, transom.front, () => aluminium, aluminium);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
167
201
|
// Mullions on each office run, whole on one run and held clear of its ends. Their
|
|
168
202
|
// backs stand a centimetre off the wall: on the block's side walls the middle band's
|
|
169
203
|
// mullions start just inside the end bays, whose closing walls lie in that plane and
|
|
@@ -207,7 +241,7 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
207
241
|
const emblem = (centre, t, n, y, shape) => {
|
|
208
242
|
// The corrected screen panels stand ahead of the old backing plane.
|
|
209
243
|
const point = ([u, v], depth) => [centre[0] + t[0] * u + n[0] * depth, y + v, centre[1] + t[1] * u + n[1] * depth];
|
|
210
|
-
const backDepth =
|
|
244
|
+
const backDepth = infillFront + emblemClearance, frontDepth = backDepth + 0.23;
|
|
211
245
|
const out = [n[0], 0, n[1]], back = [-n[0], 0, -n[1]];
|
|
212
246
|
shape.forEach((p, i) => {
|
|
213
247
|
const q = shape[(i + 1) % shape.length];
|
|
@@ -224,8 +258,8 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
224
258
|
const cross = [[0.7, 2.1], [0.7, 0.7], [2.1, 0.7], [2.1, -0.7], [0.7, -0.7], [0.7, -2.1],
|
|
225
259
|
[-0.7, -2.1], [-0.7, -0.7], [-2.1, -0.7], [-2.1, 0.7], [-0.7, 0.7], [-0.7, 2.1]];
|
|
226
260
|
const shield = [[-1.8, 2.1], [1.8, 2.1], [1.8, 0.3], [1.6, -0.7], [1.0, -1.5], [0, -2.1], [-1.0, -1.5], [-1.6, -0.7], [-1.8, 0.3]];
|
|
227
|
-
// The block's columns, exposed in each band and in the screen on its long faces:
|
|
228
|
-
//
|
|
261
|
+
// The block's columns, exposed in each band and in the screen on its long faces:
|
|
262
|
+
// eight south, six north. The emblems stand in the screen's first two south
|
|
229
263
|
// bays at the south face's west end, the cross west of the shield.
|
|
230
264
|
const faces = chainsOf(planOf(block)).filter(({ length }) => length > 50);
|
|
231
265
|
faces.forEach((chain) => {
|
|
@@ -233,16 +267,16 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
233
267
|
const a = run.at(0), b = last.at(last.length), n = run.normal(0), length = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
234
268
|
const t = [(b[0] - a[0]) / length, (b[1] - a[1]) / length];
|
|
235
269
|
const at = (s) => [a[0] + t[0] * s, a[1] + t[1] * s];
|
|
236
|
-
const pitch = length /
|
|
237
|
-
for (let i = 0; i <
|
|
270
|
+
const slotCount = n[1] > 0.9 ? 8 : 6, pitch = length / slotCount;
|
|
271
|
+
for (let i = 0; i < slotCount; i += 1) {
|
|
238
272
|
for (const [lo, hi] of [...h.bands, [h.roof + 0.4, h.top - 0.3]])
|
|
239
273
|
kit.box(bars, at((i + 0.5) * pitch), n, 0.45, 0.07, 0.4, lo + 0.1, hi - 0.1);
|
|
240
274
|
}
|
|
241
|
-
if (n[1] > 0.9) {
|
|
242
|
-
// FID-BCBS-002: the
|
|
275
|
+
if (Math.abs(n[1]) > 0.9) {
|
|
276
|
+
// FID-BCBS-002/005: the long elevations have narrow column slots,
|
|
243
277
|
// with curtain-wall panels between them, not an uninterrupted dark band.
|
|
244
|
-
//
|
|
245
|
-
//
|
|
278
|
+
// The northeast photograph resolves six on the north crown. The five
|
|
279
|
+
// internal north atrium bays alone do not establish facade spacing.
|
|
246
280
|
const panel = (target, s0, s1, levels, columns, back, front, paint, sides) => {
|
|
247
281
|
const k0 = chain.starts.findLastIndex((s) => s <= s0);
|
|
248
282
|
const k1 = chain.starts.findLastIndex((s) => s < s1);
|
|
@@ -270,21 +304,22 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
|
|
|
270
304
|
}
|
|
271
305
|
append(hi - 0.18, crown ? screen : glassTones[1]);
|
|
272
306
|
append(hi - 0.02, aluminium);
|
|
273
|
-
for (let bay = 0; bay <=
|
|
307
|
+
for (let bay = 0; bay <= slotCount; bay += 1) {
|
|
274
308
|
const s0 = bay === 0 ? 0.02 : (bay - 0.5) * pitch + 0.95;
|
|
275
|
-
const s1 = bay ===
|
|
276
|
-
panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2,
|
|
309
|
+
const s1 = bay === slotCount ? chain.length - 0.02 : (bay + 0.5) * pitch - 0.95;
|
|
310
|
+
panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2, infillFront, (cell, row) => cell === 1 || cell === 3 ? aluminium : paints[row], aluminium);
|
|
277
311
|
}
|
|
278
312
|
}
|
|
279
313
|
// Pale rails frame the office ribbons. They sit just ahead of the vertical
|
|
280
314
|
// mullions and never cross the exposed column slots or celebration panes.
|
|
281
315
|
for (const row of rows.filter(({ kind }) => kind === "glass")) {
|
|
282
|
-
for (const [lo, hi] of [[row.lo -
|
|
283
|
-
panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [],
|
|
316
|
+
for (const [lo, hi] of [[row.lo - transom.height, row.lo], [row.hi, row.hi + transom.height]]) {
|
|
317
|
+
panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [], transom.back, transom.front, () => aluminium, aluminium);
|
|
284
318
|
}
|
|
285
319
|
}
|
|
286
320
|
// The south face runs west to east; the emblems stand in its first two bays.
|
|
287
|
-
|
|
321
|
+
if (n[1] > 0.9)
|
|
322
|
+
[cross, shield].forEach((shape, i) => emblem(at((i + 1) * pitch), t, n, h.roof + 6.7, shape));
|
|
288
323
|
}
|
|
289
324
|
});
|
|
290
325
|
const model = kit.finish({ height: h.top, outlines: [shell, mullions], opacity: 0.16 });
|
|
@@ -117,8 +117,8 @@ export const colourMaterials = {
|
|
|
117
117
|
"glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x383838: "opaque spandrel", 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
|
|
118
118
|
mullions: ["dark metal"],
|
|
119
119
|
"band columns and emblems": ["band lights"],
|
|
120
|
-
"
|
|
121
|
-
|
|
120
|
+
"band infill": ["glass", { 0x383838: "opaque spandrel", 0x3e3e3e: "screen", 0xb4b4b4: "aluminium" }],
|
|
121
|
+
transoms: ["aluminium"],
|
|
122
122
|
},
|
|
123
123
|
// Blue-green glass, and the south face's white concrete frame.
|
|
124
124
|
"building-340-on-the-park": {
|