@a2f0/skyline 0.2.25 → 0.2.27

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/README.md CHANGED
@@ -47,8 +47,9 @@ bun run deploy # publish to Cloudflare; needs Node.js 22+ and
47
47
 
48
48
  [Development](docs/development.md) explains the checks, hooks, and PR workflow, and
49
49
  [Deploying](docs/deploying.md) the release, its verification, and the gitignored `.secrets` link
50
- (`ln -s ../matrix-secrets/skyline .secrets`) that Terraform reads credentials through. To add a
51
- building or improve one, start with [Adding a building](docs/adding-a-building.md) or the
52
- [fidelity queue](docs/building-fidelity.md). Coding agents follow [`AGENTS.md`](AGENTS.md).
50
+ (`ln -s ../matrix-secrets/skyline .secrets`) that Terraform reads its Cloudflare and R2 state
51
+ credentials through. To add a building or improve one, start with
52
+ [Adding a building](docs/adding-a-building.md) or the [fidelity queue](docs/building-fidelity.md).
53
+ Coding agents follow [`AGENTS.md`](AGENTS.md).
53
54
 
54
55
  All documentation is indexed in [`docs/`](docs/README.md).
@@ -9,8 +9,9 @@ export declare const daylightColours: {
9
9
  export declare const nightColours: {
10
10
  readonly ambient: 10596296;
11
11
  readonly fill: 9281469;
12
- readonly window: 16764817;
13
- readonly windowWhite: 16772558;
12
+ readonly window: 16761213;
13
+ readonly windowWhite: 16772570;
14
+ readonly windowCool: 14478079;
14
15
  };
15
16
  export declare const timeOfDayColours: readonly [{
16
17
  readonly elevation: -18;
@@ -290,8 +290,9 @@ export const measuredColours = {
290
290
  // above the horizon, each the median of a clear box of the same panorama (scripts/sample-colours.ts,
291
291
  // the day study's sky), and the sun's warm white, chosen.
292
292
  export const daylightColours = { sky: 0x9ebbdb, horizon: 0xc4d1e1, sun: 0xfff4e6 };
293
- // Art-directed night illumination: quiet cool facades and a mix of warm office lamps.
294
- export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffcf91, windowWhite: 0xffedce };
293
+ // Illustrated warm, neutral and cool white fixtures; not calibrated Kelvin values.
294
+ // DOE's white-tuning overview informs the range; see docs/viewer.md (time of day).
295
+ export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffc17d, windowWhite: 0xffedda, windowCool: 0xdceaff };
295
296
  // Illustrated clear-sky time previews. Solar elevation chooses the stops; these are
296
297
  // artistic colours, separate from the photograph-calibrated daytime palette above.
297
298
  export const timeOfDayColours = [
@@ -22,6 +22,7 @@ export function glassReflection(material, geometry, mask, windows = new Float32A
22
22
  skylineWindowsSuppressed: { value: 0 },
23
23
  skylineWindowWarm: { value: new THREE.Color(nightColours.window) },
24
24
  skylineWindowWhite: { value: new THREE.Color(nightColours.windowWhite) },
25
+ skylineWindowCool: { value: new THREE.Color(nightColours.windowCool) },
25
26
  };
26
27
  const compile = (shader, renderer) => {
27
28
  originalCompile.call(material, shader, renderer);
@@ -44,7 +45,8 @@ uniform float skylineDarkness;
44
45
  uniform float skylineActivity;
45
46
  uniform float skylineWindowsSuppressed;
46
47
  uniform vec3 skylineWindowWarm;
47
- uniform vec3 skylineWindowWhite;`).replace("#include <opaque_fragment>", `
48
+ uniform vec3 skylineWindowWhite;
49
+ uniform vec3 skylineWindowCool;`).replace("#include <opaque_fragment>", `
48
50
  if (vSkylineGlass > 0.0 && skylineReflecting > 0.0) {
49
51
  vec3 eye = vec3(0.0);
50
52
  eye.z = 1.0;
@@ -59,7 +61,10 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
59
61
  float occupied = 1.0 - smoothstep(skylineActivity - 0.05, skylineActivity + 0.05, vSkylineWindow.x);
60
62
  float dusk = smoothstep(0.12 + 0.35 * vSkylineWindow.x, 0.45 + 0.35 * vSkylineWindow.x, skylineDarkness);
61
63
  float light = vSkylineWindow.z > 1.5 ? skylineDarkness : occupied * dusk * (1.0 - skylineWindowsSuppressed);
62
- vec3 lamp = mix(skylineWindowWarm, skylineWindowWhite, vSkylineWindow.w);
64
+ float tint = vSkylineWindow.w;
65
+ vec3 lamp = tint < 0.5
66
+ ? mix(skylineWindowWarm, skylineWindowWhite, tint * 2.0)
67
+ : mix(skylineWindowWhite, skylineWindowCool, (tint - 0.5) * 2.0);
63
68
  outgoingLight += lamp * vSkylineWindow.y * light;
64
69
  }
65
70
  #include <opaque_fragment>`);
@@ -74,7 +79,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
74
79
  geometry.setAttribute("skylineGlass", attribute);
75
80
  geometry.setAttribute("skylineWindow", windowAttribute);
76
81
  material.onBeforeCompile = compile;
77
- material.customProgramCacheKey = () => `${key}:skyline-facade-v2`;
82
+ material.customProgramCacheKey = () => `${key}:skyline-facade-v3`;
78
83
  }
79
84
  else {
80
85
  material.onBeforeCompile = originalCompile;
@@ -99,7 +104,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
99
104
  uniforms.skylineDarkness.value = lighting?.darkness ?? 0;
100
105
  uniforms.skylineWindowsSuppressed.value = celebration ? 1 : 0;
101
106
  uniforms.skylineActivity.value = lighting?.activity ?? 0;
102
- for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite]]) {
107
+ for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite], [uniforms.skylineWindowCool, nightColours.windowCool]]) {
103
108
  uniform.value.setHex(hex);
104
109
  if (!coloured) {
105
110
  const { r, g, b } = uniform.value, grey = r * 0.2126 + g * 0.7152 + b * 0.0722;
@@ -29,6 +29,11 @@ const architectural = new Set(["lamp", "crown lights", "band lights", "sign lett
29
29
  export function nightWindowMask(geometry, buildingId, materialOf) {
30
30
  const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
31
31
  const mask = new Float32Array(positions.count * 4);
32
+ // A dominant fixture family per building, with smaller tenant-floor and room
33
+ // differences. Independent seeds keep colour from tracking occupancy/brightness.
34
+ // These are illustrated white-light mixes, not surveyed fixture temperatures.
35
+ const buildingTint = hash(`${buildingId}:fixture`);
36
+ const architecturalTint = 0.15 + 0.65 * hash(`${buildingId}:architectural-fixture`);
32
37
  const samePoint = (a, b) => positions.getX(a) === positions.getX(b) && positions.getY(a) === positions.getY(b) && positions.getZ(a) === positions.getZ(b);
33
38
  for (let first = 0; first < positions.count;) {
34
39
  const name = materialOf(first), crown = architectural.has(name);
@@ -61,6 +66,9 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
61
66
  const floor = Math.round(y / count / 3.4), across = Math.floor(x / count / 7), depth = Math.floor(z / count / 7);
62
67
  const room = hash(`${buildingId}:${floor}:${across}:${depth}`);
63
68
  const brightness = hash(`${buildingId}:${floor}:${across}:${depth}:brightness`);
69
+ const tint = crown ? architecturalTint : 0.65 * buildingTint
70
+ + 0.2 * hash(`${buildingId}:${floor}:fixture`)
71
+ + 0.15 * hash(`${buildingId}:${floor}:${across}:${depth}:fixture`);
64
72
  for (let vertex = first; vertex < end; vertex += 1) {
65
73
  if (materialOf(vertex) !== name)
66
74
  continue;
@@ -68,7 +76,7 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
68
76
  mask[vertex * 4] = rank;
69
77
  mask[vertex * 4 + 1] = crown ? 0.3 : 0.25 + brightness * 0.45;
70
78
  mask[vertex * 4 + 2] = crown ? 2 : 1;
71
- mask[vertex * 4 + 3] = brightness;
79
+ mask[vertex * 4 + 3] = tint;
72
80
  }
73
81
  first = end;
74
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2f0/skyline",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "description": "Interactive Chicago skyline viewer and reusable Three.js building models",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -290,8 +290,9 @@ export const measuredColours = {
290
290
  // above the horizon, each the median of a clear box of the same panorama (scripts/sample-colours.ts,
291
291
  // the day study's sky), and the sun's warm white, chosen.
292
292
  export const daylightColours = { sky: 0x9ebbdb, horizon: 0xc4d1e1, sun: 0xfff4e6 };
293
- // Art-directed night illumination: quiet cool facades and a mix of warm office lamps.
294
- export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffcf91, windowWhite: 0xffedce };
293
+ // Illustrated warm, neutral and cool white fixtures; not calibrated Kelvin values.
294
+ // DOE's white-tuning overview informs the range; see docs/viewer.md (time of day).
295
+ export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffc17d, windowWhite: 0xffedda, windowCool: 0xdceaff };
295
296
  // Illustrated clear-sky time previews. Solar elevation chooses the stops; these are
296
297
  // artistic colours, separate from the photograph-calibrated daytime palette above.
297
298
  export const timeOfDayColours = [
@@ -22,6 +22,7 @@ export function glassReflection(material, geometry, mask, windows = new Float32A
22
22
  skylineWindowsSuppressed: { value: 0 },
23
23
  skylineWindowWarm: { value: new THREE.Color(nightColours.window) },
24
24
  skylineWindowWhite: { value: new THREE.Color(nightColours.windowWhite) },
25
+ skylineWindowCool: { value: new THREE.Color(nightColours.windowCool) },
25
26
  };
26
27
  const compile = (shader, renderer) => {
27
28
  originalCompile.call(material, shader, renderer);
@@ -44,7 +45,8 @@ uniform float skylineDarkness;
44
45
  uniform float skylineActivity;
45
46
  uniform float skylineWindowsSuppressed;
46
47
  uniform vec3 skylineWindowWarm;
47
- uniform vec3 skylineWindowWhite;`).replace("#include <opaque_fragment>", `
48
+ uniform vec3 skylineWindowWhite;
49
+ uniform vec3 skylineWindowCool;`).replace("#include <opaque_fragment>", `
48
50
  if (vSkylineGlass > 0.0 && skylineReflecting > 0.0) {
49
51
  vec3 eye = vec3(0.0);
50
52
  eye.z = 1.0;
@@ -59,7 +61,10 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
59
61
  float occupied = 1.0 - smoothstep(skylineActivity - 0.05, skylineActivity + 0.05, vSkylineWindow.x);
60
62
  float dusk = smoothstep(0.12 + 0.35 * vSkylineWindow.x, 0.45 + 0.35 * vSkylineWindow.x, skylineDarkness);
61
63
  float light = vSkylineWindow.z > 1.5 ? skylineDarkness : occupied * dusk * (1.0 - skylineWindowsSuppressed);
62
- vec3 lamp = mix(skylineWindowWarm, skylineWindowWhite, vSkylineWindow.w);
64
+ float tint = vSkylineWindow.w;
65
+ vec3 lamp = tint < 0.5
66
+ ? mix(skylineWindowWarm, skylineWindowWhite, tint * 2.0)
67
+ : mix(skylineWindowWhite, skylineWindowCool, (tint - 0.5) * 2.0);
63
68
  outgoingLight += lamp * vSkylineWindow.y * light;
64
69
  }
65
70
  #include <opaque_fragment>`);
@@ -74,7 +79,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
74
79
  geometry.setAttribute("skylineGlass", attribute);
75
80
  geometry.setAttribute("skylineWindow", windowAttribute);
76
81
  material.onBeforeCompile = compile;
77
- material.customProgramCacheKey = () => `${key}:skyline-facade-v2`;
82
+ material.customProgramCacheKey = () => `${key}:skyline-facade-v3`;
78
83
  }
79
84
  else {
80
85
  material.onBeforeCompile = originalCompile;
@@ -99,7 +104,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
99
104
  uniforms.skylineDarkness.value = lighting?.darkness ?? 0;
100
105
  uniforms.skylineWindowsSuppressed.value = celebration ? 1 : 0;
101
106
  uniforms.skylineActivity.value = lighting?.activity ?? 0;
102
- for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite]]) {
107
+ for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite], [uniforms.skylineWindowCool, nightColours.windowCool]]) {
103
108
  uniform.value.setHex(hex);
104
109
  if (!coloured) {
105
110
  const { r, g, b } = uniform.value, grey = r * 0.2126 + g * 0.7152 + b * 0.0722;
@@ -29,6 +29,11 @@ const architectural = new Set(["lamp", "crown lights", "band lights", "sign lett
29
29
  export function nightWindowMask(geometry, buildingId, materialOf) {
30
30
  const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
31
31
  const mask = new Float32Array(positions.count * 4);
32
+ // A dominant fixture family per building, with smaller tenant-floor and room
33
+ // differences. Independent seeds keep colour from tracking occupancy/brightness.
34
+ // These are illustrated white-light mixes, not surveyed fixture temperatures.
35
+ const buildingTint = hash(`${buildingId}:fixture`);
36
+ const architecturalTint = 0.15 + 0.65 * hash(`${buildingId}:architectural-fixture`);
32
37
  const samePoint = (a, b) => positions.getX(a) === positions.getX(b) && positions.getY(a) === positions.getY(b) && positions.getZ(a) === positions.getZ(b);
33
38
  for (let first = 0; first < positions.count;) {
34
39
  const name = materialOf(first), crown = architectural.has(name);
@@ -61,6 +66,9 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
61
66
  const floor = Math.round(y / count / 3.4), across = Math.floor(x / count / 7), depth = Math.floor(z / count / 7);
62
67
  const room = hash(`${buildingId}:${floor}:${across}:${depth}`);
63
68
  const brightness = hash(`${buildingId}:${floor}:${across}:${depth}:brightness`);
69
+ const tint = crown ? architecturalTint : 0.65 * buildingTint
70
+ + 0.2 * hash(`${buildingId}:${floor}:fixture`)
71
+ + 0.15 * hash(`${buildingId}:${floor}:${across}:${depth}:fixture`);
64
72
  for (let vertex = first; vertex < end; vertex += 1) {
65
73
  if (materialOf(vertex) !== name)
66
74
  continue;
@@ -68,7 +76,7 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
68
76
  mask[vertex * 4] = rank;
69
77
  mask[vertex * 4 + 1] = crown ? 0.3 : 0.25 + brightness * 0.45;
70
78
  mask[vertex * 4 + 2] = crown ? 2 : 1;
71
- mask[vertex * 4 + 3] = brightness;
79
+ mask[vertex * 4 + 3] = tint;
72
80
  }
73
81
  first = end;
74
82
  }