@a2f0/skyline 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +20 -0
- package/README.md +52 -2
- package/docs/package.md +224 -0
- package/docs/skyline-geography.md +503 -0
- package/lib/models/aon-center.d.ts +4 -0
- package/lib/models/aon-center.js +60 -0
- package/lib/models/aon-geographic.d.ts +9 -0
- package/lib/models/aon-geographic.js +35 -0
- package/lib/models/aon-tower.d.ts +23 -0
- package/lib/models/aon-tower.js +130 -0
- package/lib/models/blue-cross-geographic.d.ts +14 -0
- package/lib/models/blue-cross-geographic.js +248 -0
- package/lib/models/borg-warner-geographic.d.ts +20 -0
- package/lib/models/borg-warner-geographic.js +143 -0
- package/lib/models/buckingham-geographic.d.ts +10 -0
- package/lib/models/buckingham-geographic.js +121 -0
- package/lib/models/building-kit.d.ts +84 -0
- package/lib/models/building-kit.js +358 -0
- package/lib/models/celebrations.d.ts +39 -0
- package/lib/models/celebrations.js +44 -0
- package/lib/models/chicago-athletic-association-geographic.d.ts +32 -0
- package/lib/models/chicago-athletic-association-geographic.js +173 -0
- package/lib/models/crain-communications.d.ts +5 -0
- package/lib/models/crain-communications.js +75 -0
- package/lib/models/crain-geographic.d.ts +10 -0
- package/lib/models/crain-geographic.js +69 -0
- package/lib/models/crain-tower.d.ts +32 -0
- package/lib/models/crain-tower.js +378 -0
- package/lib/models/facade-grid.d.ts +18 -0
- package/lib/models/facade-grid.js +176 -0
- package/lib/models/gage-geographic.d.ts +19 -0
- package/lib/models/gage-geographic.js +154 -0
- package/lib/models/heritage-at-millennium-park.d.ts +3 -0
- package/lib/models/heritage-at-millennium-park.js +284 -0
- package/lib/models/heritage-geographic.d.ts +11 -0
- package/lib/models/heritage-geographic.js +208 -0
- package/lib/models/hyatt-west-tower-geographic.d.ts +26 -0
- package/lib/models/hyatt-west-tower-geographic.js +86 -0
- package/lib/models/keith-ascher-geographic.d.ts +29 -0
- package/lib/models/keith-ascher-geographic.js +110 -0
- package/lib/models/kemper-geographic.d.ts +8 -0
- package/lib/models/kemper-geographic.js +153 -0
- package/lib/models/kemper.d.ts +2 -0
- package/lib/models/kemper.js +78 -0
- package/lib/models/lake-view-geographic.d.ts +15 -0
- package/lib/models/lake-view-geographic.js +134 -0
- package/lib/models/maclean-center-geographic.d.ts +20 -0
- package/lib/models/maclean-center-geographic.js +162 -0
- package/lib/models/michigan-boulevard-geographic.d.ts +12 -0
- package/lib/models/michigan-boulevard-geographic.js +157 -0
- package/lib/models/michigan-plaza-south-geographic.d.ts +7 -0
- package/lib/models/michigan-plaza-south-geographic.js +84 -0
- package/lib/models/michigan-plaza-south.d.ts +2 -0
- package/lib/models/michigan-plaza-south.js +68 -0
- package/lib/models/millennium-park-plaza-geographic.d.ts +9 -0
- package/lib/models/millennium-park-plaza-geographic.js +103 -0
- package/lib/models/monroe-geographic.d.ts +19 -0
- package/lib/models/monroe-geographic.js +177 -0
- package/lib/models/north-michigan-180-geographic.d.ts +14 -0
- package/lib/models/north-michigan-180-geographic.js +117 -0
- package/lib/models/north-wabash-geographic.d.ts +14 -0
- package/lib/models/north-wabash-geographic.js +122 -0
- package/lib/models/on-the-park-geographic.d.ts +11 -0
- package/lib/models/on-the-park-geographic.js +245 -0
- package/lib/models/one-prudential-geographic.d.ts +12 -0
- package/lib/models/one-prudential-geographic.js +55 -0
- package/lib/models/one-prudential-plaza.d.ts +5 -0
- package/lib/models/one-prudential-plaza.js +72 -0
- package/lib/models/one-prudential-tower.d.ts +45 -0
- package/lib/models/one-prudential-tower.js +246 -0
- package/lib/models/peoples-gas-geographic.d.ts +23 -0
- package/lib/models/peoples-gas-geographic.js +250 -0
- package/lib/models/railway-exchange-geographic.d.ts +18 -0
- package/lib/models/railway-exchange-geographic.js +176 -0
- package/lib/models/river-plaza-geographic.d.ts +16 -0
- package/lib/models/river-plaza-geographic.js +104 -0
- package/lib/models/sheraton-grand-geographic.d.ts +24 -0
- package/lib/models/sheraton-grand-geographic.js +112 -0
- package/lib/models/six-north-michigan-geographic.d.ts +16 -0
- package/lib/models/six-north-michigan-geographic.js +180 -0
- package/lib/models/skyline-geography-data.d.ts +44 -0
- package/lib/models/skyline-geography-data.js +607 -0
- package/lib/models/skyline-geography.d.ts +23 -0
- package/lib/models/skyline-geography.js +322 -0
- package/lib/models/swissotel-geographic.d.ts +24 -0
- package/lib/models/swissotel-geographic.js +79 -0
- package/lib/models/three-illinois-center-geographic.d.ts +26 -0
- package/lib/models/three-illinois-center-geographic.js +86 -0
- package/lib/models/trump-geographic.d.ts +14 -0
- package/lib/models/trump-geographic.js +51 -0
- package/lib/models/trump-international-tower.d.ts +4 -0
- package/lib/models/trump-international-tower.js +84 -0
- package/lib/models/trump-tower.d.ts +43 -0
- package/lib/models/trump-tower.js +229 -0
- package/lib/models/two-illinois-center-geographic.d.ts +26 -0
- package/lib/models/two-illinois-center-geographic.js +85 -0
- package/lib/models/two-prudential-geographic.d.ts +25 -0
- package/lib/models/two-prudential-geographic.js +47 -0
- package/lib/models/two-prudential-plaza.d.ts +8 -0
- package/lib/models/two-prudential-plaza.js +55 -0
- package/lib/models/two-prudential-tower.d.ts +53 -0
- package/lib/models/two-prudential-tower.js +441 -0
- package/lib/models/university-club-geographic.d.ts +24 -0
- package/lib/models/university-club-geographic.js +217 -0
- package/lib/models/willoughby-tower-geographic.d.ts +17 -0
- package/lib/models/willoughby-tower-geographic.js +195 -0
- package/lib/models/window-illumination.d.ts +14 -0
- package/lib/models/window-illumination.js +64 -0
- package/lib/package-assets.d.ts +5 -0
- package/lib/package-assets.js +9 -0
- package/lib/skyline-package.d.ts +23 -0
- package/lib/skyline-package.js +25 -0
- package/lib/skyline-scene.d.ts +25 -0
- package/lib/skyline-scene.js +86 -0
- package/lib/study-types.d.ts +49 -0
- package/lib/study-types.js +1 -0
- package/lib/vendor/three-r186.d.ts +1 -0
- package/lib/vendor/three-r186.js +1 -0
- package/package.json +64 -4
- package/site/building-detail.html +54 -0
- package/site/building-detail.js +84 -0
- package/site/building-study.html +58 -0
- package/site/building-study.js +3 -0
- package/site/index.html +407 -0
- package/site/models/aon-center.js +60 -0
- package/site/models/aon-geographic.js +35 -0
- package/site/models/aon-tower.js +130 -0
- package/site/models/blue-cross-geographic.js +248 -0
- package/site/models/borg-warner-geographic.js +143 -0
- package/site/models/buckingham-geographic.js +121 -0
- package/site/models/building-kit.js +358 -0
- package/site/models/celebrations.js +44 -0
- package/site/models/chicago-athletic-association-geographic.js +173 -0
- package/site/models/crain-communications.js +75 -0
- package/site/models/crain-geographic.js +69 -0
- package/site/models/crain-reference.svg +10 -0
- package/site/models/crain-tower.js +378 -0
- package/site/models/facade-grid.js +176 -0
- package/site/models/gage-geographic.js +154 -0
- package/site/models/heritage-at-millennium-park.js +284 -0
- package/site/models/heritage-geographic.js +208 -0
- package/site/models/hyatt-west-tower-geographic.js +86 -0
- package/site/models/keith-ascher-geographic.js +110 -0
- package/site/models/kemper-geographic.js +153 -0
- package/site/models/kemper.js +78 -0
- package/site/models/lake-view-geographic.js +134 -0
- package/site/models/maclean-center-geographic.js +162 -0
- package/site/models/michigan-boulevard-geographic.js +157 -0
- package/site/models/michigan-plaza-south-geographic.js +84 -0
- package/site/models/michigan-plaza-south.js +68 -0
- package/site/models/millennium-park-plaza-geographic.js +103 -0
- package/site/models/monroe-geographic.js +177 -0
- package/site/models/north-michigan-180-geographic.js +117 -0
- package/site/models/north-wabash-geographic.js +122 -0
- package/site/models/on-the-park-geographic.js +245 -0
- package/site/models/one-prudential-geographic.js +55 -0
- package/site/models/one-prudential-plaza.js +72 -0
- package/site/models/one-prudential-tower.js +246 -0
- package/site/models/peoples-gas-geographic.js +250 -0
- package/site/models/railway-exchange-geographic.js +176 -0
- package/site/models/river-plaza-geographic.js +104 -0
- package/site/models/sheraton-grand-geographic.js +112 -0
- package/site/models/six-north-michigan-geographic.js +180 -0
- package/site/models/skyline-geography-data.js +607 -0
- package/site/models/skyline-geography.js +322 -0
- package/site/models/skyline-panorama.svg +3491 -0
- package/site/models/skyline-reference.svg +3090 -0
- package/site/models/swissotel-geographic.js +79 -0
- package/site/models/three-illinois-center-geographic.js +86 -0
- package/site/models/trump-geographic.js +51 -0
- package/site/models/trump-international-tower.js +84 -0
- package/site/models/trump-tower.js +229 -0
- package/site/models/two-illinois-center-geographic.js +85 -0
- package/site/models/two-prudential-geographic.js +47 -0
- package/site/models/two-prudential-plaza.js +55 -0
- package/site/models/two-prudential-tower.js +441 -0
- package/site/models/university-club-geographic.js +217 -0
- package/site/models/willoughby-tower-geographic.js +195 -0
- package/site/models/window-illumination.js +64 -0
- package/site/skyline-3d.html +92 -0
- package/site/skyline-3d.js +333 -0
- package/site/skyline-animated.svg +18795 -0
- package/site/skyline-comparison.js +97 -0
- package/site/skyline-original-fit.svg +18312 -0
- package/site/skyline-scene.js +86 -0
- package/site/skyline-study.html +83 -0
- package/site/skyline-study.js +109 -0
- package/site/skyline-webgl.html +667 -0
- package/site/stars.svg +176 -0
- package/site/study-loader.js +20 -0
- package/site/study-types.js +1 -0
- package/site/study-viewer.js +605 -0
- package/site/study.css +204 -0
- package/site/vendor/THREE-LICENSE.txt +21 -0
- package/site/vendor/three-r186.js +4228 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import type { BuildingModel, Vec2 } from "./building-kit.js";
|
|
3
|
+
import type { GeoBuilding, GeoStreet } from "./skyline-geography-data.js";
|
|
4
|
+
export declare const geographicOrigin: [number, number];
|
|
5
|
+
export declare function projectGround([longitude, lat]: [number, number]): [number, number];
|
|
6
|
+
export declare function footprintMetrics(coordinates: [number, number][]): {
|
|
7
|
+
min: Vec2;
|
|
8
|
+
max: Vec2;
|
|
9
|
+
center: Vec2;
|
|
10
|
+
size: Vec2;
|
|
11
|
+
area: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function createGeographicBuilding(record: GeoBuilding, offset?: [number, number]): BuildingModel;
|
|
14
|
+
export declare function streetWidth(street: GeoStreet): number;
|
|
15
|
+
export declare function createGeographicRoads(bounds: {
|
|
16
|
+
x: Vec2;
|
|
17
|
+
z: Vec2;
|
|
18
|
+
}, color: number, offset?: [number, number]): THREE.Mesh;
|
|
19
|
+
export declare function createGeographicGround(offset?: [number, number]): {
|
|
20
|
+
group: THREE.Group;
|
|
21
|
+
streets: THREE.Group;
|
|
22
|
+
footprints: THREE.LineSegments;
|
|
23
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import { line } from "./building-kit.js";
|
|
3
|
+
import { geographicBuildings, geographicStreets } from "./skyline-geography-data.js";
|
|
4
|
+
import { createAonGeographicBuilding } from "./aon-geographic.js";
|
|
5
|
+
import { createBlueCrossGeographicBuilding } from "./blue-cross-geographic.js";
|
|
6
|
+
import { createBorgWarnerGeographicBuilding } from "./borg-warner-geographic.js";
|
|
7
|
+
import { createBuckinghamGeographicBuilding } from "./buckingham-geographic.js";
|
|
8
|
+
import { createChicagoAthleticAssociationGeographicBuilding } from "./chicago-athletic-association-geographic.js";
|
|
9
|
+
import { createCrainGeographicBuilding } from "./crain-geographic.js";
|
|
10
|
+
import { createGageGeographicBuilding } from "./gage-geographic.js";
|
|
11
|
+
import { createHeritageGeographicBuilding } from "./heritage-geographic.js";
|
|
12
|
+
import { createHyattWestTowerGeographicBuilding } from "./hyatt-west-tower-geographic.js";
|
|
13
|
+
import { createAscherGeographicBuilding, createKeithGeographicBuilding } from "./keith-ascher-geographic.js";
|
|
14
|
+
import { createKemperGeographicBuilding } from "./kemper-geographic.js";
|
|
15
|
+
import { createLakeViewGeographicBuilding } from "./lake-view-geographic.js";
|
|
16
|
+
import { createMacleanCenterGeographicBuilding } from "./maclean-center-geographic.js";
|
|
17
|
+
import { createMichiganBoulevardGeographicBuilding } from "./michigan-boulevard-geographic.js";
|
|
18
|
+
import { createMichiganPlazaSouthGeographicBuilding } from "./michigan-plaza-south-geographic.js";
|
|
19
|
+
import { createMillenniumParkPlazaGeographicBuilding } from "./millennium-park-plaza-geographic.js";
|
|
20
|
+
import { createMonroeGeographicBuilding } from "./monroe-geographic.js";
|
|
21
|
+
import { createNorthMichigan180GeographicBuilding } from "./north-michigan-180-geographic.js";
|
|
22
|
+
import { createNorthWabashGeographicBuilding } from "./north-wabash-geographic.js";
|
|
23
|
+
import { createOnTheParkGeographicBuilding } from "./on-the-park-geographic.js";
|
|
24
|
+
import { createOnePrudentialGeographicBuilding } from "./one-prudential-geographic.js";
|
|
25
|
+
import { createPeoplesGasGeographicBuilding } from "./peoples-gas-geographic.js";
|
|
26
|
+
import { createRailwayExchangeGeographicBuilding } from "./railway-exchange-geographic.js";
|
|
27
|
+
import { createRiverPlazaGeographicBuilding } from "./river-plaza-geographic.js";
|
|
28
|
+
import { createSheratonGrandGeographicBuilding } from "./sheraton-grand-geographic.js";
|
|
29
|
+
import { createSixNorthMichiganGeographicBuilding } from "./six-north-michigan-geographic.js";
|
|
30
|
+
import { createSwissotelGeographicBuilding } from "./swissotel-geographic.js";
|
|
31
|
+
import { createThreeIllinoisCenterGeographicBuilding } from "./three-illinois-center-geographic.js";
|
|
32
|
+
import { createTrumpGeographicBuilding } from "./trump-geographic.js";
|
|
33
|
+
import { createTwoIllinoisCenterGeographicBuilding } from "./two-illinois-center-geographic.js";
|
|
34
|
+
import { createTwoPrudentialGeographicBuilding } from "./two-prudential-geographic.js";
|
|
35
|
+
import { createUniversityClubGeographicBuilding } from "./university-club-geographic.js";
|
|
36
|
+
import { createWilloughbyTowerGeographicBuilding } from "./willoughby-tower-geographic.js";
|
|
37
|
+
// Local WGS84 tangent plane, centered on the bounding-box center of Crain's
|
|
38
|
+
// mapped footprint. Ground coordinates are east/north meters; Three uses x/-z.
|
|
39
|
+
export const geographicOrigin = [-87.62497155, 41.88482645];
|
|
40
|
+
const radians = Math.PI / 180;
|
|
41
|
+
const latitude = geographicOrigin[1] * radians;
|
|
42
|
+
const eccentricitySquared = 6.69437999014e-3;
|
|
43
|
+
const w = Math.sqrt(1 - eccentricitySquared * Math.sin(latitude) ** 2);
|
|
44
|
+
const eastPerDegree = 6378137 / w * Math.cos(latitude) * radians;
|
|
45
|
+
const northPerDegree = 6378137 * (1 - eccentricitySquared) / w ** 3 * radians;
|
|
46
|
+
export function projectGround([longitude, lat]) {
|
|
47
|
+
return [(longitude - geographicOrigin[0]) * eastPerDegree, (lat - geographicOrigin[1]) * northPerDegree];
|
|
48
|
+
}
|
|
49
|
+
export function footprintMetrics(coordinates) {
|
|
50
|
+
const points = coordinates.map(projectGround);
|
|
51
|
+
const min = [0, 1].map((axis) => Math.min(...points.map((p) => p[axis])));
|
|
52
|
+
const max = [0, 1].map((axis) => Math.max(...points.map((p) => p[axis])));
|
|
53
|
+
const area = Math.abs(points.reduce((sum, a, i) => {
|
|
54
|
+
const b = points[(i + 1) % points.length];
|
|
55
|
+
return sum + a[0] * b[1] - b[0] * a[1];
|
|
56
|
+
}, 0)) / 2;
|
|
57
|
+
return { min, max, center: min.map((n, i) => (n + max[i]) / 2), size: min.map((n, i) => max[i] - n), area };
|
|
58
|
+
}
|
|
59
|
+
function plan(coordinates) {
|
|
60
|
+
const points = coordinates.map((p) => { const [east, north] = projectGround(p); return [east, -north]; });
|
|
61
|
+
const area = points.reduce((sum, a, i) => {
|
|
62
|
+
const b = points[(i + 1) % points.length];
|
|
63
|
+
return sum + a[0] * b[1] - b[0] * a[1];
|
|
64
|
+
}, 0);
|
|
65
|
+
if (area > 0)
|
|
66
|
+
points.reverse();
|
|
67
|
+
return points.map((p, i) => line(p, points[(i + 1) % points.length]));
|
|
68
|
+
}
|
|
69
|
+
export function createGeographicBuilding(record, offset = [0, 0]) {
|
|
70
|
+
if (record.id === "layer3") {
|
|
71
|
+
return createAonGeographicBuilding(record, plan, offset);
|
|
72
|
+
}
|
|
73
|
+
if (record.id === "building-crain-communications") {
|
|
74
|
+
return createCrainGeographicBuilding(record, plan, offset);
|
|
75
|
+
}
|
|
76
|
+
if (record.id === "building-blue-cross-blue-shield") {
|
|
77
|
+
return createBlueCrossGeographicBuilding(record, plan, offset);
|
|
78
|
+
}
|
|
79
|
+
if (record.id === "building-340-on-the-park") {
|
|
80
|
+
return createOnTheParkGeographicBuilding(record, plan, offset);
|
|
81
|
+
}
|
|
82
|
+
if (record.id === "building-the-buckingham") {
|
|
83
|
+
return createBuckinghamGeographicBuilding(record, plan, offset);
|
|
84
|
+
}
|
|
85
|
+
if (record.id === "building-michigan-plaza-front-tall") {
|
|
86
|
+
return createMillenniumParkPlazaGeographicBuilding(record, plan, offset);
|
|
87
|
+
}
|
|
88
|
+
if (record.id === "building-willoughby-tower") {
|
|
89
|
+
return createWilloughbyTowerGeographicBuilding(record, plan, offset);
|
|
90
|
+
}
|
|
91
|
+
if (record.id === "building-six-north-michigan") {
|
|
92
|
+
return createSixNorthMichiganGeographicBuilding(record, plan, offset);
|
|
93
|
+
}
|
|
94
|
+
if (record.id === "building-six-north-far-east") {
|
|
95
|
+
return createMichiganBoulevardGeographicBuilding(record, plan, offset);
|
|
96
|
+
}
|
|
97
|
+
if (record.id === "building-michigan-plaza-front-middle") {
|
|
98
|
+
return createNorthMichigan180GeographicBuilding(record, plan, offset);
|
|
99
|
+
}
|
|
100
|
+
if (record.id === "building-university-club") {
|
|
101
|
+
return createUniversityClubGeographicBuilding(record, plan, offset);
|
|
102
|
+
}
|
|
103
|
+
if (record.id === "building-monroe") {
|
|
104
|
+
return createMonroeGeographicBuilding(record, plan, offset);
|
|
105
|
+
}
|
|
106
|
+
if (record.id === "building-maclean-center") {
|
|
107
|
+
return createMacleanCenterGeographicBuilding(record, plan, offset);
|
|
108
|
+
}
|
|
109
|
+
if (record.id === "building-lakeview") {
|
|
110
|
+
return createLakeViewGeographicBuilding(record, plan, offset);
|
|
111
|
+
}
|
|
112
|
+
if (record.id === "building-peoples-gas") {
|
|
113
|
+
return createPeoplesGasGeographicBuilding(record, plan, offset);
|
|
114
|
+
}
|
|
115
|
+
if (record.id === "building-200-south-michigan") {
|
|
116
|
+
return createBorgWarnerGeographicBuilding(record, plan, offset);
|
|
117
|
+
}
|
|
118
|
+
if (record.id === "building-railway-exchange") {
|
|
119
|
+
return createRailwayExchangeGeographicBuilding(record, plan, offset);
|
|
120
|
+
}
|
|
121
|
+
if (record.id === "building-michigan-west-right") {
|
|
122
|
+
return createGageGeographicBuilding(record, plan, offset);
|
|
123
|
+
}
|
|
124
|
+
if (record.id === "building-michigan-west-front") {
|
|
125
|
+
return createKeithGeographicBuilding(record, plan, offset);
|
|
126
|
+
}
|
|
127
|
+
if (record.id === "building-30-south-michigan") {
|
|
128
|
+
return createAscherGeographicBuilding(record, plan, offset);
|
|
129
|
+
}
|
|
130
|
+
if (record.id === "building-chicago-athletic-association") {
|
|
131
|
+
return createChicagoAthleticAssociationGeographicBuilding(record, plan, offset);
|
|
132
|
+
}
|
|
133
|
+
if (record.id === "building-office-west-of-aon") {
|
|
134
|
+
return createTwoIllinoisCenterGeographicBuilding(record, plan, offset);
|
|
135
|
+
}
|
|
136
|
+
if (record.id === "building-river-plaza") {
|
|
137
|
+
return createRiverPlazaGeographicBuilding(record, plan, offset);
|
|
138
|
+
}
|
|
139
|
+
if (record.id === "building-hyatt-regency-west-tower") {
|
|
140
|
+
return createHyattWestTowerGeographicBuilding(record, plan, offset);
|
|
141
|
+
}
|
|
142
|
+
if (record.id === "building-buckingham-east") {
|
|
143
|
+
return createSheratonGrandGeographicBuilding(record, plan, offset);
|
|
144
|
+
}
|
|
145
|
+
if (record.id === "building-three-illinois-center") {
|
|
146
|
+
return createThreeIllinoisCenterGeographicBuilding(record, plan, offset);
|
|
147
|
+
}
|
|
148
|
+
if (record.id === "building-swissotel") {
|
|
149
|
+
return createSwissotelGeographicBuilding(record, plan, offset);
|
|
150
|
+
}
|
|
151
|
+
if (record.id === "building-heritage-at-millennium-park") {
|
|
152
|
+
return createHeritageGeographicBuilding(record, plan, offset);
|
|
153
|
+
}
|
|
154
|
+
if (record.id === "building-kemper") {
|
|
155
|
+
return createKemperGeographicBuilding(record, plan, offset);
|
|
156
|
+
}
|
|
157
|
+
if (record.id === "building-michigan-plaza-south-tower") {
|
|
158
|
+
return createMichiganPlazaSouthGeographicBuilding(record, plan, offset);
|
|
159
|
+
}
|
|
160
|
+
if (record.id === "building-330-north-wabash") {
|
|
161
|
+
return createNorthWabashGeographicBuilding(record, plan, offset);
|
|
162
|
+
}
|
|
163
|
+
if (record.id === "building-one-prudential-plaza") {
|
|
164
|
+
return createOnePrudentialGeographicBuilding(record, plan, offset);
|
|
165
|
+
}
|
|
166
|
+
if (record.id === "building-trump-tower-only") {
|
|
167
|
+
return createTrumpGeographicBuilding(record, plan, offset);
|
|
168
|
+
}
|
|
169
|
+
if (record.id === "building-two-prudential-plaza") {
|
|
170
|
+
return createTwoPrudentialGeographicBuilding(record, plan, offset);
|
|
171
|
+
}
|
|
172
|
+
// Every geographic building has a dedicated factory; a record without one
|
|
173
|
+
// fails loudly here rather than silently rendering generic massing.
|
|
174
|
+
throw new Error(`No geographic factory for ${record.id}.`);
|
|
175
|
+
}
|
|
176
|
+
function lines(points, color) {
|
|
177
|
+
const geometry = new THREE.BufferGeometry();
|
|
178
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(points.flat(), 3));
|
|
179
|
+
return new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color }));
|
|
180
|
+
}
|
|
181
|
+
// A street's roadway, 3.3 m to a lane: its mapped lanes, or its class's usual count, which
|
|
182
|
+
// depends on whether it is one-way. A named service way is a single 5 m lane.
|
|
183
|
+
const usualLanes = {
|
|
184
|
+
trunk: [3, 6], primary: [3, 4], secondary: [3, 4], tertiary: [2, 2], residential: [1, 2], unclassified: [1, 2], living_street: [1, 2],
|
|
185
|
+
};
|
|
186
|
+
export function streetWidth(street) {
|
|
187
|
+
if (street.highway === "service")
|
|
188
|
+
return 5;
|
|
189
|
+
const lanes = street.lanes ?? (street.highway.endsWith("_link") ? 1 : usualLanes[street.highway]?.[street.oneway ? 0 : 1] ?? 2);
|
|
190
|
+
return lanes * 3.3;
|
|
191
|
+
}
|
|
192
|
+
// Clips a polygon in the ground's x/z plane to a rectangle (Sutherland–Hodgman).
|
|
193
|
+
function clipToRectangle(polygon, [xMin, xMax], [zMin, zMax]) {
|
|
194
|
+
const edges = [[0, xMin, false], [0, xMax, true], [1, zMin, false], [1, zMax, true]];
|
|
195
|
+
return edges.reduce((points, [axis, limit, keepBelow]) => {
|
|
196
|
+
const inside = (p) => keepBelow ? p[axis] <= limit : p[axis] >= limit;
|
|
197
|
+
const clipped = [];
|
|
198
|
+
points.forEach((current, i) => {
|
|
199
|
+
const previous = points[(i + points.length - 1) % points.length];
|
|
200
|
+
if (inside(current) !== inside(previous)) {
|
|
201
|
+
const t = (limit - previous[axis]) / (current[axis] - previous[axis]);
|
|
202
|
+
clipped.push([previous[0] + (current[0] - previous[0]) * t, previous[1] + (current[1] - previous[1]) * t]);
|
|
203
|
+
}
|
|
204
|
+
if (inside(current))
|
|
205
|
+
clipped.push(current);
|
|
206
|
+
});
|
|
207
|
+
return clipped;
|
|
208
|
+
}, polygon);
|
|
209
|
+
}
|
|
210
|
+
// The mapped streets as roadway surfaces, each its width about its centreline with round
|
|
211
|
+
// joins, cut to a rectangle of the ground (x east, z south, in metres from the ground's
|
|
212
|
+
// origin) so no road runs past the platform it lies on. They sit just above the platform,
|
|
213
|
+
// under the footprint outlines; overlapping roadways share one flat colour. Marked as
|
|
214
|
+
// ground, so they take building shadows wherever the platform does.
|
|
215
|
+
export function createGeographicRoads(bounds, color, offset = [0, 0]) {
|
|
216
|
+
const positions = [];
|
|
217
|
+
const addPolygon = (polygon) => {
|
|
218
|
+
const points = clipToRectangle(polygon, bounds.x, bounds.z);
|
|
219
|
+
for (let i = 1; i < points.length - 1; i += 1) {
|
|
220
|
+
const a = points[0], b = points[i], c = points[i + 1];
|
|
221
|
+
// Wind each triangle to face up, and drop the slivers a clip or a straight run leaves.
|
|
222
|
+
const area = (b[1] - a[1]) * (c[0] - a[0]) - (b[0] - a[0]) * (c[1] - a[1]);
|
|
223
|
+
if (Math.abs(area) < 1e-6)
|
|
224
|
+
continue;
|
|
225
|
+
for (const [x, z] of area > 0 ? [a, b, c] : [a, c, b])
|
|
226
|
+
positions.push(x, 0.03, z);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
for (const street of geographicStreets) {
|
|
230
|
+
const radius = streetWidth(street) / 2;
|
|
231
|
+
const points = street.coordinates.map((coordinate) => { const [east, north] = projectGround(coordinate); return [east, -north]; })
|
|
232
|
+
.filter((p, i, all) => i === 0 || Math.hypot(p[0] - all[i - 1][0], p[1] - all[i - 1][1]) > 1e-6);
|
|
233
|
+
// Each segment's unit normal, scaled to the roadway's half width.
|
|
234
|
+
const sides = points.slice(1).map((b, i) => {
|
|
235
|
+
const a = points[i], length = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
236
|
+
return [-(b[1] - a[1]) / length * radius, (b[0] - a[0]) / length * radius];
|
|
237
|
+
});
|
|
238
|
+
points.forEach((a, i) => {
|
|
239
|
+
// A round join where the street ends or turns. A shallow bend needs only the bevel
|
|
240
|
+
// between its segments' edge corners, on either side, to close the outer wedge.
|
|
241
|
+
const before = sides[i - 1], after = sides[i];
|
|
242
|
+
const turn = before && after ? Math.abs(Math.atan2(before[0] * after[1] - before[1] * after[0], before[0] * after[0] + before[1] * after[1])) : Math.PI;
|
|
243
|
+
if (turn > 0.05) {
|
|
244
|
+
// Twelve sides about the half width's circle, so every edge corner within it is covered.
|
|
245
|
+
const joint = [], reach = radius / Math.cos(Math.PI / 12);
|
|
246
|
+
for (let k = 0; k < 12; k += 1)
|
|
247
|
+
joint.push([a[0] + reach * Math.cos(k * Math.PI / 6), a[1] + reach * Math.sin(k * Math.PI / 6)]);
|
|
248
|
+
addPolygon(joint);
|
|
249
|
+
}
|
|
250
|
+
else if (before && after) {
|
|
251
|
+
for (const sign of [1, -1])
|
|
252
|
+
addPolygon([a, [a[0] + sign * before[0], a[1] + sign * before[1]], [a[0] + sign * after[0], a[1] + sign * after[1]]]);
|
|
253
|
+
}
|
|
254
|
+
const b = points[i + 1];
|
|
255
|
+
if (!b || !after)
|
|
256
|
+
return;
|
|
257
|
+
addPolygon([[a[0] + after[0], a[1] + after[1]], [b[0] + after[0], b[1] + after[1]], [b[0] - after[0], b[1] - after[1]], [a[0] - after[0], a[1] - after[1]]]);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const geometry = new THREE.BufferGeometry();
|
|
261
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
262
|
+
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(positions.map((_, i) => i % 3 === 1 ? 1 : 0), 3));
|
|
263
|
+
const roads = new THREE.Mesh(geometry, new THREE.MeshToonMaterial({ color }));
|
|
264
|
+
roads.name = "Mapped streets";
|
|
265
|
+
roads.position.set(offset[0], 0, offset[1]);
|
|
266
|
+
roads.receiveShadow = true;
|
|
267
|
+
roads.userData["ground"] = true;
|
|
268
|
+
return roads;
|
|
269
|
+
}
|
|
270
|
+
export function createGeographicGround(offset = [0, 0]) {
|
|
271
|
+
const group = new THREE.Group();
|
|
272
|
+
group.name = "Geographic reference";
|
|
273
|
+
group.position.set(offset[0], 0, offset[1]);
|
|
274
|
+
// The study rectangle, 100 m squares from 400 m west of Crain to 500 m east and from
|
|
275
|
+
// 850 m south, past the Railway Exchange at Jackson, to 650 m north.
|
|
276
|
+
const grid = [];
|
|
277
|
+
for (let x = -400; x <= 500; x += 100)
|
|
278
|
+
grid.push([x, 0.02, -650], [x, 0.02, 850]);
|
|
279
|
+
for (let north = -850; north <= 650; north += 100)
|
|
280
|
+
grid.push([-400, 0.02, -north], [500, 0.02, -north]);
|
|
281
|
+
group.add(lines(grid, 0x393939));
|
|
282
|
+
const streets = new THREE.Group();
|
|
283
|
+
streets.name = "Mapped street centerlines";
|
|
284
|
+
for (const street of geographicStreets) {
|
|
285
|
+
const points = street.coordinates.map((coordinate) => {
|
|
286
|
+
const [east, north] = projectGround(coordinate);
|
|
287
|
+
return [east, 0.06, -north];
|
|
288
|
+
});
|
|
289
|
+
const segments = [];
|
|
290
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
291
|
+
// Clip every segment to the study rectangle, retaining crossing segments.
|
|
292
|
+
const a = points[i - 1], b = points[i];
|
|
293
|
+
let lo = 0, hi = 1;
|
|
294
|
+
for (const [axis, min, max] of [[0, -400, 500], [2, -650, 850]]) {
|
|
295
|
+
const delta = b[axis] - a[axis];
|
|
296
|
+
if (Math.abs(delta) < 1e-10) {
|
|
297
|
+
if (a[axis] < min || a[axis] > max)
|
|
298
|
+
hi = -1;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const t1 = (min - a[axis]) / delta, t2 = (max - a[axis]) / delta;
|
|
302
|
+
lo = Math.max(lo, Math.min(t1, t2));
|
|
303
|
+
hi = Math.min(hi, Math.max(t1, t2));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (lo <= hi)
|
|
307
|
+
segments.push(...[lo, hi].map((t) => a.map((n, axis) => n + (b[axis] - n) * t)));
|
|
308
|
+
}
|
|
309
|
+
const object = lines(segments, 0xa7a7a7);
|
|
310
|
+
object.name = street.name;
|
|
311
|
+
streets.add(object);
|
|
312
|
+
}
|
|
313
|
+
group.add(streets);
|
|
314
|
+
const outlines = [];
|
|
315
|
+
for (const record of geographicBuildings) {
|
|
316
|
+
const points = record.footprint.coordinates.map((p) => { const [x, y] = projectGround(p); return [x, 0.1, -y]; });
|
|
317
|
+
points.forEach((p, i) => outlines.push(p, points[(i + 1) % points.length]));
|
|
318
|
+
}
|
|
319
|
+
const footprints = lines(outlines, 0xc9c9c9);
|
|
320
|
+
group.add(footprints);
|
|
321
|
+
return { group, streets, footprints };
|
|
322
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BuildingModel, Plan } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const swissotelLevels: Readonly<{
|
|
4
|
+
levels: 45;
|
|
5
|
+
roof: 139.3;
|
|
6
|
+
module: 1.5;
|
|
7
|
+
frame: 0.06;
|
|
8
|
+
coping: 0.1;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const swissotelPalette: Readonly<{
|
|
11
|
+
glass: number[];
|
|
12
|
+
lit: 8882055;
|
|
13
|
+
dim: 5329233;
|
|
14
|
+
frame: 4276545;
|
|
15
|
+
}>;
|
|
16
|
+
type Row = {
|
|
17
|
+
lo: number;
|
|
18
|
+
hi: number;
|
|
19
|
+
kind: "glass" | "frame";
|
|
20
|
+
floor: number;
|
|
21
|
+
};
|
|
22
|
+
export declare const swissotelRows: Row[];
|
|
23
|
+
export declare function createSwissotelGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
24
|
+
export {};
|
|
@@ -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,26 @@
|
|
|
1
|
+
import type { BuildingModel, Plan } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const threeIllinoisLevels: Readonly<{
|
|
4
|
+
pitch: 3.505;
|
|
5
|
+
lobby: number;
|
|
6
|
+
fascia: 1.26;
|
|
7
|
+
penthouse: 29;
|
|
8
|
+
roof: 106.7;
|
|
9
|
+
module: 1.524;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const threeIllinoisPalette: Readonly<{
|
|
12
|
+
glass: number[];
|
|
13
|
+
lit: 7368816;
|
|
14
|
+
dim: 4605510;
|
|
15
|
+
mullion: 3289650;
|
|
16
|
+
spandrel: 2631720;
|
|
17
|
+
}>;
|
|
18
|
+
type Row = {
|
|
19
|
+
lo: number;
|
|
20
|
+
hi: number;
|
|
21
|
+
kind: "spandrel" | "glass" | "penthouse";
|
|
22
|
+
floor: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const threeIllinoisRows: Row[];
|
|
25
|
+
export declare function createThreeIllinoisCenterGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
26
|
+
export {};
|
|
@@ -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,14 @@
|
|
|
1
|
+
import type { BuildingModel, Plan, Vec2 } from "./building-kit.js";
|
|
2
|
+
import type { GeoBuilding } from "./skyline-geography-data.js";
|
|
3
|
+
export declare const trumpGeographicLevels: Readonly<{
|
|
4
|
+
podium: 60;
|
|
5
|
+
setbacks: readonly [120, 200];
|
|
6
|
+
roof: 354.4;
|
|
7
|
+
shoulder: 347.3;
|
|
8
|
+
crownTop: 364.9;
|
|
9
|
+
joints: readonly [377.4, 402.2];
|
|
10
|
+
tip: 423.2;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const trumpNotch: Vec2;
|
|
13
|
+
export declare const trumpSpire: Vec2;
|
|
14
|
+
export declare function createTrumpGeographicBuilding(record: GeoBuilding, projectPlan: (coordinates: [number, number][]) => Plan, offset: [number, number]): BuildingModel;
|
|
@@ -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
|
+
}
|