@a2f0/skyline 0.2.59 → 0.2.61
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 +7 -3
- package/lib/models/colour-materials.d.ts +10 -0
- package/lib/models/colour-materials.js +40 -0
- package/lib/skyline-shell.js +25 -1
- package/package.json +1 -1
- package/site/index.html +4 -1
- package/site/models/colour-materials.js +40 -0
- package/site/skyline-3d.js +6 -3
- package/site/skyline-shell.js +25 -1
- package/site/viewer-modules.json +62 -0
package/docs/package.md
CHANGED
|
@@ -255,7 +255,7 @@ the option out.
|
|
|
255
255
|
|
|
256
256
|
### How the viewer's code loads
|
|
257
257
|
|
|
258
|
-
The package's module (`lib/`) is a small loader that a host bundles (under 4 KB). When
|
|
258
|
+
The package's module (`lib/`) is a small loader that a host bundles (under 4.5 KB). When
|
|
259
259
|
it mounts, it imports the viewer's code from the assets (`skyline-viewer.js`) with a
|
|
260
260
|
dynamic `import()` that carries `webpackIgnore`, `turbopackIgnore` and `@vite-ignore`
|
|
261
261
|
comments, so webpack (including Next.js with `--webpack`), Turbopack and Vite leave it
|
|
@@ -263,13 +263,17 @@ to the browser; Rollup, esbuild and Bun leave an import of a variable alone anyw
|
|
|
263
263
|
bundler that tried to resolve it would fail to build: tell it to ignore that import.
|
|
264
264
|
The viewer then loads its stylesheets, the scene's markup from `skyline-3d.html`, its
|
|
265
265
|
stars from `stars.svg`, and the scene's modules and models from the same directory.
|
|
266
|
-
Each module loads once per page, however many viewers mount.
|
|
266
|
+
Each module loads once per page, however many viewers mount. So that those modules
|
|
267
|
+
arrive together rather than one import at a time, the loader also reads
|
|
268
|
+
`viewer-modules.json`, which the build writes, and adds a `modulepreload` link for each
|
|
269
|
+
to the document's head, leaving out the vendored engine when the host shares its own;
|
|
270
|
+
the links leave the head once the viewer is ready, fails, or is destroyed.
|
|
267
271
|
|
|
268
272
|
### Content security policy and other origins
|
|
269
273
|
|
|
270
274
|
The viewer runs in the host's page, under the host's content security policy. A host
|
|
271
275
|
with one must allow the assets' origin (usually `'self'`) in `script-src`,
|
|
272
|
-
`style-src` and `connect-src` (the viewer fetches its markup and
|
|
276
|
+
`style-src` and `connect-src` (the viewer fetches its markup, stars and module list), and inline
|
|
273
277
|
styles in `style-src`, which the viewer sets on its elements. Serve the assets from
|
|
274
278
|
the host's origin when you can; on another origin, every file must allow the page's
|
|
275
279
|
origin through CORS. The viewer's code runs with the host page's privileges wherever
|
|
@@ -44,3 +44,13 @@ export interface SkylineColour {
|
|
|
44
44
|
refresh(): void;
|
|
45
45
|
}
|
|
46
46
|
export declare function createSkylineColour(models: readonly BuildingModel[]): SkylineColour;
|
|
47
|
+
/** A colour layer that is made on first use; see `deferSkylineColour`. */
|
|
48
|
+
export interface DeferredSkylineColour extends SkylineColour {
|
|
49
|
+
/**
|
|
50
|
+
* Makes the layer now, if it isn't made yet. Call it before anything rewrites the buildings'
|
|
51
|
+
* colours, such as a celebration's window illumination: the layer reads their greys once,
|
|
52
|
+
* when it is made.
|
|
53
|
+
*/
|
|
54
|
+
prepare(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function deferSkylineColour(models: readonly BuildingModel[]): DeferredSkylineColour;
|
|
@@ -506,3 +506,43 @@ export function createSkylineColour(models) {
|
|
|
506
506
|
},
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
+
// Making the layer reads every vertex of every building, a large share of the scene's start-up
|
|
510
|
+
// time, and changes nothing on screen until colour, a previewed time or a celebration needs it:
|
|
511
|
+
// its facade shaders install only then (colour-reflections.ts). So this one is made on first
|
|
512
|
+
// use, or by `prepare`, from the same buildings and with the sky and night it was given
|
|
513
|
+
// meanwhile, and acts from then on exactly as one made at once.
|
|
514
|
+
export function deferSkylineColour(models) {
|
|
515
|
+
let layer;
|
|
516
|
+
let sky, night = null;
|
|
517
|
+
const made = () => {
|
|
518
|
+
if (layer)
|
|
519
|
+
return layer;
|
|
520
|
+
layer = createSkylineColour(models);
|
|
521
|
+
if (sky !== undefined)
|
|
522
|
+
layer.setSky(sky);
|
|
523
|
+
if (night)
|
|
524
|
+
layer.setNight(night);
|
|
525
|
+
return layer;
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
get enabled() { return layer?.enabled ?? false; },
|
|
529
|
+
prepare() { made(); },
|
|
530
|
+
set(enabled) {
|
|
531
|
+
if (layer || enabled)
|
|
532
|
+
made().set(enabled);
|
|
533
|
+
},
|
|
534
|
+
setSky(colours) {
|
|
535
|
+
sky = colours;
|
|
536
|
+
layer?.setSky(colours);
|
|
537
|
+
},
|
|
538
|
+
setNight(lighting) {
|
|
539
|
+
night = lighting;
|
|
540
|
+
if (layer)
|
|
541
|
+
layer.setNight(lighting);
|
|
542
|
+
else if (lighting)
|
|
543
|
+
made();
|
|
544
|
+
},
|
|
545
|
+
// Until it is made, nothing shows colour or a previewed night for it to refresh.
|
|
546
|
+
refresh() { layer?.refresh(); },
|
|
547
|
+
};
|
|
548
|
+
}
|
package/lib/skyline-shell.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// The viewer's code and styles, relative to the assets.
|
|
2
2
|
const VIEWER = "skyline-viewer.js";
|
|
3
3
|
const STYLESHEETS = ["study.css", "viewer.css"];
|
|
4
|
+
// The modules the viewer's first frame needs, which the build lists (scripts/build/viewer-modules.ts).
|
|
5
|
+
const MODULES = "viewer-modules.json";
|
|
6
|
+
// The asset directories whose viewers were offered the host's engine: each directory's first
|
|
7
|
+
// scene keeps the engine it took (three-engine.ts), so its later viewers never fetch the
|
|
8
|
+
// vendored one either.
|
|
9
|
+
const sharedEngines = new Set();
|
|
4
10
|
/** The validated, slash-terminated directory the assets are served from. */
|
|
5
11
|
export function assetsBase(document, assetsUrl) {
|
|
6
12
|
const base = new URL(assetsUrl, document.baseURI);
|
|
@@ -20,6 +26,19 @@ export function assetsBase(document, assetsUrl) {
|
|
|
20
26
|
function importViewer(url) {
|
|
21
27
|
return import(/* webpackIgnore: true */ /* @vite-ignore */ /* turbopackIgnore: true */ url);
|
|
22
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Asks the browser to fetch every module the viewer's first frame needs at once, rather than
|
|
31
|
+
* each only once the module importing it has arrived, a chain several round trips deep. The
|
|
32
|
+
* links go in the document's head, where modulepreload applies, and leave it once the viewer
|
|
33
|
+
* settles or is destroyed. The vendored engine is fetched only when no viewer from the same
|
|
34
|
+
* assets offered the host's Three.js. Preloading only saves time, so a list that fails to load changes nothing else.
|
|
35
|
+
*/
|
|
36
|
+
function preloadModules(document, assets, hostEngine, signal) {
|
|
37
|
+
return fetch(new URL(MODULES, assets), { signal })
|
|
38
|
+
.then((response) => response.json())
|
|
39
|
+
.then(({ modules, engine }) => signal.aborted ? [] : [...(hostEngine || !engine ? [] : [engine]), ...modules].map((file) => document.head.appendChild(Object.assign(document.createElement("link"), { rel: "modulepreload", href: new URL(file, assets).href }))))
|
|
40
|
+
.catch(() => []);
|
|
41
|
+
}
|
|
23
42
|
/** Resolves once `link` has loaded its stylesheet. */
|
|
24
43
|
function loaded(link) {
|
|
25
44
|
return new Promise((resolve, reject) => {
|
|
@@ -79,6 +98,11 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
79
98
|
const controller = new AbortController();
|
|
80
99
|
let destroyed = false, mounted = false;
|
|
81
100
|
const context = { ...options, theme, assets, signal: controller.signal };
|
|
101
|
+
if (options.three)
|
|
102
|
+
sharedEngines.add(assets.href);
|
|
103
|
+
const preloads = preloadModules(document, assets, sharedEngines.has(assets.href), controller.signal);
|
|
104
|
+
const dropPreloads = () => void preloads.then((links) => links.forEach((link) => link.remove()));
|
|
105
|
+
controller.signal.addEventListener("abort", dropPreloads, { once: true });
|
|
82
106
|
const ready = new Promise((resolve, reject) => {
|
|
83
107
|
Promise.all([importViewer(new URL(VIEWER, assets).href), ...stylesheets.map(loaded)])
|
|
84
108
|
.then(async ([viewer]) => {
|
|
@@ -108,7 +132,7 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
108
132
|
});
|
|
109
133
|
// The element and the console report a failure, so a host that ignores `ready` sees no
|
|
110
134
|
// unhandled rejection.
|
|
111
|
-
ready.
|
|
135
|
+
ready.then(dropPreloads, dropPreloads);
|
|
112
136
|
return {
|
|
113
137
|
element,
|
|
114
138
|
ready,
|
package/package.json
CHANGED
package/site/index.html
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
11
11
|
<title>Chicago Skyline — Living Night</title>
|
|
12
12
|
<!-- viewer-page.js mounts the viewer from skyline-viewer.js, its styles and skyline-3d.html,
|
|
13
|
-
and starts the scene's code; fetch them alongside it
|
|
13
|
+
and starts the scene's code; fetch them alongside it, with the list of the scene's
|
|
14
|
+
modules that the viewer's shell preloads. -->
|
|
15
|
+
<link rel="modulepreload" href="skyline-shell.js" />
|
|
14
16
|
<link rel="modulepreload" href="skyline-viewer.js" />
|
|
17
|
+
<link rel="preload" href="viewer-modules.json" as="fetch" crossorigin="anonymous" />
|
|
15
18
|
<link rel="preload" href="viewer.css" as="style" />
|
|
16
19
|
<link rel="preload" href="study.css" as="style" />
|
|
17
20
|
<style>
|
|
@@ -506,3 +506,43 @@ export function createSkylineColour(models) {
|
|
|
506
506
|
},
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
+
// Making the layer reads every vertex of every building, a large share of the scene's start-up
|
|
510
|
+
// time, and changes nothing on screen until colour, a previewed time or a celebration needs it:
|
|
511
|
+
// its facade shaders install only then (colour-reflections.ts). So this one is made on first
|
|
512
|
+
// use, or by `prepare`, from the same buildings and with the sky and night it was given
|
|
513
|
+
// meanwhile, and acts from then on exactly as one made at once.
|
|
514
|
+
export function deferSkylineColour(models) {
|
|
515
|
+
let layer;
|
|
516
|
+
let sky, night = null;
|
|
517
|
+
const made = () => {
|
|
518
|
+
if (layer)
|
|
519
|
+
return layer;
|
|
520
|
+
layer = createSkylineColour(models);
|
|
521
|
+
if (sky !== undefined)
|
|
522
|
+
layer.setSky(sky);
|
|
523
|
+
if (night)
|
|
524
|
+
layer.setNight(night);
|
|
525
|
+
return layer;
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
get enabled() { return layer?.enabled ?? false; },
|
|
529
|
+
prepare() { made(); },
|
|
530
|
+
set(enabled) {
|
|
531
|
+
if (layer || enabled)
|
|
532
|
+
made().set(enabled);
|
|
533
|
+
},
|
|
534
|
+
setSky(colours) {
|
|
535
|
+
sky = colours;
|
|
536
|
+
layer?.setSky(colours);
|
|
537
|
+
},
|
|
538
|
+
setNight(lighting) {
|
|
539
|
+
night = lighting;
|
|
540
|
+
if (layer)
|
|
541
|
+
layer.setNight(lighting);
|
|
542
|
+
else if (lighting)
|
|
543
|
+
made();
|
|
544
|
+
},
|
|
545
|
+
// Until it is made, nothing shows colour or a previewed night for it to refresh.
|
|
546
|
+
refresh() { layer?.refresh(); },
|
|
547
|
+
};
|
|
548
|
+
}
|
package/site/skyline-3d.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chicagoDay, chicagoTime } from "./solar-time.js";
|
|
|
4
4
|
import { createTimeLighting } from "./time-of-day.js";
|
|
5
5
|
import { createGeographicRoads, geographicOrigin } from "./models/skyline-geography.js";
|
|
6
6
|
import { celebrations } from "./models/celebrations.js";
|
|
7
|
-
import { createDaylight,
|
|
7
|
+
import { createDaylight, daylightGround, deferSkylineColour } from "./models/colour-materials.js";
|
|
8
8
|
import { loadMarkup, loadStylesheet } from "./markup.js";
|
|
9
9
|
// The skyline viewer's full-screen 3D mode: every mapped building, seen through the
|
|
10
10
|
// drawing's own camera and framed as the viewer frames the drawing, so each tower stands
|
|
@@ -60,8 +60,9 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
60
60
|
enablePan: true,
|
|
61
61
|
minimumCameraHeight: 1,
|
|
62
62
|
});
|
|
63
|
-
// The colour trial's layer (models/colour-materials.ts), which the colour toggle turns on.
|
|
64
|
-
|
|
63
|
+
// The colour trial's layer (models/colour-materials.ts), which the colour toggle turns on. It
|
|
64
|
+
// is made on first use rather than before the first frame, which it does not change.
|
|
65
|
+
const colour = deferSkylineColour(models);
|
|
65
66
|
// A toolbar badge applies one celebration across the skyline, using each
|
|
66
67
|
// building's own supported wording. Context menus change only their building.
|
|
67
68
|
const illuminated = models.filter((model) => model.illumination);
|
|
@@ -69,6 +70,8 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
69
70
|
const celebrationAnnouncement = root.querySelector("#celebration-announcement");
|
|
70
71
|
const celebrationButtons = [...root.querySelectorAll("[data-celebration]")];
|
|
71
72
|
function setCelebration(targets, next) {
|
|
73
|
+
// The colour layer reads the buildings' greys before the lights rewrite them.
|
|
74
|
+
colour.prepare();
|
|
72
75
|
targets.forEach((model) => model.illumination.set(model.illumination.presets.some(({ id }) => id === next) ? next : null));
|
|
73
76
|
colour.refresh();
|
|
74
77
|
for (const button of celebrationButtons) {
|
package/site/skyline-shell.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// The viewer's code and styles, relative to the assets.
|
|
2
2
|
const VIEWER = "skyline-viewer.js";
|
|
3
3
|
const STYLESHEETS = ["study.css", "viewer.css"];
|
|
4
|
+
// The modules the viewer's first frame needs, which the build lists (scripts/build/viewer-modules.ts).
|
|
5
|
+
const MODULES = "viewer-modules.json";
|
|
6
|
+
// The asset directories whose viewers were offered the host's engine: each directory's first
|
|
7
|
+
// scene keeps the engine it took (three-engine.ts), so its later viewers never fetch the
|
|
8
|
+
// vendored one either.
|
|
9
|
+
const sharedEngines = new Set();
|
|
4
10
|
/** The validated, slash-terminated directory the assets are served from. */
|
|
5
11
|
export function assetsBase(document, assetsUrl) {
|
|
6
12
|
const base = new URL(assetsUrl, document.baseURI);
|
|
@@ -20,6 +26,19 @@ export function assetsBase(document, assetsUrl) {
|
|
|
20
26
|
function importViewer(url) {
|
|
21
27
|
return import(/* webpackIgnore: true */ /* @vite-ignore */ /* turbopackIgnore: true */ url);
|
|
22
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Asks the browser to fetch every module the viewer's first frame needs at once, rather than
|
|
31
|
+
* each only once the module importing it has arrived, a chain several round trips deep. The
|
|
32
|
+
* links go in the document's head, where modulepreload applies, and leave it once the viewer
|
|
33
|
+
* settles or is destroyed. The vendored engine is fetched only when no viewer from the same
|
|
34
|
+
* assets offered the host's Three.js. Preloading only saves time, so a list that fails to load changes nothing else.
|
|
35
|
+
*/
|
|
36
|
+
function preloadModules(document, assets, hostEngine, signal) {
|
|
37
|
+
return fetch(new URL(MODULES, assets), { signal })
|
|
38
|
+
.then((response) => response.json())
|
|
39
|
+
.then(({ modules, engine }) => signal.aborted ? [] : [...(hostEngine || !engine ? [] : [engine]), ...modules].map((file) => document.head.appendChild(Object.assign(document.createElement("link"), { rel: "modulepreload", href: new URL(file, assets).href }))))
|
|
40
|
+
.catch(() => []);
|
|
41
|
+
}
|
|
23
42
|
/** Resolves once `link` has loaded its stylesheet. */
|
|
24
43
|
function loaded(link) {
|
|
25
44
|
return new Promise((resolve, reject) => {
|
|
@@ -79,6 +98,11 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
79
98
|
const controller = new AbortController();
|
|
80
99
|
let destroyed = false, mounted = false;
|
|
81
100
|
const context = { ...options, theme, assets, signal: controller.signal };
|
|
101
|
+
if (options.three)
|
|
102
|
+
sharedEngines.add(assets.href);
|
|
103
|
+
const preloads = preloadModules(document, assets, sharedEngines.has(assets.href), controller.signal);
|
|
104
|
+
const dropPreloads = () => void preloads.then((links) => links.forEach((link) => link.remove()));
|
|
105
|
+
controller.signal.addEventListener("abort", dropPreloads, { once: true });
|
|
82
106
|
const ready = new Promise((resolve, reject) => {
|
|
83
107
|
Promise.all([importViewer(new URL(VIEWER, assets).href), ...stylesheets.map(loaded)])
|
|
84
108
|
.then(async ([viewer]) => {
|
|
@@ -108,7 +132,7 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
108
132
|
});
|
|
109
133
|
// The element and the console report a failure, so a host that ignores `ready` sees no
|
|
110
134
|
// unhandled rejection.
|
|
111
|
-
ready.
|
|
135
|
+
ready.then(dropPreloads, dropPreloads);
|
|
112
136
|
return {
|
|
113
137
|
element,
|
|
114
138
|
ready,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modules": [
|
|
3
|
+
"markup.js",
|
|
4
|
+
"three-engine.js",
|
|
5
|
+
"skyline-3d.js",
|
|
6
|
+
"study-viewer.js",
|
|
7
|
+
"vendor/three-r186.js",
|
|
8
|
+
"skyline-scene.js",
|
|
9
|
+
"models/skyline-geography-data.js",
|
|
10
|
+
"models/skyline-geography.js",
|
|
11
|
+
"models/building-kit.js",
|
|
12
|
+
"models/aon-geographic.js",
|
|
13
|
+
"models/aon-tower.js",
|
|
14
|
+
"models/facade-grid.js",
|
|
15
|
+
"models/blue-cross-geographic.js",
|
|
16
|
+
"models/window-illumination.js",
|
|
17
|
+
"models/celebrations.js",
|
|
18
|
+
"models/borg-warner-geographic.js",
|
|
19
|
+
"models/buckingham-geographic.js",
|
|
20
|
+
"models/chicago-athletic-association-geographic.js",
|
|
21
|
+
"models/crain-geographic.js",
|
|
22
|
+
"models/crain-tower.js",
|
|
23
|
+
"models/gage-geographic.js",
|
|
24
|
+
"models/heritage-geographic.js",
|
|
25
|
+
"models/hyatt-west-tower-geographic.js",
|
|
26
|
+
"models/keith-ascher-geographic.js",
|
|
27
|
+
"models/kemper-geographic.js",
|
|
28
|
+
"models/lake-view-geographic.js",
|
|
29
|
+
"models/maclean-center-geographic.js",
|
|
30
|
+
"models/michigan-boulevard-geographic.js",
|
|
31
|
+
"models/michigan-plaza-south-geographic.js",
|
|
32
|
+
"models/millennium-park-plaza-geographic.js",
|
|
33
|
+
"models/monroe-geographic.js",
|
|
34
|
+
"models/north-michigan-180-geographic.js",
|
|
35
|
+
"models/north-wabash-geographic.js",
|
|
36
|
+
"models/on-the-park-geographic.js",
|
|
37
|
+
"models/one-prudential-geographic.js",
|
|
38
|
+
"models/one-prudential-tower.js",
|
|
39
|
+
"models/peoples-gas-geographic.js",
|
|
40
|
+
"models/railway-exchange-geographic.js",
|
|
41
|
+
"models/river-plaza-geographic.js",
|
|
42
|
+
"models/sheraton-grand-geographic.js",
|
|
43
|
+
"models/six-north-michigan-geographic.js",
|
|
44
|
+
"models/swissotel-geographic.js",
|
|
45
|
+
"models/three-illinois-center-geographic.js",
|
|
46
|
+
"models/trump-geographic.js",
|
|
47
|
+
"models/trump-tower.js",
|
|
48
|
+
"models/two-illinois-center-geographic.js",
|
|
49
|
+
"models/two-prudential-geographic.js",
|
|
50
|
+
"models/two-prudential-tower.js",
|
|
51
|
+
"models/university-club-geographic.js",
|
|
52
|
+
"models/willoughby-tower-geographic.js",
|
|
53
|
+
"solar-time.js",
|
|
54
|
+
"time-of-day.js",
|
|
55
|
+
"models/colour-materials.js",
|
|
56
|
+
"models/colour-palette.js",
|
|
57
|
+
"models/colour-reflections.js",
|
|
58
|
+
"models/night-windows.js",
|
|
59
|
+
"models/material-names.js"
|
|
60
|
+
],
|
|
61
|
+
"engine": "vendor/three-r186.bundle.js"
|
|
62
|
+
}
|