diff --git a/src/engine/buildingGlyph.ts b/src/engine/buildingGlyph.ts index 8250575..7f268a1 100644 --- a/src/engine/buildingGlyph.ts +++ b/src/engine/buildingGlyph.ts @@ -49,6 +49,18 @@ interface WindowPlacement { } 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_LIT = new THREE.Color(0xd7c68d); @@ -203,7 +215,7 @@ export function createBuildingGlyph( windows.forEach((window, i) => { position.set(window.x, window.y, window.z); 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); windowMesh.setMatrixAt(i, matrix); // A few warm panes stop the repeated grid reading as graph paper. The @@ -248,7 +260,7 @@ export function createBuildingGlyph( vertical(first.base), (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; group.add(door); pickables.push(door); diff --git a/src/test/buildingGlyph.test.ts b/src/test/buildingGlyph.test.ts index b7baa88..fd80f3a 100644 --- a/src/test/buildingGlyph.test.ts +++ b/src/test/buildingGlyph.test.ts @@ -1,7 +1,12 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { layoutBuildingGlyph } from "../engine/buildingGlyph.ts"; -import type { BuildingGlyph } from "../engine/types.ts"; +import * as THREE from "three"; +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 { 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`, + ); + }); +});