Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/src/three/bridges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import * as THREE from 'three'
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'
import { mat } from './typologyMeshes'
import { WATER_LEVEL } from './terrain'

const K = 3.4 // cartoon scale-up
const STEEL = '#ffc21a' // Pittsburgh bridge yellow
Expand Down Expand Up @@ -30,7 +31,7 @@ export function makeBridge(form: string, lengthUnits: number, deckY: number): TH
boxAt(Math.hypot(dx, dy), t, t, (x1 + x2) / 2, (y1 + y2) / 2, z, c, Math.atan2(dy, dx))
}
const S = [-0.9, 0.9]
const WY = 0.3 / K
const WY = WATER_LEVEL / K
boxAt(L, 0.45, 1.8, 0, y, 0, DECK)
for (const s of S) boxAt(L, 0.3, 0.12, 0, y + 0.3, s, STEEL)
const piers = (xs: number[]) => xs.forEach((x) => boxAt(0.8, y + 0.6, 1.4, x, (y - 0.6) / 2, 0, PIER))
Expand Down
12 changes: 8 additions & 4 deletions web/src/three/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { makeBridge } from './bridges'
import { loadTerrain, type Terrain } from './terrain'
import { loadTerrain, WATER_LEVEL, type Terrain } from './terrain'
import { box, house, makeBuilding, mat, mesh, type BuildingForm } from './typologyMeshes'

export interface SceneConfig {
Expand Down Expand Up @@ -315,9 +315,13 @@ export function createEngine(container: HTMLElement, opts: EngineOptions): Engin

function buildCity(t: Terrain): void {
terrain = t
// Water plane at the normal pool; land sits above it.
const water = new THREE.Mesh(new THREE.PlaneGeometry(6000, 6000).rotateX(-Math.PI / 2), new THREE.MeshStandardMaterial({ color: WATER, roughness: 0.28, metalness: 0.1 }))
water.position.y = 0.3
// Water plane at the normal pool; land sits above it. Depth offset pushes the
// water back so the shoreline never z-fights with the banks.
const water = new THREE.Mesh(
new THREE.PlaneGeometry(6000, 6000).rotateX(-Math.PI / 2),
new THREE.MeshStandardMaterial({ color: WATER, roughness: 0.28, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: 2, polygonOffsetUnits: 4 }),
)
water.position.y = WATER_LEVEL
water.receiveShadow = true
city.add(water)
// Ground: vivid inside city limits, pale countryside outside.
Expand Down
12 changes: 9 additions & 3 deletions web/src/three/terrain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ interface Meta {
fine: { extent: number; step: number; n: number }
}

const WATER_H = -0.6
/** Height of the flat water plane. */
export const WATER_LEVEL = 0.3
// Riverbed and lowest land sit well clear of the water plane: at city zoom the
// depth buffer can't separate surfaces a fraction of a unit apart, and flat banks
// near the pool z-fight with the water (rivers flicker as the camera moves).
const WATER_H = WATER_LEVEL - 2.5
const LAND_MIN = WATER_LEVEL + 0.8

function sampler(data: Int16Array, offset: number, extent: number, step: number, n: number, scale: number, water: number) {
const stride = n + 1
const at = (i: number, j: number) => {
const v = data[offset + j * stride + i]
return v === water ? WATER_H : v / scale
return v === water ? WATER_H : Math.max(LAND_MIN, v / scale)
}
const wet = (i: number, j: number) => data[offset + j * stride + i] === water
const idx = (x: number, z: number) => {
Expand Down Expand Up @@ -101,7 +107,7 @@ function ruleTerrain(riverPts: [number, number][][], half: number): Terrain {
const d = dist(x, z)
if (d < 15) return WATER_H
const n = 0.5 + 0.5 * Math.sin(x * 0.011 + 1.3) * Math.cos(z * 0.013 - 0.7) + 0.22 * Math.sin(x * 0.029 + z * 0.023)
return 0.8 + sst(40, 105, d) * sst(80, 190, Math.hypot(x + 15, z)) * (15 + 14 * n)
return LAND_MIN + sst(40, 105, d) * sst(80, 190, Math.hypot(x + 15, z)) * (15 + 14 * n)
}
return {
source: 'rules',
Expand Down
Loading