1
0

California gets roads, traffic, and a car to follow

This commit is contained in:
2026-08-11 18:24:53 -07:00
parent 9c9e78f6f9
commit fe58290728
43 changed files with 5593 additions and 68 deletions
+61 -6
View File
@@ -73,6 +73,20 @@ interface Box {
commercial: number;
}
/**
* A named building's claim on the anonymous city scatter, in scene units.
*
* Circles are deliberately conservative. Anonymous buildings rotate with
* their districts and named glyphs rotate with their streets; a circle is the
* one cheap overlap test that cannot leave a corner poking through, and there
* are only a handful of reservations to test.
*/
export interface BuildingReservation {
x: number;
z: number;
radius: number;
}
function polygonBounds(poly: [number, number][]) {
let minLat = Infinity;
let maxLat = -Infinity;
@@ -87,7 +101,10 @@ function polygonBounds(poly: [number, number][]) {
return { minLat, maxLat, minLng, maxLng };
}
export function createBlocks(world: World): THREE.InstancedMesh {
export function createBlocks(
world: World,
reservations: readonly BuildingReservation[] = [],
): THREE.InstancedMesh {
const boxes: Box[] = [];
let seedBase = 1337;
@@ -165,15 +182,40 @@ export function createBlocks(world: World): THREE.InstancedMesh {
// towers assemble their sites, and Salesforce Tower is about 5:1.
const fill = isTower ? 1.5 + rand() * 0.7 : 0.78 + rand() * 0.18;
const width = LOT * fill;
const depth = LOT * fill * (0.85 + rand() * 0.3);
const rotation = angle + (rand() - 0.5) * 0.03;
const color = new THREE.Color(
palette[Math.floor(rand() * palette.length)] ?? 0xd9d3c6,
);
/**
* Keep a named building legible instead of drawing it inside a random
* one at the same address.
*
* Every random property is drawn before this test. The sequence is
* load-bearing: skipping those calls for one reserved lot would
* reshuffle every anonymous building after it and turn a local change
* into a whole new skyline.
*/
const radius = Math.hypot(width, depth) / 2;
if (
reservations.some(
(reserved) => Math.hypot(x - reserved.x, z - reserved.z) < radius + reserved.radius,
)
) {
continue;
}
boxes.push({
x,
z,
y: world.groundAt(lat, lng),
w: LOT * fill,
d: LOT * fill * (0.85 + rand() * 0.3),
w: width,
d: depth,
h: world.metres(heightM),
rot: angle + (rand() - 0.5) * 0.03,
color: new THREE.Color(palette[Math.floor(rand() * palette.length)] ?? 0xd9d3c6),
rot: rotation,
color,
// A tower is an office whatever district it landed in.
commercial: Math.min(1, commercial + (isTower ? 0.4 : 0)),
});
@@ -230,12 +272,25 @@ export function createBlocks(world: World): THREE.InstancedMesh {
* specific silhouettes — a pyramid at Montgomery, a white finger on Telegraph
* Hill, the red tripod on the ridge — and a box would not do.
*/
export function createLandmarks(world: World): THREE.Group {
export function createLandmarks(
world: World,
reservations: readonly BuildingReservation[] = [],
): THREE.Group {
const group = new THREE.Group();
group.name = "landmarks";
for (const lm of world.city.landmarks) {
const [x, z] = world.project(lm.lat, lm.lng);
// A richer stable glyph at this address supersedes the coarse landmark
// primitive. Drawing both would hide the glyph inside the old mesh and
// leave two different sources claiming the same real building.
if (
reservations.some(
(reserved) => Math.hypot(x - reserved.x, z - reserved.z) < reserved.radius,
)
) {
continue;
}
const base = world.groundAt(lm.lat, lm.lng);
const h = world.metres(lm.height);
const w = lm.footprint * world.lngScale * 2;