@a2f0/skyline 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +20 -0
- package/README.md +52 -2
- package/docs/package.md +222 -0
- package/docs/skyline-geography.md +503 -0
- package/lib/models/aon-center.d.ts +4 -0
- package/lib/models/aon-center.js +60 -0
- package/lib/models/aon-geographic.d.ts +9 -0
- package/lib/models/aon-geographic.js +35 -0
- package/lib/models/aon-tower.d.ts +23 -0
- package/lib/models/aon-tower.js +130 -0
- package/lib/models/blue-cross-geographic.d.ts +14 -0
- package/lib/models/blue-cross-geographic.js +248 -0
- package/lib/models/borg-warner-geographic.d.ts +20 -0
- package/lib/models/borg-warner-geographic.js +143 -0
- package/lib/models/buckingham-geographic.d.ts +10 -0
- package/lib/models/buckingham-geographic.js +121 -0
- package/lib/models/building-kit.d.ts +84 -0
- package/lib/models/building-kit.js +358 -0
- package/lib/models/celebrations.d.ts +39 -0
- package/lib/models/celebrations.js +44 -0
- package/lib/models/chicago-athletic-association-geographic.d.ts +32 -0
- package/lib/models/chicago-athletic-association-geographic.js +173 -0
- package/lib/models/crain-communications.d.ts +5 -0
- package/lib/models/crain-communications.js +75 -0
- package/lib/models/crain-geographic.d.ts +10 -0
- package/lib/models/crain-geographic.js +69 -0
- package/lib/models/crain-tower.d.ts +32 -0
- package/lib/models/crain-tower.js +378 -0
- package/lib/models/facade-grid.d.ts +18 -0
- package/lib/models/facade-grid.js +176 -0
- package/lib/models/gage-geographic.d.ts +19 -0
- package/lib/models/gage-geographic.js +154 -0
- package/lib/models/heritage-at-millennium-park.d.ts +3 -0
- package/lib/models/heritage-at-millennium-park.js +284 -0
- package/lib/models/heritage-geographic.d.ts +11 -0
- package/lib/models/heritage-geographic.js +208 -0
- package/lib/models/hyatt-west-tower-geographic.d.ts +26 -0
- package/lib/models/hyatt-west-tower-geographic.js +86 -0
- package/lib/models/keith-ascher-geographic.d.ts +29 -0
- package/lib/models/keith-ascher-geographic.js +110 -0
- package/lib/models/kemper-geographic.d.ts +8 -0
- package/lib/models/kemper-geographic.js +153 -0
- package/lib/models/kemper.d.ts +2 -0
- package/lib/models/kemper.js +78 -0
- package/lib/models/lake-view-geographic.d.ts +15 -0
- package/lib/models/lake-view-geographic.js +134 -0
- package/lib/models/maclean-center-geographic.d.ts +20 -0
- package/lib/models/maclean-center-geographic.js +162 -0
- package/lib/models/michigan-boulevard-geographic.d.ts +12 -0
- package/lib/models/michigan-boulevard-geographic.js +157 -0
- package/lib/models/michigan-plaza-south-geographic.d.ts +7 -0
- package/lib/models/michigan-plaza-south-geographic.js +84 -0
- package/lib/models/michigan-plaza-south.d.ts +2 -0
- package/lib/models/michigan-plaza-south.js +68 -0
- package/lib/models/millennium-park-plaza-geographic.d.ts +9 -0
- package/lib/models/millennium-park-plaza-geographic.js +103 -0
- package/lib/models/monroe-geographic.d.ts +19 -0
- package/lib/models/monroe-geographic.js +177 -0
- package/lib/models/north-michigan-180-geographic.d.ts +14 -0
- package/lib/models/north-michigan-180-geographic.js +117 -0
- package/lib/models/north-wabash-geographic.d.ts +14 -0
- package/lib/models/north-wabash-geographic.js +122 -0
- package/lib/models/on-the-park-geographic.d.ts +11 -0
- package/lib/models/on-the-park-geographic.js +245 -0
- package/lib/models/one-prudential-geographic.d.ts +12 -0
- package/lib/models/one-prudential-geographic.js +55 -0
- package/lib/models/one-prudential-plaza.d.ts +5 -0
- package/lib/models/one-prudential-plaza.js +72 -0
- package/lib/models/one-prudential-tower.d.ts +45 -0
- package/lib/models/one-prudential-tower.js +246 -0
- package/lib/models/peoples-gas-geographic.d.ts +23 -0
- package/lib/models/peoples-gas-geographic.js +250 -0
- package/lib/models/railway-exchange-geographic.d.ts +18 -0
- package/lib/models/railway-exchange-geographic.js +176 -0
- package/lib/models/river-plaza-geographic.d.ts +16 -0
- package/lib/models/river-plaza-geographic.js +104 -0
- package/lib/models/sheraton-grand-geographic.d.ts +24 -0
- package/lib/models/sheraton-grand-geographic.js +112 -0
- package/lib/models/six-north-michigan-geographic.d.ts +16 -0
- package/lib/models/six-north-michigan-geographic.js +180 -0
- package/lib/models/skyline-geography-data.d.ts +44 -0
- package/lib/models/skyline-geography-data.js +607 -0
- package/lib/models/skyline-geography.d.ts +23 -0
- package/lib/models/skyline-geography.js +322 -0
- package/lib/models/swissotel-geographic.d.ts +24 -0
- package/lib/models/swissotel-geographic.js +79 -0
- package/lib/models/three-illinois-center-geographic.d.ts +26 -0
- package/lib/models/three-illinois-center-geographic.js +86 -0
- package/lib/models/trump-geographic.d.ts +14 -0
- package/lib/models/trump-geographic.js +51 -0
- package/lib/models/trump-international-tower.d.ts +4 -0
- package/lib/models/trump-international-tower.js +84 -0
- package/lib/models/trump-tower.d.ts +43 -0
- package/lib/models/trump-tower.js +229 -0
- package/lib/models/two-illinois-center-geographic.d.ts +26 -0
- package/lib/models/two-illinois-center-geographic.js +85 -0
- package/lib/models/two-prudential-geographic.d.ts +25 -0
- package/lib/models/two-prudential-geographic.js +47 -0
- package/lib/models/two-prudential-plaza.d.ts +8 -0
- package/lib/models/two-prudential-plaza.js +55 -0
- package/lib/models/two-prudential-tower.d.ts +53 -0
- package/lib/models/two-prudential-tower.js +441 -0
- package/lib/models/university-club-geographic.d.ts +24 -0
- package/lib/models/university-club-geographic.js +217 -0
- package/lib/models/willoughby-tower-geographic.d.ts +17 -0
- package/lib/models/willoughby-tower-geographic.js +195 -0
- package/lib/models/window-illumination.d.ts +14 -0
- package/lib/models/window-illumination.js +64 -0
- package/lib/package-assets.d.ts +5 -0
- package/lib/package-assets.js +9 -0
- package/lib/skyline-package.d.ts +23 -0
- package/lib/skyline-package.js +25 -0
- package/lib/skyline-scene.d.ts +25 -0
- package/lib/skyline-scene.js +86 -0
- package/lib/study-types.d.ts +49 -0
- package/lib/study-types.js +1 -0
- package/lib/vendor/three-r186.d.ts +1 -0
- package/lib/vendor/three-r186.js +1 -0
- package/package.json +64 -4
- package/site/building-detail.html +54 -0
- package/site/building-detail.js +84 -0
- package/site/building-study.html +58 -0
- package/site/building-study.js +3 -0
- package/site/index.html +407 -0
- package/site/models/aon-center.js +60 -0
- package/site/models/aon-geographic.js +35 -0
- package/site/models/aon-tower.js +130 -0
- package/site/models/blue-cross-geographic.js +248 -0
- package/site/models/borg-warner-geographic.js +143 -0
- package/site/models/buckingham-geographic.js +121 -0
- package/site/models/building-kit.js +358 -0
- package/site/models/celebrations.js +44 -0
- package/site/models/chicago-athletic-association-geographic.js +173 -0
- package/site/models/crain-communications.js +75 -0
- package/site/models/crain-geographic.js +69 -0
- package/site/models/crain-reference.svg +10 -0
- package/site/models/crain-tower.js +378 -0
- package/site/models/facade-grid.js +176 -0
- package/site/models/gage-geographic.js +154 -0
- package/site/models/heritage-at-millennium-park.js +284 -0
- package/site/models/heritage-geographic.js +208 -0
- package/site/models/hyatt-west-tower-geographic.js +86 -0
- package/site/models/keith-ascher-geographic.js +110 -0
- package/site/models/kemper-geographic.js +153 -0
- package/site/models/kemper.js +78 -0
- package/site/models/lake-view-geographic.js +134 -0
- package/site/models/maclean-center-geographic.js +162 -0
- package/site/models/michigan-boulevard-geographic.js +157 -0
- package/site/models/michigan-plaza-south-geographic.js +84 -0
- package/site/models/michigan-plaza-south.js +68 -0
- package/site/models/millennium-park-plaza-geographic.js +103 -0
- package/site/models/monroe-geographic.js +177 -0
- package/site/models/north-michigan-180-geographic.js +117 -0
- package/site/models/north-wabash-geographic.js +122 -0
- package/site/models/on-the-park-geographic.js +245 -0
- package/site/models/one-prudential-geographic.js +55 -0
- package/site/models/one-prudential-plaza.js +72 -0
- package/site/models/one-prudential-tower.js +246 -0
- package/site/models/peoples-gas-geographic.js +250 -0
- package/site/models/railway-exchange-geographic.js +176 -0
- package/site/models/river-plaza-geographic.js +104 -0
- package/site/models/sheraton-grand-geographic.js +112 -0
- package/site/models/six-north-michigan-geographic.js +180 -0
- package/site/models/skyline-geography-data.js +607 -0
- package/site/models/skyline-geography.js +322 -0
- package/site/models/skyline-panorama.svg +3491 -0
- package/site/models/skyline-reference.svg +3090 -0
- package/site/models/swissotel-geographic.js +79 -0
- package/site/models/three-illinois-center-geographic.js +86 -0
- package/site/models/trump-geographic.js +51 -0
- package/site/models/trump-international-tower.js +84 -0
- package/site/models/trump-tower.js +229 -0
- package/site/models/two-illinois-center-geographic.js +85 -0
- package/site/models/two-prudential-geographic.js +47 -0
- package/site/models/two-prudential-plaza.js +55 -0
- package/site/models/two-prudential-tower.js +441 -0
- package/site/models/university-club-geographic.js +217 -0
- package/site/models/willoughby-tower-geographic.js +195 -0
- package/site/models/window-illumination.js +64 -0
- package/site/skyline-3d.html +92 -0
- package/site/skyline-3d.js +333 -0
- package/site/skyline-animated.svg +18795 -0
- package/site/skyline-comparison.js +97 -0
- package/site/skyline-original-fit.svg +18312 -0
- package/site/skyline-scene.js +86 -0
- package/site/skyline-study.html +83 -0
- package/site/skyline-study.js +109 -0
- package/site/skyline-webgl.html +667 -0
- package/site/stars.svg +176 -0
- package/site/study-loader.js +20 -0
- package/site/study-types.js +1 -0
- package/site/study-viewer.js +605 -0
- package/site/study.css +204 -0
- package/site/vendor/THREE-LICENSE.txt +21 -0
- package/site/vendor/three-r186.js +4228 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { geographicBuildings } from "./models/skyline-geography-data.js";
|
|
2
|
+
import { createGeographicBuilding, createGeographicGround } from "./models/skyline-geography.js";
|
|
3
|
+
// The panorama's viewBox, models/skyline-panorama.svg, in the drawing's layer units. Its
|
|
4
|
+
// centre is the skyline camera's sightline and its height the camera's vertical field of view.
|
|
5
|
+
const panorama = { x: -1400, y: -154.834, width: 9378.476, height: 3535.05 };
|
|
6
|
+
// The frame index.html shows the drawing in: skyline-animated.svg's viewBox, bottom-aligned
|
|
7
|
+
// and centred, in the same layer units. The file's `skyline-position` group translates the
|
|
8
|
+
// layer by (1105.5923, -87.26366) inside its viewBox.
|
|
9
|
+
const drawingFrame = { x: -1105.5923, y: 87.26366, width: 8501.0986, height: 2782.0373 };
|
|
10
|
+
// The direction the drawing's camera looks from, south-east of the skyline: its azimuth from
|
|
11
|
+
// south toward east. A building's detail faces it the same way.
|
|
12
|
+
export const skylineAzimuth = 36.1247 * Math.PI / 180;
|
|
13
|
+
// The geographic skyline, registered with Crain's mapped centre at `anchor`: every mapped
|
|
14
|
+
// building, their ground, the settings a study shows them with, and the drawing's camera.
|
|
15
|
+
export function createGeographicSkyline(anchor = [0, 0, 0]) {
|
|
16
|
+
const offset = [anchor[0], anchor[2]];
|
|
17
|
+
const models = geographicBuildings.map((record) => createGeographicBuilding(record, offset));
|
|
18
|
+
const ground = createGeographicGround(offset);
|
|
19
|
+
// The comparison views share a frame set in ground plan, which holds both layouts: the
|
|
20
|
+
// original's platform, and every mapped footprint, from 330 North Wabash's west side to
|
|
21
|
+
// The Buckingham's east, with about 50 m east and west even where a portrait phone's
|
|
22
|
+
// width binds the frame. North and south it runs from about 100 m past River Plaza's north
|
|
23
|
+
// side, across the river, to 127 m past the Railway Exchange's mapped south side at
|
|
24
|
+
// Jackson. Height comparison sees it at an angle, and on a portrait phone can clip the
|
|
25
|
+
// platform's south-west corner.
|
|
26
|
+
const center = [anchor[0] + 128, 0, anchor[2] + 85];
|
|
27
|
+
const planFit = { width: 1050, height: 1520 };
|
|
28
|
+
const settings = {
|
|
29
|
+
target: [center[0], 155, center[2]],
|
|
30
|
+
fit: { width: 1100, height: 950 },
|
|
31
|
+
platform: { width: 1200, depth: 1600, x: center[0], z: center[2] },
|
|
32
|
+
clippingMargin: 1800,
|
|
33
|
+
lightPosition: [-700, 1100, 500],
|
|
34
|
+
// Wide enough for the platform's far corners, which the geography suite checks.
|
|
35
|
+
shadowCamera: { left: -1300, right: 1300, top: 1300, bottom: -1300, near: 1, far: 2600 },
|
|
36
|
+
};
|
|
37
|
+
// The drawing traces a lakefront photograph. Fitting the mapped buildings'
|
|
38
|
+
// roofs and tips to their drawn positions (scripts/fit-geographic-camera.ts)
|
|
39
|
+
// puts its eye on the shore by the Adler Planetarium, 2 m above the street
|
|
40
|
+
// datum, looking up at the skyline; it is held there while the angles and lens
|
|
41
|
+
// are refitted, since the drawing barely tells its distance from its lens. The
|
|
42
|
+
// buildings stay where they are mapped; only the camera is fitted. Eye positions
|
|
43
|
+
// are meters east, up, and south of Crain's mapped centre.
|
|
44
|
+
const photoEye = [1471.76, 2, 1948.8];
|
|
45
|
+
const photoAzimuth = skylineAzimuth, photoPolar = 94.0182 * Math.PI / 180;
|
|
46
|
+
// The panorama's vertical field of view. The panorama is the reference excerpt's frame
|
|
47
|
+
// widened about the same centre to every building the drawing shows, so the camera aims
|
|
48
|
+
// where it always has.
|
|
49
|
+
const frameFov = 10.5279 * Math.PI / 180;
|
|
50
|
+
const back = [Math.sin(photoPolar) * Math.sin(photoAzimuth), Math.cos(photoPolar), Math.sin(photoPolar) * Math.cos(photoAzimuth)];
|
|
51
|
+
// Orbit and zoom pivot on the sightline at Crain's depth.
|
|
52
|
+
const photoDistance = photoEye[0] * back[0] + photoEye[1] * back[1] + photoEye[2] * back[2];
|
|
53
|
+
const frameHeight = 2 * photoDistance * Math.tan(frameFov / 2);
|
|
54
|
+
const photoView = {
|
|
55
|
+
azimuth: photoAzimuth,
|
|
56
|
+
polar: photoPolar,
|
|
57
|
+
distance: photoDistance,
|
|
58
|
+
target: [anchor[0] + photoEye[0] - photoDistance * back[0], photoEye[1] - photoDistance * back[1], anchor[2] + photoEye[2] - photoDistance * back[2]],
|
|
59
|
+
fit: { width: frameHeight * panorama.width / panorama.height, height: frameHeight },
|
|
60
|
+
};
|
|
61
|
+
// The same camera framing what index.html frames: the drawing's viewBox, a lens shift off
|
|
62
|
+
// the panorama's centre, held to the viewport's bottom edge. The mapped buildings then
|
|
63
|
+
// stand where the drawing shows them in the skyline viewer.
|
|
64
|
+
const unit = frameHeight / panorama.height;
|
|
65
|
+
const drawingView = {
|
|
66
|
+
...photoView,
|
|
67
|
+
label: "skyline view",
|
|
68
|
+
fit: { width: drawingFrame.width * unit, height: drawingFrame.height * unit },
|
|
69
|
+
offset: [
|
|
70
|
+
(drawingFrame.x + drawingFrame.width / 2 - panorama.x - panorama.width / 2) * unit,
|
|
71
|
+
(panorama.y + panorama.height / 2 - drawingFrame.y - drawingFrame.height / 2) * unit,
|
|
72
|
+
],
|
|
73
|
+
align: "bottom",
|
|
74
|
+
};
|
|
75
|
+
const layout = { models, extras: [ground.group], defaultView: "skyline", ...settings, views: { skyline: photoView } };
|
|
76
|
+
const comparisonViews = {
|
|
77
|
+
top: { azimuth: 0, polar: 0, projection: "orthographic", label: "ground plan · north up", fit: planFit, target: center },
|
|
78
|
+
heights: { azimuth: 0.65, polar: 1.18, projection: "orthographic", label: "height comparison", fit: planFit, target: [center[0], 155, center[2]] },
|
|
79
|
+
};
|
|
80
|
+
// The names the comparison views show over each building.
|
|
81
|
+
const labels = geographicBuildings.map(({ id, shortName }) => ({
|
|
82
|
+
id, text: shortName,
|
|
83
|
+
placement: id === "layer3" ? "right" : id === "building-one-prudential-plaza" ? "below" : "above",
|
|
84
|
+
}));
|
|
85
|
+
return { models, ground, center, settings, layout, drawingView, comparisonViews, labels };
|
|
86
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type * as THREE from "./vendor/three-r186.js";
|
|
2
|
+
import type { BuildingModel, Vec3 } from "./models/building-kit.js";
|
|
3
|
+
export interface FitBox {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PlatformOptions {
|
|
8
|
+
width: number;
|
|
9
|
+
depth: number;
|
|
10
|
+
x?: number;
|
|
11
|
+
z?: number;
|
|
12
|
+
color?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ShadowCameraOptions {
|
|
15
|
+
left: number;
|
|
16
|
+
right: number;
|
|
17
|
+
top: number;
|
|
18
|
+
bottom: number;
|
|
19
|
+
near: number;
|
|
20
|
+
far: number;
|
|
21
|
+
}
|
|
22
|
+
export interface StudyView {
|
|
23
|
+
azimuth: number;
|
|
24
|
+
polar: number;
|
|
25
|
+
label?: string;
|
|
26
|
+
projection?: "orthographic" | "perspective";
|
|
27
|
+
fit?: FitBox;
|
|
28
|
+
target?: Vec3;
|
|
29
|
+
distance?: number;
|
|
30
|
+
offset?: [number, number];
|
|
31
|
+
align?: "center" | "bottom";
|
|
32
|
+
}
|
|
33
|
+
export interface StudyLayout {
|
|
34
|
+
models?: BuildingModel[];
|
|
35
|
+
extras?: THREE.Object3D[];
|
|
36
|
+
defaultView?: string;
|
|
37
|
+
target?: Vec3;
|
|
38
|
+
fit?: FitBox;
|
|
39
|
+
platform?: PlatformOptions;
|
|
40
|
+
lightPosition?: Vec3;
|
|
41
|
+
shadowCamera?: ShadowCameraOptions;
|
|
42
|
+
clippingMargin?: number | null;
|
|
43
|
+
views?: Record<string, Partial<StudyView>>;
|
|
44
|
+
}
|
|
45
|
+
export interface StudyLabel {
|
|
46
|
+
id: string;
|
|
47
|
+
text: string;
|
|
48
|
+
placement?: string;
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "three";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "three";
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a2f0/skyline",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Interactive Chicago skyline viewer and reusable Three.js building models",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/a2f0/skyline.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": { "access": "public" },
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": ["./site/**/*.js"],
|
|
13
|
+
"files": ["lib/", "site/", "NOTICE.md", "docs/package.md", "docs/skyline-geography.md"],
|
|
14
|
+
"main": "./lib/skyline-package.js",
|
|
15
|
+
"types": "./lib/skyline-package.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/skyline-package.d.ts",
|
|
19
|
+
"import": "./lib/skyline-package.js"
|
|
20
|
+
},
|
|
21
|
+
"./scene": {
|
|
22
|
+
"types": "./lib/skyline-scene.d.ts",
|
|
23
|
+
"import": "./lib/skyline-scene.js"
|
|
24
|
+
},
|
|
25
|
+
"./models/*": {
|
|
26
|
+
"types": "./lib/models/*.d.ts",
|
|
27
|
+
"import": "./lib/models/*.js"
|
|
28
|
+
},
|
|
29
|
+
"./build": {
|
|
30
|
+
"types": "./lib/package-assets.d.ts",
|
|
31
|
+
"import": "./lib/package-assets.js"
|
|
32
|
+
},
|
|
33
|
+
"./assets/*": "./site/*",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"engines": { "node": ">=22" },
|
|
37
|
+
"peerDependencies": { "three": "~0.186.0" },
|
|
38
|
+
"peerDependenciesMeta": { "three": { "optional": true } },
|
|
39
|
+
"dependencies": { "@types/three": "0.186.0" },
|
|
40
|
+
"scripts": {
|
|
41
|
+
"agent-tool": "agent-tool",
|
|
42
|
+
"agents:sync": "bun run agent-tool skills install --apply",
|
|
43
|
+
"agents:check": "bun run agent-tool skills check",
|
|
44
|
+
"check": "bun scripts/check.ts",
|
|
45
|
+
"test": "bun scripts/check.ts",
|
|
46
|
+
"typecheck": "bunx --bun tsc -p tsconfig.json",
|
|
47
|
+
"build:site": "bun scripts/build-site.ts",
|
|
48
|
+
"build:package": "bun scripts/build-package.ts",
|
|
49
|
+
"check:package": "bun test tests/package.test.ts",
|
|
50
|
+
"prepack": "bun run build:package",
|
|
51
|
+
"prepublishOnly": "bun run check",
|
|
52
|
+
"fidelity": "bun scripts/fidelity-report.ts",
|
|
53
|
+
"renders": "bun scripts/render-study.ts",
|
|
54
|
+
"deploy": "scripts/deploy.sh",
|
|
55
|
+
"verify:deploy": "bun scripts/verify-deploy.ts"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@a2f0/agent-tool": "0.1.4",
|
|
59
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
60
|
+
"@types/bun": "^1.4.2",
|
|
61
|
+
"three": "0.186.0",
|
|
62
|
+
"playwright": "1.63.0",
|
|
63
|
+
"typescript": "^7.0.2",
|
|
64
|
+
"wrangler": "4.131.2"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="robots" content="noindex" />
|
|
7
|
+
<meta name="theme-color" content="#080808" />
|
|
8
|
+
<link rel="icon" href="data:," />
|
|
9
|
+
<title>Building Detail — Chicago Skyline</title>
|
|
10
|
+
<link rel="stylesheet" href="study.css" />
|
|
11
|
+
<!-- Floated over the skyline viewer, the page leaves its links to the viewer's panel. -->
|
|
12
|
+
<script>if (window.frameElement) document.documentElement.classList.add("framed");</script>
|
|
13
|
+
</head>
|
|
14
|
+
<body class="building-detail">
|
|
15
|
+
<header>
|
|
16
|
+
<div>
|
|
17
|
+
<p class="eyebrow">Chicago skyline / building detail</p>
|
|
18
|
+
<h1 id="building-name">Building detail</h1>
|
|
19
|
+
<p class="address" id="building-heights"></p>
|
|
20
|
+
</div>
|
|
21
|
+
<nav class="study-links" aria-label="Studies"><a href="skyline-study.html">Skyline study →</a><a class="back" href="index.html">← back to skyline</a></nav>
|
|
22
|
+
</header>
|
|
23
|
+
<main>
|
|
24
|
+
<div class="detail-model">
|
|
25
|
+
<div class="viewport" id="viewport">
|
|
26
|
+
<canvas id="building" tabindex="0" role="img" aria-label="Interactive 3D model of the building" aria-describedby="camera-hint"></canvas>
|
|
27
|
+
<span class="view-label" id="view-label">three-quarter view</span>
|
|
28
|
+
<p class="loading" id="loading" role="status">Preparing the building…</p>
|
|
29
|
+
<p class="hint" id="camera-hint">Drag to orbit · scroll or pinch to zoom<br />Arrow keys rotate · + / − zoom · Home resets</p>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="toolbar" aria-label="Building detail controls">
|
|
32
|
+
<div class="button-group" role="group" aria-label="Camera views">
|
|
33
|
+
<button type="button" data-view="front">front</button>
|
|
34
|
+
<button type="button" data-view="quarter" aria-pressed="true">three-quarter</button>
|
|
35
|
+
<button type="button" data-view="side">side</button>
|
|
36
|
+
<button type="button" id="reset">reset view</button>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="button-group" role="group" aria-label="Model display">
|
|
39
|
+
<button type="button" id="wireframe" aria-pressed="false">wireframe</button>
|
|
40
|
+
<button type="button" id="turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="notes">
|
|
45
|
+
<p id="building-note"></p>
|
|
46
|
+
<p id="building-facts"></p>
|
|
47
|
+
<p id="motion-status" role="status">Camera moves only when you interact or start the turntable.</p>
|
|
48
|
+
</div>
|
|
49
|
+
<noscript><p>Enable JavaScript to explore the 3D model.</p></noscript>
|
|
50
|
+
</main>
|
|
51
|
+
<div class="tooltip" id="tooltip" aria-hidden="true" hidden></div>
|
|
52
|
+
<script src="study-loader.js" data-study="./building-detail.js"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as THREE from "./vendor/three-r186.js";
|
|
2
|
+
import { createBuildingStudy } from "./study-viewer.js";
|
|
3
|
+
import { skylineAzimuth } from "./skyline-comparison.js";
|
|
4
|
+
import { geographicBuildings } from "./models/skyline-geography-data.js";
|
|
5
|
+
import { createGeographicBuilding, footprintMetrics } from "./models/skyline-geography.js";
|
|
6
|
+
// One mapped building of the 3D skyline, alone on its own platform, as the building study
|
|
7
|
+
// shows Crain: building-detail.html?building=<id>. The skyline viewer floats it over the
|
|
8
|
+
// skyline from a building's context menu.
|
|
9
|
+
const id = new URLSearchParams(location.search).get("building");
|
|
10
|
+
const record = geographicBuildings.find((entry) => entry.id === id);
|
|
11
|
+
if (record)
|
|
12
|
+
showBuilding(record);
|
|
13
|
+
else {
|
|
14
|
+
document.querySelector("#loading").textContent = "No mapped building by that name. Right-click a building on the 3D skyline to see its detail.";
|
|
15
|
+
document.querySelectorAll("button").forEach((button) => { button.disabled = true; });
|
|
16
|
+
}
|
|
17
|
+
function showBuilding(record) {
|
|
18
|
+
const footprint = footprintMetrics(record.footprint.coordinates);
|
|
19
|
+
// Centred on its mapped outline: ground coordinates are east and north, the scene's x and -z.
|
|
20
|
+
const model = createGeographicBuilding(record, [-footprint.center[0], footprint.center[1]]);
|
|
21
|
+
// The page names the building before the 3D view starts, so a failed start leaves it named.
|
|
22
|
+
document.title = `${record.name} — Building Detail`;
|
|
23
|
+
document.querySelector("#building-name").textContent = record.name;
|
|
24
|
+
document.querySelector("#building").setAttribute("aria-label", `Interactive 3D model of ${record.name}`);
|
|
25
|
+
const number = (n) => `${Number(n.toFixed(1))} m`;
|
|
26
|
+
const heights = document.querySelector("#building-heights");
|
|
27
|
+
// As the skyline study's table gives them.
|
|
28
|
+
const osm = record.heightSource.startsWith("https://www.openstreetmap.org/way/") ? " (OSM)" : "";
|
|
29
|
+
const source = link(record.heightSource, record.heightFromDrawing ? `${number(record.height)}, measured on the drawing` : `${number(record.height)} architectural${osm}`);
|
|
30
|
+
heights.replaceChildren(source, ...record.tipHeight !== record.height ? [` · tip ${number(record.tipHeight)}`] : []);
|
|
31
|
+
document.querySelector("#building-note").textContent = record.note;
|
|
32
|
+
const way = link(`https://www.openstreetmap.org/way/${record.footprint.way}`, `OpenStreetMap way ${record.footprint.way}`);
|
|
33
|
+
document.querySelector("#building-facts").replaceChildren(`Mapped outline ${footprint.size[0].toFixed(1)} × ${footprint.size[1].toFixed(1)} m, from `, way, `. Model: ${model.triangleCount.toLocaleString("en-US")} triangles.`);
|
|
34
|
+
const min = new THREE.Vector3(Infinity, Infinity, Infinity), max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);
|
|
35
|
+
model.building.updateMatrixWorld(true);
|
|
36
|
+
model.building.traverse((child) => {
|
|
37
|
+
const mesh = child;
|
|
38
|
+
if (!mesh.isMesh)
|
|
39
|
+
return;
|
|
40
|
+
const positions = mesh.geometry.getAttribute("position"), point = new THREE.Vector3();
|
|
41
|
+
for (let i = 0; i < positions.count; i += 1) {
|
|
42
|
+
point.fromBufferAttribute(positions, i).applyMatrix4(mesh.matrixWorld);
|
|
43
|
+
min.min(point);
|
|
44
|
+
max.max(point);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// The platform reaches past the building on every side, and the frame holds the building
|
|
48
|
+
// and its platform from any side: its height, and its plan's diagonal.
|
|
49
|
+
const height = max.y;
|
|
50
|
+
const reach = Math.max(-min.x, max.x, -min.z, max.z);
|
|
51
|
+
const margin = Math.max(10, reach * 0.35);
|
|
52
|
+
const platform = { width: 2 * (Math.max(-min.x, max.x) + margin), depth: 2 * (Math.max(-min.z, max.z) + margin) };
|
|
53
|
+
const across = Math.hypot(platform.width, platform.depth);
|
|
54
|
+
const size = Math.max(height, across);
|
|
55
|
+
// The building study's views, turned so the front view faces the building as the skyline
|
|
56
|
+
// camera does.
|
|
57
|
+
const views = {
|
|
58
|
+
front: { azimuth: skylineAzimuth, polar: Math.PI / 2 - 0.035, label: "front view · as the skyline shows it" },
|
|
59
|
+
quarter: { azimuth: skylineAzimuth + 0.22, polar: Math.PI / 2 - 0.15, label: "three-quarter view" },
|
|
60
|
+
side: { azimuth: skylineAzimuth + Math.PI / 2, polar: Math.PI / 2 - 0.12, label: "side view" },
|
|
61
|
+
};
|
|
62
|
+
createBuildingStudy({
|
|
63
|
+
models: [model],
|
|
64
|
+
views,
|
|
65
|
+
target: [0, height * 0.48, 0],
|
|
66
|
+
fit: { height: height * 1.3 + across * 0.4, width: across * 1.2 },
|
|
67
|
+
platform,
|
|
68
|
+
lightPosition: [-0.62 * size, 1.36 * size, 0.96 * size],
|
|
69
|
+
shadowCamera: { left: -size, right: size, top: size, bottom: -size, near: 1, far: 4 * size },
|
|
70
|
+
far: 12 * size,
|
|
71
|
+
// The detail opens circling the building, which any view, drag, or the turntable
|
|
72
|
+
// button stops.
|
|
73
|
+
turntable: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Sources open beside the page, so a detail floated over the skyline stays where it is.
|
|
77
|
+
function link(href, text) {
|
|
78
|
+
const anchor = document.createElement("a");
|
|
79
|
+
anchor.href = href;
|
|
80
|
+
anchor.textContent = text;
|
|
81
|
+
anchor.target = "_blank";
|
|
82
|
+
anchor.rel = "noopener";
|
|
83
|
+
return anchor;
|
|
84
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="robots" content="noindex" />
|
|
7
|
+
<meta name="theme-color" content="#080808" />
|
|
8
|
+
<link rel="icon" href="data:," />
|
|
9
|
+
<title>Crain Communications Building — 3D Study</title>
|
|
10
|
+
<link rel="stylesheet" href="study.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header>
|
|
14
|
+
<div>
|
|
15
|
+
<p class="eyebrow">Chicago skyline / building study 01</p>
|
|
16
|
+
<h1>Crain Communications Building</h1>
|
|
17
|
+
<p class="address">150 North Michigan Avenue · the diamond roof</p>
|
|
18
|
+
</div>
|
|
19
|
+
<nav class="study-links" aria-label="Studies"><a href="skyline-study.html">Skyline study →</a><a class="back" href="index.html">← back to skyline</a></nav>
|
|
20
|
+
</header>
|
|
21
|
+
<main>
|
|
22
|
+
<div class="comparison">
|
|
23
|
+
<figure class="reference">
|
|
24
|
+
<figcaption class="caption">01 / existing SVG</figcaption>
|
|
25
|
+
<img src="models/crain-reference.svg" alt="Existing grayscale illustration of the Crain Communications Building, with its diamond roof and horizontal window bands" />
|
|
26
|
+
</figure>
|
|
27
|
+
<figure>
|
|
28
|
+
<figcaption class="caption">02 / 3D study</figcaption>
|
|
29
|
+
<div class="viewport" id="viewport">
|
|
30
|
+
<canvas id="building" tabindex="0" role="img" aria-label="Interactive 3D model of the Crain Communications Building" aria-describedby="camera-hint"></canvas>
|
|
31
|
+
<span class="view-label" id="view-label">three-quarter view</span>
|
|
32
|
+
<p class="loading" id="loading" role="status">Preparing the building…</p>
|
|
33
|
+
<p class="hint" id="camera-hint">Drag to orbit · scroll or pinch to zoom<br />Arrow keys rotate · + / − zoom · Home resets</p>
|
|
34
|
+
</div>
|
|
35
|
+
</figure>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="toolbar" aria-label="Building study controls">
|
|
38
|
+
<div class="button-group" role="group" aria-label="Camera views">
|
|
39
|
+
<button type="button" data-view="front">front</button>
|
|
40
|
+
<button type="button" data-view="quarter" aria-pressed="true">three-quarter</button>
|
|
41
|
+
<button type="button" data-view="side">side</button>
|
|
42
|
+
<button type="button" id="reset">reset view</button>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="button-group" role="group" aria-label="Model display">
|
|
45
|
+
<button type="button" id="wireframe" aria-pressed="false">wireframe</button>
|
|
46
|
+
<button type="button" id="turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="notes">
|
|
50
|
+
<p>The tower as built, from its mapped plan and the photograph the drawing was traced from: two halves split along the diagonal under one glazed diamond, the slot between its peaks, a notch at each end of the split, and white spandrels between ribbon windows. The lobby, window module, and roof grid are estimates.</p>
|
|
51
|
+
<p id="motion-status" role="status">Camera moves only when you interact or start the turntable.</p>
|
|
52
|
+
</div>
|
|
53
|
+
<noscript><p>Enable JavaScript to explore the 3D model. The existing SVG reference is shown above.</p></noscript>
|
|
54
|
+
</main>
|
|
55
|
+
<div class="tooltip" id="tooltip" aria-hidden="true" hidden></div>
|
|
56
|
+
<script src="study-loader.js" data-study="./building-study.js"></script>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|