@a2f0/skyline 0.2.10 → 0.2.12
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/docs/package.md +5 -0
- package/lib/models/colour-materials.d.ts +1 -1
- package/lib/models/colour-materials.js +25 -5
- package/lib/models/colour-palette.js +13 -0
- package/lib/models/colour-reflections.d.ts +2 -0
- package/lib/models/colour-reflections.js +53 -0
- package/lib/models/kemper-geographic.js +12 -6
- package/lib/skyline-package.d.ts +3 -1
- package/lib/skyline-package.js +4 -2
- package/lib/skyline-shell.d.ts +2 -0
- package/package.json +1 -1
- package/site/building-detail-page.js +1 -0
- package/site/building-detail.html +1 -0
- package/site/building-detail.js +24 -4
- package/site/models/colour-materials.js +25 -5
- package/site/models/colour-palette.js +13 -0
- package/site/models/colour-reflections.js +53 -0
- package/site/models/kemper-geographic.js +12 -6
- package/site/skyline-3d-page.js +1 -1
- package/site/skyline-3d.js +18 -8
- package/site/skyline-viewer.js +2 -1
- package/site/viewer-page.js +1 -0
package/docs/package.md
CHANGED
|
@@ -5,6 +5,11 @@ modules. Package builds emit ordinary ESM JavaScript and TypeScript declarations
|
|
|
5
5
|
consumers of the published package do not compile repository sources or run installation
|
|
6
6
|
scripts. Browser code and Node/Bun build helpers have separate entrypoints.
|
|
7
7
|
|
|
8
|
+
To start an embed in sunny-day colour, pass `colour: true` to `mountSkyline` alongside
|
|
9
|
+
`assetsUrl`. Its toolbar can return to greyscale, and building details inherit and share its mode.
|
|
10
|
+
Omitting `colour` keeps the existing greyscale default. The setting belongs to that instance;
|
|
11
|
+
it does not use storage or affect another embed. The standalone viewer accepts `?colour=1`.
|
|
12
|
+
|
|
8
13
|
## Install from npm
|
|
9
14
|
|
|
10
15
|
```sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BuildingModel } from "./building-kit.js";
|
|
2
2
|
import type { Daylight } from "../study-types.js";
|
|
3
|
-
export declare const materialNames: readonly ["neutral", "glass", "green glass", "blue-green glass", "bronze glass", "lit window", "dim window", "lamp", "limestone", "marble", "white granite", "granite", "dark granite", "slate", "white terracotta", "pink terracotta", "green tile", "brick", "common brick", "orange brick", "brown brick", "buff brick", "glazed brick", "concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless", "crown glass", "crown lights", "screen", "band lights", "sign board", "sign letters", "cap lights"];
|
|
3
|
+
export declare const materialNames: readonly ["neutral", "glass", "green glass", "blue-green glass", "bronze glass", "lit window", "dim window", "lamp", "limestone", "marble", "white granite", "granite", "dark granite", "slate", "white terracotta", "pink terracotta", "green tile", "brick", "common brick", "orange brick", "brown brick", "buff brick", "glazed brick", "concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless", "opaque spandrel", "crown glass", "crown lights", "screen", "band lights", "sign board", "sign letters", "cap lights"];
|
|
4
4
|
export type MaterialName = (typeof materialNames)[number];
|
|
5
5
|
export type Tones = Readonly<Record<number, MaterialName>>;
|
|
6
6
|
export type Batch = readonly [material: MaterialName, tones?: Tones];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as THREE from "../vendor/three-r186.js";
|
|
2
2
|
import { daylightColours, measuredColours } from "./colour-palette.js";
|
|
3
|
+
import { glassReflection } from "./colour-reflections.js";
|
|
3
4
|
// The colour trial (https://github.com/a2f0/skyline/issues/115): which named material each
|
|
4
5
|
// surface of each building is, and a layer that shows the colours measured for those materials
|
|
5
6
|
// (colour-palette.ts) over the greys the models build in, restoring the greys exactly when it is
|
|
@@ -11,7 +12,7 @@ export const materialNames = [
|
|
|
11
12
|
"neutral", "glass", "green glass", "blue-green glass", "bronze glass", "lit window", "dim window", "lamp",
|
|
12
13
|
"limestone", "marble", "white granite", "granite", "dark granite", "slate",
|
|
13
14
|
"white terracotta", "pink terracotta", "green tile", "brick", "common brick", "orange brick", "brown brick", "buff brick", "glazed brick",
|
|
14
|
-
"concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless",
|
|
15
|
+
"concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless", "opaque spandrel",
|
|
15
16
|
// Parts the photographs show on their own, named as the night first showed them: crown glass
|
|
16
17
|
// and crown lights, an emblem screen and the lights in its bands, a sign's board and letters,
|
|
17
18
|
// and a lit cap.
|
|
@@ -34,6 +35,7 @@ export const colourMaterials = {
|
|
|
34
35
|
"building-kemper": {
|
|
35
36
|
"marble shell": ["marble"],
|
|
36
37
|
"mullions and bands": ["marble"],
|
|
38
|
+
"opaque spandrels": ["opaque spandrel"],
|
|
37
39
|
"crown glazing": ["glass"],
|
|
38
40
|
"crown fins": ["aluminium"],
|
|
39
41
|
"roof cap": ["marble"],
|
|
@@ -61,7 +63,7 @@ export const colourMaterials = {
|
|
|
61
63
|
"bronze mullions and columns": ["bronze"],
|
|
62
64
|
},
|
|
63
65
|
"building-michigan-plaza-south-tower": {
|
|
64
|
-
"mapped tower shell": ["
|
|
66
|
+
"mapped tower shell": ["aluminium"],
|
|
65
67
|
"mullions and bands": ["aluminium"],
|
|
66
68
|
"lit glazing": ["lit window"],
|
|
67
69
|
"dim glazing": ["dim window"],
|
|
@@ -353,6 +355,8 @@ export function createDaylight() {
|
|
|
353
355
|
// another batch already took, since the builder lets batches share them.
|
|
354
356
|
export function createSkylineColour(models) {
|
|
355
357
|
const painted = [], flat = [], taken = new Set();
|
|
358
|
+
const reflections = [];
|
|
359
|
+
const glassMaterials = new Set(["glass", "green glass", "blue-green glass", "bronze glass", "crown glass", "lit window", "dim window"]);
|
|
356
360
|
for (const model of models) {
|
|
357
361
|
const buildingId = String(model.building.userData["buildingId"]);
|
|
358
362
|
if (!measuredColours[buildingId])
|
|
@@ -384,16 +388,30 @@ export function createSkylineColour(models) {
|
|
|
384
388
|
for (let vertex = 0; vertex < attribute.count; vertex += 1)
|
|
385
389
|
vertices.set(attribute.array[vertex * 3], (vertices.get(attribute.array[vertex * 3]) ?? 0) + 1);
|
|
386
390
|
vertices.forEach((n, grey) => count(materialOf(grey), grey, n));
|
|
387
|
-
surfaces.push({ material, colours: { attribute, array: attribute.array }, materialOf });
|
|
391
|
+
surfaces.push({ geometry: mesh.geometry, material, colours: { attribute, array: attribute.array }, materialOf });
|
|
388
392
|
}
|
|
389
393
|
else if (!taken.has(material)) {
|
|
390
394
|
taken.add(material);
|
|
391
395
|
count(materialOf(material.color.r), material.color.r, mesh.geometry.getAttribute("position").count);
|
|
392
|
-
surfaces.push({ material, colours: null, materialOf });
|
|
396
|
+
surfaces.push({ geometry: mesh.geometry, material, colours: null, materialOf });
|
|
393
397
|
}
|
|
394
398
|
});
|
|
395
399
|
const swatches = swatchesOf(buildingId, greys);
|
|
396
|
-
for (const { material, colours, materialOf } of surfaces) {
|
|
400
|
+
for (const { geometry, material, colours, materialOf } of surfaces) {
|
|
401
|
+
const mask = new Float32Array(geometry.getAttribute("position").count);
|
|
402
|
+
const reflecting = new Map();
|
|
403
|
+
for (let vertex = 0; vertex < mask.length; vertex += 1) {
|
|
404
|
+
const grey = colours ? colours.array[vertex * 3] : material.color.r;
|
|
405
|
+
let reflects = reflecting.get(grey);
|
|
406
|
+
if (reflects === undefined) {
|
|
407
|
+
const name = materialOf(grey);
|
|
408
|
+
reflects = glassMaterials.has(name) && swatches.get(name) ? 1 : 0;
|
|
409
|
+
reflecting.set(grey, reflects);
|
|
410
|
+
}
|
|
411
|
+
mask[vertex] = reflects;
|
|
412
|
+
}
|
|
413
|
+
if (mask.some((value) => value > 0))
|
|
414
|
+
reflections.push(glassReflection(material, geometry, mask));
|
|
397
415
|
const colourOf = (grey) => {
|
|
398
416
|
const swatch = swatches.get(materialOf(grey));
|
|
399
417
|
return swatch ? paint(grey, swatch) : [grey, grey, grey];
|
|
@@ -438,6 +456,8 @@ export function createSkylineColour(models) {
|
|
|
438
456
|
}
|
|
439
457
|
for (const { material, grey, colour } of flat)
|
|
440
458
|
material.color.copy(next ? colour : grey);
|
|
459
|
+
for (const reflect of reflections)
|
|
460
|
+
reflect(next);
|
|
441
461
|
},
|
|
442
462
|
refresh() {
|
|
443
463
|
if (!enabled)
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// By day no office lights show: lit and dim windows are the glass, in its measured colour.
|
|
2
2
|
const glass = (colour, material = "glass") => ({ [material]: colour, "lit window": colour, "dim window": colour });
|
|
3
3
|
export const measuredColours = {
|
|
4
|
+
// FID-COL-007: provisional, direct 2014 daytime close-ups, WITHOUT the panorama's light
|
|
5
|
+
// calibration. These measure appearance (sky reflections included), not intrinsic albedo.
|
|
6
|
+
// docs/three-illinois-center-reference.md and docs/michigan-plaza-south-geographic-reference.md.
|
|
7
|
+
"building-three-illinois-center": {
|
|
8
|
+
bronze: 0x55585f,
|
|
9
|
+
...glass(0x707d8e, "bronze glass"),
|
|
10
|
+
neutral: null,
|
|
11
|
+
},
|
|
12
|
+
"building-michigan-plaza-south-tower": {
|
|
13
|
+
aluminium: 0x1d2736,
|
|
14
|
+
...glass(0x6385a2),
|
|
15
|
+
},
|
|
4
16
|
// docs/railway-exchange-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg.
|
|
5
17
|
"building-railway-exchange": {
|
|
6
18
|
"white terracotta": 0xc8c1b3,
|
|
@@ -77,6 +89,7 @@ export const measuredColours = {
|
|
|
77
89
|
marble: 0xc3c0b6,
|
|
78
90
|
...glass(0x535455),
|
|
79
91
|
aluminium: null,
|
|
92
|
+
"opaque spandrel": null,
|
|
80
93
|
},
|
|
81
94
|
// docs/north-wabash-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg.
|
|
82
95
|
"building-330-north-wabash": {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import { daylightColours } from "./colour-palette.js";
|
|
3
|
+
// A small sky reflection over the measured glass colour. This is an illustration of an open
|
|
4
|
+
// sky, not a reflection of nearby buildings or a replacement for the toon renderer. The mask
|
|
5
|
+
// separates glass from masonry and mullions even when the builder batches them in one mesh.
|
|
6
|
+
// Install only in colour mode, and restore the original shader on exit. Keep the mask attached
|
|
7
|
+
// once used so Three.js releases its GPU buffer when it disposes the geometry.
|
|
8
|
+
export function glassReflection(material, geometry, mask) {
|
|
9
|
+
const attribute = new THREE.Float32BufferAttribute(mask, 1);
|
|
10
|
+
const originalCompile = material.onBeforeCompile, originalKey = material.customProgramCacheKey;
|
|
11
|
+
const key = originalKey.call(material);
|
|
12
|
+
const uniforms = {
|
|
13
|
+
skylineSky: { value: new THREE.Color(daylightColours.sky) },
|
|
14
|
+
skylineHorizon: { value: new THREE.Color(daylightColours.horizon) },
|
|
15
|
+
skylineGround: { value: new THREE.Color(0x444444) },
|
|
16
|
+
};
|
|
17
|
+
const compile = (shader, renderer) => {
|
|
18
|
+
originalCompile.call(material, shader, renderer);
|
|
19
|
+
Object.assign(shader.uniforms, uniforms);
|
|
20
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", `#include <common>
|
|
21
|
+
attribute float skylineGlass;
|
|
22
|
+
varying float vSkylineGlass;`).replace("#include <begin_vertex>", `#include <begin_vertex>
|
|
23
|
+
vSkylineGlass = skylineGlass;`);
|
|
24
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
25
|
+
varying float vSkylineGlass;
|
|
26
|
+
uniform vec3 skylineSky;
|
|
27
|
+
uniform vec3 skylineHorizon;
|
|
28
|
+
uniform vec3 skylineGround;`).replace("#include <opaque_fragment>", `
|
|
29
|
+
if (vSkylineGlass > 0.0) {
|
|
30
|
+
vec3 eye = vec3(0.0);
|
|
31
|
+
eye.z = 1.0;
|
|
32
|
+
if (!isOrthographic) eye = normalize(vViewPosition);
|
|
33
|
+
vec3 reflected = inverseTransformDirection(reflect(-eye, normal), viewMatrix);
|
|
34
|
+
vec3 sky = mix(skylineHorizon, skylineSky, smoothstep(0.0, 0.7, reflected.y));
|
|
35
|
+
vec3 environment = mix(skylineGround, sky, smoothstep(-0.12, 0.12, reflected.y));
|
|
36
|
+
float grazing = pow(1.0 - clamp(dot(normal, eye), 0.0, 1.0), 5.0);
|
|
37
|
+
outgoingLight = mix(outgoingLight, environment, vSkylineGlass * (0.06 + 0.54 * grazing));
|
|
38
|
+
}
|
|
39
|
+
#include <opaque_fragment>`);
|
|
40
|
+
};
|
|
41
|
+
return (enabled) => {
|
|
42
|
+
if (enabled) {
|
|
43
|
+
geometry.setAttribute("skylineGlass", attribute);
|
|
44
|
+
material.onBeforeCompile = compile;
|
|
45
|
+
material.customProgramCacheKey = () => `${key}:skyline-glass-v1`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
material.onBeforeCompile = originalCompile;
|
|
49
|
+
material.customProgramCacheKey = originalKey;
|
|
50
|
+
}
|
|
51
|
+
material.needsUpdate = true;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -4,7 +4,8 @@ import { createBuilder, inside, line, polygonOf } from "./building-kit.js";
|
|
|
4
4
|
// outlines; the 159 m top is OSM's height tag (522 ft in the 1962 record).
|
|
5
5
|
// The marble shell, window bays, raised mullions, dark crown band with light
|
|
6
6
|
// fins, and projecting cap reuse the fitted model's vocabulary at meter scale.
|
|
7
|
-
// Window-
|
|
7
|
+
// Window-column and pane proportions follow a 2012 photograph; bay counts,
|
|
8
|
+
// window-row and crown spacings remain estimates. See
|
|
8
9
|
// docs/kemper-geographic-reference.md. Units are meters; +x is east, +z south.
|
|
9
10
|
export const kemperGeographicLevels = Object.freeze({
|
|
10
11
|
podium: 7.8, // OSM way 685494067: two floors at the tower's average floor height
|
|
@@ -25,6 +26,7 @@ export function createKemperGeographicBuilding(record, projectPlan, offset) {
|
|
|
25
26
|
// with scattered lit units, pale mullions and fins.
|
|
26
27
|
const marble = kit.batch("Kemper · marble shell", kit.material(0x929292));
|
|
27
28
|
const mullions = kit.batch("Kemper · mullions and bands", kit.material(0x9b9b9b));
|
|
29
|
+
const spandrels = kit.batch("Kemper · opaque spandrels", kit.material(0x626262));
|
|
28
30
|
const crownGlass = kit.batch("Kemper · crown glazing", kit.material(0x1c1c1c));
|
|
29
31
|
const crownFins = kit.batch("Kemper · crown fins", kit.material(0xa3a3a3));
|
|
30
32
|
const cap = kit.batch("Kemper · roof cap", kit.material(0xa8a8a8));
|
|
@@ -58,21 +60,25 @@ export function createKemperGeographicBuilding(record, projectPlan, offset) {
|
|
|
58
60
|
return;
|
|
59
61
|
kit.box(batch, run.at((from + to) / 2), run.normal(0), (to - from) / 2, back, front, y0, y1);
|
|
60
62
|
};
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
+
// FID-KEM-002: the 2012 north-face photograph gives openings about 48% of
|
|
64
|
+
// each bay and glass about 52% of each floor pitch. Opaque panels continue
|
|
65
|
+
// the narrow columns between windows; the marble piers stay uninterrupted.
|
|
63
66
|
tower.forEach((run, side) => {
|
|
64
67
|
const bays = Math.max(1, Math.round(run.length / 3.1));
|
|
65
68
|
const width = run.length / bays;
|
|
69
|
+
const inset = width * 0.26;
|
|
70
|
+
for (let bay = 0; bay < bays; bay += 1) {
|
|
71
|
+
strip(spandrels, run, bay * width + inset, (bay + 1) * width - inset, h.podium, crownBase, 0.01, 0.04);
|
|
72
|
+
}
|
|
66
73
|
for (let floor = 3; floor <= 39; floor += 1) {
|
|
67
74
|
const bottom = floorHeight(floor), top = floorHeight(floor + 1);
|
|
68
75
|
for (let bay = 0; bay < bays; bay += 1) {
|
|
69
|
-
strip(paneTone(floor, bay, side), run, bay * width + 0.
|
|
76
|
+
strip(paneTone(floor, bay, side), run, bay * width + inset + 0.01, (bay + 1) * width - inset - 0.01, bottom + pitch * 0.24, top - pitch * 0.24, 0.02, 0.07);
|
|
70
77
|
}
|
|
71
|
-
strip(mullions, run, 0.015, run.length - 0.015, top - 0.15, top + 0.05, 0.03, 0.12);
|
|
72
78
|
}
|
|
73
79
|
for (let i = 0; i <= bays; i += 1) {
|
|
74
80
|
const s = Math.max(0.18, Math.min(run.length - 0.18, i * width));
|
|
75
|
-
strip(mullions, run, s -
|
|
81
|
+
strip(mullions, run, s - inset, s + inset, h.podium + 0.02, crownBase - 0.05, 0.05, 0.16);
|
|
76
82
|
}
|
|
77
83
|
strip(mullions, run, 0.015, run.length - 0.015, h.podium - 0.05, h.podium + 0.35, 0.06, 0.18);
|
|
78
84
|
});
|
package/lib/skyline-package.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface SkylineOptions {
|
|
|
16
16
|
readonly title?: string;
|
|
17
17
|
/** Start the 3D scene's control bar open, or folded behind its star. Closed by default. */
|
|
18
18
|
readonly controls?: "open" | "closed";
|
|
19
|
+
/** Start in the opt-in sunny-day colour view. Greyscale by default. */
|
|
20
|
+
readonly colour?: boolean;
|
|
19
21
|
/**
|
|
20
22
|
* Show the OpenStreetMap credit with the 3D scene's controls. Shown by default. OpenStreetMap's
|
|
21
23
|
* licence requires that credit wherever its data appears, so hide it only when the host page
|
|
@@ -36,4 +38,4 @@ export interface SkylineOptions {
|
|
|
36
38
|
* size and placement: give it an explicit height. Call `destroy` when the hosting component
|
|
37
39
|
* leaves.
|
|
38
40
|
*/
|
|
39
|
-
export declare function mountSkyline(container: HTMLElement, { assetsUrl, title, controls, attribution, three }: SkylineOptions): SkylineInstance;
|
|
41
|
+
export declare function mountSkyline(container: HTMLElement, { assetsUrl, title, controls, colour, attribution, three }: SkylineOptions): SkylineInstance;
|
package/lib/skyline-package.js
CHANGED
|
@@ -8,8 +8,10 @@ import { mountViewer } from "./skyline-shell.js";
|
|
|
8
8
|
* size and placement: give it an explicit height. Call `destroy` when the hosting component
|
|
9
9
|
* leaves.
|
|
10
10
|
*/
|
|
11
|
-
export function mountSkyline(container, { assetsUrl, title = "Interactive Chicago skyline", controls = "closed", attribution = true, three }) {
|
|
11
|
+
export function mountSkyline(container, { assetsUrl, title = "Interactive Chicago skyline", controls = "closed", colour = false, attribution = true, three }) {
|
|
12
12
|
if (controls !== "open" && controls !== "closed")
|
|
13
13
|
throw new TypeError('Skyline controls must be "open" or "closed".');
|
|
14
|
-
|
|
14
|
+
if (typeof colour !== "boolean")
|
|
15
|
+
throw new TypeError("Skyline colour must be a boolean.");
|
|
16
|
+
return mountViewer(container, { assetsUrl, title, controls, colour, attribution, navigation: false, page: false, three });
|
|
15
17
|
}
|
package/lib/skyline-shell.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ViewerContext {
|
|
|
11
11
|
readonly signal: AbortSignal;
|
|
12
12
|
/** Start the 3D scene's control bar open, rather than folded behind its star. */
|
|
13
13
|
readonly controls: "open" | "closed";
|
|
14
|
+
/** Start the scene and its building details in sunny-day colour. */
|
|
15
|
+
readonly colour?: boolean;
|
|
14
16
|
/** Show the OpenStreetMap credit with the scene's controls. */
|
|
15
17
|
readonly attribution: boolean;
|
|
16
18
|
/**
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
</div>
|
|
42
42
|
<div class="button-group" role="group" aria-label="Model display">
|
|
43
43
|
<button type="button" id="wireframe" aria-pressed="false">wireframe</button>
|
|
44
|
+
<button type="button" id="colour" aria-pressed="false" title="Show the building in sunny-day colour">colour</button>
|
|
44
45
|
<button type="button" id="turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
45
46
|
</div>
|
|
46
47
|
</div>
|
package/site/building-detail.js
CHANGED
|
@@ -3,19 +3,21 @@ import { createBuildingStudy } from "./study-viewer.js";
|
|
|
3
3
|
import { skylineAzimuth } from "./skyline-comparison.js";
|
|
4
4
|
import { geographicBuildings } from "./models/skyline-geography-data.js";
|
|
5
5
|
import { createGeographicBuilding, footprintMetrics } from "./models/skyline-geography.js";
|
|
6
|
+
import { createDaylight, createSkylineColour } from "./models/colour-materials.js";
|
|
6
7
|
// One mapped building of the 3D skyline, alone on its own platform, as the building study
|
|
7
8
|
// shows Crain: building-detail.html?building=<id>, and the panel the 3D skyline floats over
|
|
8
9
|
// the skyline from a building's context menu.
|
|
9
|
-
export function showBuildingDetail({ root, id, frame, signal }) {
|
|
10
|
+
export function showBuildingDetail({ root, id, frame, signal, colour = false, onColourChange }) {
|
|
10
11
|
const record = geographicBuildings.find((entry) => entry.id === id);
|
|
11
12
|
if (record)
|
|
12
|
-
showBuilding(record, root, frame, signal);
|
|
13
|
+
return showBuilding(record, root, frame, signal, colour, onColourChange);
|
|
13
14
|
else {
|
|
14
15
|
root.querySelector("#loading").textContent = "No mapped building by that name. Right-click a building on the 3D skyline to see its detail.";
|
|
15
16
|
root.querySelectorAll("button").forEach((button) => { button.disabled = true; });
|
|
16
17
|
}
|
|
18
|
+
return undefined;
|
|
17
19
|
}
|
|
18
|
-
function showBuilding(record, root, frame, signal) {
|
|
20
|
+
function showBuilding(record, root, frame, signal, initiallyColoured, onColourChange) {
|
|
19
21
|
const footprint = footprintMetrics(record.footprint.coordinates);
|
|
20
22
|
// Centred on its mapped outline: ground coordinates are east and north, the scene's x and -z.
|
|
21
23
|
const model = createGeographicBuilding(record, [-footprint.center[0], footprint.center[1]]);
|
|
@@ -62,7 +64,7 @@ function showBuilding(record, root, frame, signal) {
|
|
|
62
64
|
quarter: { azimuth: skylineAzimuth + 0.22, polar: Math.PI / 2 - 0.15, label: "three-quarter view" },
|
|
63
65
|
side: { azimuth: skylineAzimuth + Math.PI / 2, polar: Math.PI / 2 - 0.12, label: "side view" },
|
|
64
66
|
};
|
|
65
|
-
createBuildingStudy({
|
|
67
|
+
const viewer = createBuildingStudy({
|
|
66
68
|
root,
|
|
67
69
|
frame,
|
|
68
70
|
signal,
|
|
@@ -78,6 +80,24 @@ function showBuilding(record, root, frame, signal) {
|
|
|
78
80
|
// button stops.
|
|
79
81
|
turntable: true,
|
|
80
82
|
});
|
|
83
|
+
const colour = createSkylineColour([model]), button = root.querySelector("#colour");
|
|
84
|
+
let daylight;
|
|
85
|
+
signal.addEventListener("abort", () => daylight?.sky.dispose(), { once: true });
|
|
86
|
+
function setColour(enabled) {
|
|
87
|
+
colour.set(enabled);
|
|
88
|
+
if (enabled)
|
|
89
|
+
daylight ??= createDaylight();
|
|
90
|
+
viewer.setDaylight(enabled ? daylight : null);
|
|
91
|
+
button.setAttribute("aria-pressed", String(enabled));
|
|
92
|
+
button.title = enabled ? "Restore the building's greyscale view" : "Show the building in sunny-day colour";
|
|
93
|
+
}
|
|
94
|
+
button.addEventListener("click", () => {
|
|
95
|
+
setColour(!colour.enabled);
|
|
96
|
+
onColourChange?.(colour.enabled);
|
|
97
|
+
}, { signal });
|
|
98
|
+
if (initiallyColoured)
|
|
99
|
+
setColour(true);
|
|
100
|
+
return { setColour };
|
|
81
101
|
}
|
|
82
102
|
// Sources open beside the page, so a detail floated over the skyline stays where it is.
|
|
83
103
|
function link(document, href, text) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as THREE from "../vendor/three-r186.js";
|
|
2
2
|
import { daylightColours, measuredColours } from "./colour-palette.js";
|
|
3
|
+
import { glassReflection } from "./colour-reflections.js";
|
|
3
4
|
// The colour trial (https://github.com/a2f0/skyline/issues/115): which named material each
|
|
4
5
|
// surface of each building is, and a layer that shows the colours measured for those materials
|
|
5
6
|
// (colour-palette.ts) over the greys the models build in, restoring the greys exactly when it is
|
|
@@ -11,7 +12,7 @@ export const materialNames = [
|
|
|
11
12
|
"neutral", "glass", "green glass", "blue-green glass", "bronze glass", "lit window", "dim window", "lamp",
|
|
12
13
|
"limestone", "marble", "white granite", "granite", "dark granite", "slate",
|
|
13
14
|
"white terracotta", "pink terracotta", "green tile", "brick", "common brick", "orange brick", "brown brick", "buff brick", "glazed brick",
|
|
14
|
-
"concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless",
|
|
15
|
+
"concrete", "precast", "copper", "maroon", "blue enamel", "bronze", "dark metal", "aluminium", "stainless", "opaque spandrel",
|
|
15
16
|
// Parts the photographs show on their own, named as the night first showed them: crown glass
|
|
16
17
|
// and crown lights, an emblem screen and the lights in its bands, a sign's board and letters,
|
|
17
18
|
// and a lit cap.
|
|
@@ -34,6 +35,7 @@ export const colourMaterials = {
|
|
|
34
35
|
"building-kemper": {
|
|
35
36
|
"marble shell": ["marble"],
|
|
36
37
|
"mullions and bands": ["marble"],
|
|
38
|
+
"opaque spandrels": ["opaque spandrel"],
|
|
37
39
|
"crown glazing": ["glass"],
|
|
38
40
|
"crown fins": ["aluminium"],
|
|
39
41
|
"roof cap": ["marble"],
|
|
@@ -61,7 +63,7 @@ export const colourMaterials = {
|
|
|
61
63
|
"bronze mullions and columns": ["bronze"],
|
|
62
64
|
},
|
|
63
65
|
"building-michigan-plaza-south-tower": {
|
|
64
|
-
"mapped tower shell": ["
|
|
66
|
+
"mapped tower shell": ["aluminium"],
|
|
65
67
|
"mullions and bands": ["aluminium"],
|
|
66
68
|
"lit glazing": ["lit window"],
|
|
67
69
|
"dim glazing": ["dim window"],
|
|
@@ -353,6 +355,8 @@ export function createDaylight() {
|
|
|
353
355
|
// another batch already took, since the builder lets batches share them.
|
|
354
356
|
export function createSkylineColour(models) {
|
|
355
357
|
const painted = [], flat = [], taken = new Set();
|
|
358
|
+
const reflections = [];
|
|
359
|
+
const glassMaterials = new Set(["glass", "green glass", "blue-green glass", "bronze glass", "crown glass", "lit window", "dim window"]);
|
|
356
360
|
for (const model of models) {
|
|
357
361
|
const buildingId = String(model.building.userData["buildingId"]);
|
|
358
362
|
if (!measuredColours[buildingId])
|
|
@@ -384,16 +388,30 @@ export function createSkylineColour(models) {
|
|
|
384
388
|
for (let vertex = 0; vertex < attribute.count; vertex += 1)
|
|
385
389
|
vertices.set(attribute.array[vertex * 3], (vertices.get(attribute.array[vertex * 3]) ?? 0) + 1);
|
|
386
390
|
vertices.forEach((n, grey) => count(materialOf(grey), grey, n));
|
|
387
|
-
surfaces.push({ material, colours: { attribute, array: attribute.array }, materialOf });
|
|
391
|
+
surfaces.push({ geometry: mesh.geometry, material, colours: { attribute, array: attribute.array }, materialOf });
|
|
388
392
|
}
|
|
389
393
|
else if (!taken.has(material)) {
|
|
390
394
|
taken.add(material);
|
|
391
395
|
count(materialOf(material.color.r), material.color.r, mesh.geometry.getAttribute("position").count);
|
|
392
|
-
surfaces.push({ material, colours: null, materialOf });
|
|
396
|
+
surfaces.push({ geometry: mesh.geometry, material, colours: null, materialOf });
|
|
393
397
|
}
|
|
394
398
|
});
|
|
395
399
|
const swatches = swatchesOf(buildingId, greys);
|
|
396
|
-
for (const { material, colours, materialOf } of surfaces) {
|
|
400
|
+
for (const { geometry, material, colours, materialOf } of surfaces) {
|
|
401
|
+
const mask = new Float32Array(geometry.getAttribute("position").count);
|
|
402
|
+
const reflecting = new Map();
|
|
403
|
+
for (let vertex = 0; vertex < mask.length; vertex += 1) {
|
|
404
|
+
const grey = colours ? colours.array[vertex * 3] : material.color.r;
|
|
405
|
+
let reflects = reflecting.get(grey);
|
|
406
|
+
if (reflects === undefined) {
|
|
407
|
+
const name = materialOf(grey);
|
|
408
|
+
reflects = glassMaterials.has(name) && swatches.get(name) ? 1 : 0;
|
|
409
|
+
reflecting.set(grey, reflects);
|
|
410
|
+
}
|
|
411
|
+
mask[vertex] = reflects;
|
|
412
|
+
}
|
|
413
|
+
if (mask.some((value) => value > 0))
|
|
414
|
+
reflections.push(glassReflection(material, geometry, mask));
|
|
397
415
|
const colourOf = (grey) => {
|
|
398
416
|
const swatch = swatches.get(materialOf(grey));
|
|
399
417
|
return swatch ? paint(grey, swatch) : [grey, grey, grey];
|
|
@@ -438,6 +456,8 @@ export function createSkylineColour(models) {
|
|
|
438
456
|
}
|
|
439
457
|
for (const { material, grey, colour } of flat)
|
|
440
458
|
material.color.copy(next ? colour : grey);
|
|
459
|
+
for (const reflect of reflections)
|
|
460
|
+
reflect(next);
|
|
441
461
|
},
|
|
442
462
|
refresh() {
|
|
443
463
|
if (!enabled)
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// By day no office lights show: lit and dim windows are the glass, in its measured colour.
|
|
2
2
|
const glass = (colour, material = "glass") => ({ [material]: colour, "lit window": colour, "dim window": colour });
|
|
3
3
|
export const measuredColours = {
|
|
4
|
+
// FID-COL-007: provisional, direct 2014 daytime close-ups, WITHOUT the panorama's light
|
|
5
|
+
// calibration. These measure appearance (sky reflections included), not intrinsic albedo.
|
|
6
|
+
// docs/three-illinois-center-reference.md and docs/michigan-plaza-south-geographic-reference.md.
|
|
7
|
+
"building-three-illinois-center": {
|
|
8
|
+
bronze: 0x55585f,
|
|
9
|
+
...glass(0x707d8e, "bronze glass"),
|
|
10
|
+
neutral: null,
|
|
11
|
+
},
|
|
12
|
+
"building-michigan-plaza-south-tower": {
|
|
13
|
+
aluminium: 0x1d2736,
|
|
14
|
+
...glass(0x6385a2),
|
|
15
|
+
},
|
|
4
16
|
// docs/railway-exchange-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg.
|
|
5
17
|
"building-railway-exchange": {
|
|
6
18
|
"white terracotta": 0xc8c1b3,
|
|
@@ -77,6 +89,7 @@ export const measuredColours = {
|
|
|
77
89
|
marble: 0xc3c0b6,
|
|
78
90
|
...glass(0x535455),
|
|
79
91
|
aluminium: null,
|
|
92
|
+
"opaque spandrel": null,
|
|
80
93
|
},
|
|
81
94
|
// docs/north-wabash-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg.
|
|
82
95
|
"building-330-north-wabash": {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as THREE from "../vendor/three-r186.js";
|
|
2
|
+
import { daylightColours } from "./colour-palette.js";
|
|
3
|
+
// A small sky reflection over the measured glass colour. This is an illustration of an open
|
|
4
|
+
// sky, not a reflection of nearby buildings or a replacement for the toon renderer. The mask
|
|
5
|
+
// separates glass from masonry and mullions even when the builder batches them in one mesh.
|
|
6
|
+
// Install only in colour mode, and restore the original shader on exit. Keep the mask attached
|
|
7
|
+
// once used so Three.js releases its GPU buffer when it disposes the geometry.
|
|
8
|
+
export function glassReflection(material, geometry, mask) {
|
|
9
|
+
const attribute = new THREE.Float32BufferAttribute(mask, 1);
|
|
10
|
+
const originalCompile = material.onBeforeCompile, originalKey = material.customProgramCacheKey;
|
|
11
|
+
const key = originalKey.call(material);
|
|
12
|
+
const uniforms = {
|
|
13
|
+
skylineSky: { value: new THREE.Color(daylightColours.sky) },
|
|
14
|
+
skylineHorizon: { value: new THREE.Color(daylightColours.horizon) },
|
|
15
|
+
skylineGround: { value: new THREE.Color(0x444444) },
|
|
16
|
+
};
|
|
17
|
+
const compile = (shader, renderer) => {
|
|
18
|
+
originalCompile.call(material, shader, renderer);
|
|
19
|
+
Object.assign(shader.uniforms, uniforms);
|
|
20
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", `#include <common>
|
|
21
|
+
attribute float skylineGlass;
|
|
22
|
+
varying float vSkylineGlass;`).replace("#include <begin_vertex>", `#include <begin_vertex>
|
|
23
|
+
vSkylineGlass = skylineGlass;`);
|
|
24
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
25
|
+
varying float vSkylineGlass;
|
|
26
|
+
uniform vec3 skylineSky;
|
|
27
|
+
uniform vec3 skylineHorizon;
|
|
28
|
+
uniform vec3 skylineGround;`).replace("#include <opaque_fragment>", `
|
|
29
|
+
if (vSkylineGlass > 0.0) {
|
|
30
|
+
vec3 eye = vec3(0.0);
|
|
31
|
+
eye.z = 1.0;
|
|
32
|
+
if (!isOrthographic) eye = normalize(vViewPosition);
|
|
33
|
+
vec3 reflected = inverseTransformDirection(reflect(-eye, normal), viewMatrix);
|
|
34
|
+
vec3 sky = mix(skylineHorizon, skylineSky, smoothstep(0.0, 0.7, reflected.y));
|
|
35
|
+
vec3 environment = mix(skylineGround, sky, smoothstep(-0.12, 0.12, reflected.y));
|
|
36
|
+
float grazing = pow(1.0 - clamp(dot(normal, eye), 0.0, 1.0), 5.0);
|
|
37
|
+
outgoingLight = mix(outgoingLight, environment, vSkylineGlass * (0.06 + 0.54 * grazing));
|
|
38
|
+
}
|
|
39
|
+
#include <opaque_fragment>`);
|
|
40
|
+
};
|
|
41
|
+
return (enabled) => {
|
|
42
|
+
if (enabled) {
|
|
43
|
+
geometry.setAttribute("skylineGlass", attribute);
|
|
44
|
+
material.onBeforeCompile = compile;
|
|
45
|
+
material.customProgramCacheKey = () => `${key}:skyline-glass-v1`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
material.onBeforeCompile = originalCompile;
|
|
49
|
+
material.customProgramCacheKey = originalKey;
|
|
50
|
+
}
|
|
51
|
+
material.needsUpdate = true;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -4,7 +4,8 @@ import { createBuilder, inside, line, polygonOf } from "./building-kit.js";
|
|
|
4
4
|
// outlines; the 159 m top is OSM's height tag (522 ft in the 1962 record).
|
|
5
5
|
// The marble shell, window bays, raised mullions, dark crown band with light
|
|
6
6
|
// fins, and projecting cap reuse the fitted model's vocabulary at meter scale.
|
|
7
|
-
// Window-
|
|
7
|
+
// Window-column and pane proportions follow a 2012 photograph; bay counts,
|
|
8
|
+
// window-row and crown spacings remain estimates. See
|
|
8
9
|
// docs/kemper-geographic-reference.md. Units are meters; +x is east, +z south.
|
|
9
10
|
export const kemperGeographicLevels = Object.freeze({
|
|
10
11
|
podium: 7.8, // OSM way 685494067: two floors at the tower's average floor height
|
|
@@ -25,6 +26,7 @@ export function createKemperGeographicBuilding(record, projectPlan, offset) {
|
|
|
25
26
|
// with scattered lit units, pale mullions and fins.
|
|
26
27
|
const marble = kit.batch("Kemper · marble shell", kit.material(0x929292));
|
|
27
28
|
const mullions = kit.batch("Kemper · mullions and bands", kit.material(0x9b9b9b));
|
|
29
|
+
const spandrels = kit.batch("Kemper · opaque spandrels", kit.material(0x626262));
|
|
28
30
|
const crownGlass = kit.batch("Kemper · crown glazing", kit.material(0x1c1c1c));
|
|
29
31
|
const crownFins = kit.batch("Kemper · crown fins", kit.material(0xa3a3a3));
|
|
30
32
|
const cap = kit.batch("Kemper · roof cap", kit.material(0xa8a8a8));
|
|
@@ -58,21 +60,25 @@ export function createKemperGeographicBuilding(record, projectPlan, offset) {
|
|
|
58
60
|
return;
|
|
59
61
|
kit.box(batch, run.at((from + to) / 2), run.normal(0), (to - from) / 2, back, front, y0, y1);
|
|
60
62
|
};
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
+
// FID-KEM-002: the 2012 north-face photograph gives openings about 48% of
|
|
64
|
+
// each bay and glass about 52% of each floor pitch. Opaque panels continue
|
|
65
|
+
// the narrow columns between windows; the marble piers stay uninterrupted.
|
|
63
66
|
tower.forEach((run, side) => {
|
|
64
67
|
const bays = Math.max(1, Math.round(run.length / 3.1));
|
|
65
68
|
const width = run.length / bays;
|
|
69
|
+
const inset = width * 0.26;
|
|
70
|
+
for (let bay = 0; bay < bays; bay += 1) {
|
|
71
|
+
strip(spandrels, run, bay * width + inset, (bay + 1) * width - inset, h.podium, crownBase, 0.01, 0.04);
|
|
72
|
+
}
|
|
66
73
|
for (let floor = 3; floor <= 39; floor += 1) {
|
|
67
74
|
const bottom = floorHeight(floor), top = floorHeight(floor + 1);
|
|
68
75
|
for (let bay = 0; bay < bays; bay += 1) {
|
|
69
|
-
strip(paneTone(floor, bay, side), run, bay * width + 0.
|
|
76
|
+
strip(paneTone(floor, bay, side), run, bay * width + inset + 0.01, (bay + 1) * width - inset - 0.01, bottom + pitch * 0.24, top - pitch * 0.24, 0.02, 0.07);
|
|
70
77
|
}
|
|
71
|
-
strip(mullions, run, 0.015, run.length - 0.015, top - 0.15, top + 0.05, 0.03, 0.12);
|
|
72
78
|
}
|
|
73
79
|
for (let i = 0; i <= bays; i += 1) {
|
|
74
80
|
const s = Math.max(0.18, Math.min(run.length - 0.18, i * width));
|
|
75
|
-
strip(mullions, run, s -
|
|
81
|
+
strip(mullions, run, s - inset, s + inset, h.podium + 0.02, crownBase - 0.05, 0.05, 0.16);
|
|
76
82
|
}
|
|
77
83
|
strip(mullions, run, 0.015, run.length - 0.015, h.podium - 0.05, h.podium + 0.35, 0.06, 0.18);
|
|
78
84
|
});
|
package/site/skyline-3d-page.js
CHANGED
|
@@ -3,7 +3,7 @@ import { startSkyline3d } from "./skyline-3d.js";
|
|
|
3
3
|
// switch to. Its address's settings were applied before the scene's code arrived.
|
|
4
4
|
const life = new AbortController();
|
|
5
5
|
try {
|
|
6
|
-
startSkyline3d({ root: document, frame: document.body, assets: new URL("./", location.href), signal: life.signal });
|
|
6
|
+
startSkyline3d({ root: document, frame: document.body, assets: new URL("./", location.href), signal: life.signal, colour: new URLSearchParams(location.search).get("colour") === "1" });
|
|
7
7
|
}
|
|
8
8
|
catch (error) {
|
|
9
9
|
// A scene that fails part way releases what it started; study-loader.js says why.
|
package/site/skyline-3d.js
CHANGED
|
@@ -8,7 +8,7 @@ import { loadMarkup, loadStylesheet } from "./markup.js";
|
|
|
8
8
|
// drawing's own camera and framed as the viewer frames the drawing, so each tower stands
|
|
9
9
|
// where its drawn one does until the camera moves. It renders into skyline-3d.html's own
|
|
10
10
|
// document, or into the viewer's shadow root, from the same markup.
|
|
11
|
-
export function startSkyline3d({ root, frame, assets, signal, onShowOriginal, onReady, onUnavailable }) {
|
|
11
|
+
export function startSkyline3d({ root, frame, assets, signal, colour: initiallyColoured = false, onShowOriginal, onReady, onUnavailable }) {
|
|
12
12
|
const document = root instanceof Document ? root : root.ownerDocument;
|
|
13
13
|
const listening = { signal };
|
|
14
14
|
const { models, ground, settings, drawingView, comparisonViews, labels } = createGeographicSkyline();
|
|
@@ -112,14 +112,23 @@ export function startSkyline3d({ root, frame, assets, signal, onShowOriginal, on
|
|
|
112
112
|
// The colour trial's toggle, pressed while the scene shows a sunny day, the measured buildings in
|
|
113
113
|
// their colours over the greys; pressed again, it restores the night and every grey exactly.
|
|
114
114
|
const colourButton = root.querySelector("#colour");
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
const roadway = roads.material;
|
|
116
|
+
let daylight;
|
|
117
|
+
let detailView;
|
|
118
|
+
signal.addEventListener("abort", () => daylight?.sky.dispose(), { once: true });
|
|
119
|
+
function setColour(enabled) {
|
|
120
|
+
colour.set(enabled);
|
|
121
|
+
if (enabled)
|
|
122
|
+
daylight ??= createDaylight();
|
|
119
123
|
roadway.color.setHex(colour.enabled ? daylightGround.roads : nightRoads);
|
|
120
124
|
viewer.setDaylight(colour.enabled ? daylight : null);
|
|
121
125
|
colourButton.setAttribute("aria-pressed", String(colour.enabled));
|
|
122
|
-
|
|
126
|
+
colourButton.title = enabled ? "Restore the greyscale skyline" : "Show the skyline in sunny-day colour";
|
|
127
|
+
detailView?.setColour(enabled);
|
|
128
|
+
}
|
|
129
|
+
colourButton.addEventListener("click", () => setColour(!colour.enabled), listening);
|
|
130
|
+
if (initiallyColoured)
|
|
131
|
+
setColour(true);
|
|
123
132
|
// The star in the middle of the bar opens and closes the toolbar. Each group unfolds from
|
|
124
133
|
// the star: clipped open from its `--fold` inset while it fades in, one beside the star
|
|
125
134
|
// slides out from it to its edge of the bar, and it folds back the same way before it hides. A toggle mid-way reverses the moving
|
|
@@ -223,7 +232,7 @@ export function startSkyline3d({ root, frame, assets, signal, onShowOriginal, on
|
|
|
223
232
|
function openMenu(building, x, y) {
|
|
224
233
|
menuBuilding = building.building.userData["buildingId"];
|
|
225
234
|
menuTitle.textContent = building.building.userData["geography"]?.name ?? building.building.name;
|
|
226
|
-
detailLink.href = new URL(`building-detail.html?building=${encodeURIComponent(menuBuilding)}`, assets).href;
|
|
235
|
+
detailLink.href = new URL(`building-detail.html?building=${encodeURIComponent(menuBuilding)}${colour.enabled ? "&colour=1" : ""}`, assets).href;
|
|
227
236
|
lightingMenu.replaceChildren();
|
|
228
237
|
lightingMenu.hidden = !building.illumination;
|
|
229
238
|
if (building.illumination) {
|
|
@@ -377,6 +386,7 @@ export function startSkyline3d({ root, frame, assets, signal, onShowOriginal, on
|
|
|
377
386
|
function clearDetail() {
|
|
378
387
|
detail?.abort();
|
|
379
388
|
detail = null;
|
|
389
|
+
detailView = undefined;
|
|
380
390
|
detailPanel.querySelector(".detail-host")?.remove();
|
|
381
391
|
}
|
|
382
392
|
function openDetail(id, name) {
|
|
@@ -401,7 +411,7 @@ export function startSkyline3d({ root, frame, assets, signal, onShowOriginal, on
|
|
|
401
411
|
// The panel names the detail and closes it, so the detail leaves out its own links.
|
|
402
412
|
markup.classList.add("framed");
|
|
403
413
|
detailRoot.append(document.importNode(markup, true));
|
|
404
|
-
showBuildingDetail({ root: detailRoot, id, frame: detailRoot.querySelector(".detail-root"), signal: controller.signal });
|
|
414
|
+
detailView = showBuildingDetail({ root: detailRoot, id, frame: detailRoot.querySelector(".detail-root"), signal: controller.signal, colour: colour.enabled, onColourChange: setColour });
|
|
405
415
|
}).catch((error) => {
|
|
406
416
|
if (controller.signal.aborted)
|
|
407
417
|
return;
|
package/site/skyline-viewer.js
CHANGED
|
@@ -22,7 +22,7 @@ function element(document, tag, properties = {}, attributes = {}) {
|
|
|
22
22
|
created.setAttribute(name, value);
|
|
23
23
|
return created;
|
|
24
24
|
}
|
|
25
|
-
export function mount(root, { assets, signal, controls, attribution, navigation, page, three }) {
|
|
25
|
+
export function mount(root, { assets, signal, controls, colour, attribution, navigation, page, three }) {
|
|
26
26
|
const document = root.ownerDocument;
|
|
27
27
|
const host = root.host;
|
|
28
28
|
const listening = { signal };
|
|
@@ -180,6 +180,7 @@ export function mount(root, { assets, signal, controls, attribution, navigation,
|
|
|
180
180
|
frame: scene,
|
|
181
181
|
assets,
|
|
182
182
|
signal: sceneLife.signal,
|
|
183
|
+
colour,
|
|
183
184
|
onShowOriginal: showOriginal,
|
|
184
185
|
onReady() {
|
|
185
186
|
sceneUnavailable = false;
|
package/site/viewer-page.js
CHANGED
|
@@ -14,6 +14,7 @@ mountViewer(document.querySelector("#viewer"), {
|
|
|
14
14
|
title: "Chicago skyline",
|
|
15
15
|
controls: controls === "open" || controls === "closed" ? controls : embedded ? "closed" : "open",
|
|
16
16
|
attribution: settings.get("attribution") !== "hidden",
|
|
17
|
+
colour: settings.get("colour") === "1",
|
|
17
18
|
navigation: !embedded,
|
|
18
19
|
page: true,
|
|
19
20
|
});
|