1
0

fix: a building glyph's pane and door are true metres, not scene units

0.012 and 0.028 were scene-unit thicknesses chosen against the Bay Area
board, so a pane stood 1.13 m proud and a door 2.64 m. On the merged
board that is 23 m of glass and a 54 m door — measured on the HQ glyph
at Salesforce Tower while naming the 700 m FiDi slab.

Same class as the pin, the aeroplane, and the freeway ribbon. The
Bay Area appearance does not move; the test is the two-board property.
This commit is contained in:
2026-08-24 22:04:15 -07:00
parent 11d54c48ef
commit 1c1148739c
2 changed files with 90 additions and 4 deletions
+14 -2
View File
@@ -49,6 +49,18 @@ interface WindowPlacement {
} }
const DEFAULT_BODY = 0x8d9aa4; const DEFAULT_BODY = 0x8d9aa4;
/**
* Pane and door thickness, in true metres.
*
* These were 0.012 and 0.028 **scene units**, chosen against the Bay Area
* board (94.3 m to the unit) so a pane stood 1.13 m proud and a door 2.64 m.
* On the merged board that is 23 m of glass and a 54 m door — measured on the
* HQ glyph at Salesforce Tower while hunting the 700 m FiDi slab. Same class
* of bug as the pin, the aeroplane, and the freeway ribbon: a constant in
* scene units is a constant that assumes one board.
*/
const PANE_DEPTH_M = 1.13;
const DOOR_DEPTH_M = 2.64;
const WINDOW_DAY = new THREE.Color(0x334c5d); const WINDOW_DAY = new THREE.Color(0x334c5d);
const WINDOW_LIT = new THREE.Color(0xd7c68d); const WINDOW_LIT = new THREE.Color(0xd7c68d);
@@ -203,7 +215,7 @@ export function createBuildingGlyph(
windows.forEach((window, i) => { windows.forEach((window, i) => {
position.set(window.x, window.y, window.z); position.set(window.x, window.y, window.z);
quaternion.setFromAxisAngle(up, window.yaw); quaternion.setFromAxisAngle(up, window.yaw);
scale.set(window.width, window.height, 0.012); scale.set(window.width, window.height, PANE_DEPTH_M * horizontal);
matrix.compose(position, quaternion, scale); matrix.compose(position, quaternion, scale);
windowMesh.setMatrixAt(i, matrix); windowMesh.setMatrixAt(i, matrix);
// A few warm panes stop the repeated grid reading as graph paper. The // A few warm panes stop the repeated grid reading as graph paper. The
@@ -248,7 +260,7 @@ export function createBuildingGlyph(
vertical(first.base), vertical(first.base),
(first.z + first.depth / 2 + 0.18) * horizontal, (first.z + first.depth / 2 + 0.18) * horizontal,
); );
door.scale.set(doorWidth * horizontal, vertical(doorHeight), 0.028); door.scale.set(doorWidth * horizontal, vertical(doorHeight), DOOR_DEPTH_M * horizontal);
door.castShadow = false; door.castShadow = false;
group.add(door); group.add(door);
pickables.push(door); pickables.push(door);
+76 -2
View File
@@ -1,7 +1,12 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { describe, it } from "node:test"; import { describe, it } from "node:test";
import { layoutBuildingGlyph } from "../engine/buildingGlyph.ts"; import * as THREE from "three";
import type { BuildingGlyph } from "../engine/types.ts"; import { createBuildingGlyph, layoutBuildingGlyph } from "../engine/buildingGlyph.ts";
import type { BuildingGlyph, City } from "../engine/types.ts";
import { World } from "../engine/world.ts";
import { setReconcile } from "../cities/reconcile.ts";
setReconcile(false);
function glyph(overrides: Partial<BuildingGlyph> = {}): BuildingGlyph { function glyph(overrides: Partial<BuildingGlyph> = {}): BuildingGlyph {
return { return {
@@ -57,3 +62,72 @@ describe("map building glyph layouts", () => {
); );
}); });
}); });
/** The same square of ground at two scales; see `markerScale.test.ts`. */
function board(latScale: number): City {
return {
id: `glyph-scale-${latScale}`,
name: "Glyph Scale Board",
center: { lat: 37.78, lng: -122.42 },
bounds: { minLat: 37.7, maxLat: 37.86, minLng: -122.5, maxLng: -122.34 },
latScale,
verticalExaggeration: 3.6,
cellLat: 0.01,
cellLng: 0.01,
coastFalloff: 0.02,
landmasses: [
[
[37.71, -122.49],
[37.85, -122.49],
[37.85, -122.35],
[37.71, -122.35],
],
],
parks: [],
inlandWater: [],
districts: [],
landmarks: [],
bridges: [],
roads: [],
chapters: [],
hills: [],
};
}
const HQ: BuildingGlyph = {
kind: "building",
width: 48,
depth: 42,
height: 326,
storeys: 61,
heading: 205,
profile: "tower",
seed: 115,
bodyColor: 0x8799a8,
};
function doorDepthM(latScale: number): number {
const world = new World(board(latScale));
const built = createBuildingGlyph(world, HQ, 0xf5b53f);
let depthM = 0;
built.group.traverse((node) => {
if (!(node instanceof THREE.Mesh) || node.name !== "building entrance") return;
node.updateWorldMatrix(true, false);
depthM = Math.abs(node.scale.z) * world.metresPerUnit;
});
built.dispose();
return depthM;
}
describe("a building glyph is a true-metre object", () => {
it("draws the same door thickness on a 94 m board and a 1,919 m one", () => {
const metro = doorDepthM(1180);
const state = doorDepthM(58);
assert.ok(metro > 1 && metro < 6, `a door ${metro.toFixed(2)} m thick is not a door`);
assert.ok(
Math.abs(metro - state) < 0.05,
`a door is ${metro.toFixed(2)} m thick on a 94 m/unit board and ` +
`${state.toFixed(2)} m on a 1,919 m/unit one`,
);
});
});