Log in
README.md 194 lines · 9.2 KB

oakcity

A tilt-shift city builder on a custom wgpu engine. Draw roads as splines, zone land, watch procedurally generated buildings grow along the frontage, and follow individual citizens as they commute, shop and go home.

the starter town

cargo run --release -- --demo

What's in it

Custom engine. No game framework β€” just wgpu, winit and egui. One forward HDR pass with a single-cascade shadow map, a blended water surface, and a tilt-shift post stack. Everything is vertex-coloured; there is not a single texture in the project.

Tilt-shift. Two effects stacked, which is what actually sells the toybox look. A depth-based circle of confusion blurs anything far from the focus distance, and on top of it a photographic tilt band keeps one horizontal strip of the frame sharp and softens everything above and below it regardless of depth. Both feed a two-pass separable blur at half resolution, composited back with a saturation and contrast push and a vignette. The camera runs a 25Β° lens, because the telephoto compression is the other half of the illusion. Every parameter is a slider and a hot-reloadable config value.

Splines everywhere. Roads, avenues, tram lines and railways are all Catmull-Rom splines. Laying one flattens a corridor of terrain under it, generates a ribbon mesh with kerbs, pavements and lane markings, and folds the result into a node/edge graph β€” splitting existing ways at crossings so junctions are real junctions. Pavements and markings are suppressed where another carriageway runs over them.

Generated terrain. Five map presets (assets/config/maps.ron), each a different generator: a river valley with a meandering carved channel, a coastal bay, plains with a lake, a mountain pass with ridged noise either side of a buildable valley, and an archipelago. The water line is chosen as a quantile of the finished heightfield, so water_fraction: 0.34 really does give you a third of a map of water.

Water. A single blended surface whose vertices carry the depth of the bed beneath them, so shallows go turquoise, deep water goes dark and the shoreline gets foam β€” with no second depth pass. Three summed directional waves animate the surface and flatten out as they reach the shore.

Neighbourhoods, not scatter. Growth walks fixed-width plots down both sides of every street and fills them, so buildings line up shoulder to shoulder on a common building line with driveways joining them to the pavement, instead of being sprinkled at random offsets. Each archetype carries a palette of wall and roof colours that the generator picks from per building, which is the difference between a street and a row of identical boxes. Junction approaches get zebra crossings and street trees alternate with lamps down the verge.

Generative buildings. A building spec is a recipe, not a model: a massing (block, L, H, setback tower, shed, basilica, slab, courtyard), a roof (flat, parapet, gable, hip, shed, sawtooth, dome, mansard), a window style, and a list of recognisable features. Combined with a per-building seed, no two fire stations are identical and every one of them still reads as a fire station:

BuildingWhat makes it legible
Fire stationRed brick, three roller doors, hose-drying tower, white trim
Police HQBlue-grey stone, colonnaded entrance, radio mast, roof dish, shield
HospitalWhite H-plan, red cross on the facade, rooftop helipad
SchoolBrick wings around a courtyard, flagpole, playground
ChurchNave and aisles, steeple with a cross
City hallDome, portico, flag
Power plantTwo hyperboloid cooling towers, banded stack
Water towerTank on four legs
StationClock tower, arched train shed, platforms
FactorySawtooth north-light roof, silos, banded chimneys

Windows are dark glass by day and warm light after dark β€” the same emissive vertex flag lights up street lamps.

Citizens. Everyone has a home, possibly a job, four needs (rest, food, fun, money) and a daily routine. They path with A* over the transport graph, walk on the pavement or drive in the right-hand lane, and are cached per origin/ destination pair so thousands of them cost almost nothing. Trams and trains shuttle along their lines. The clock drives the sun, the sky, the window glow and everyone's schedule.

Traffic. Cars are not on rails. Each one watches for the vehicle ahead in its own lane through a spatial hash and eases off rather than driving through it, with real acceleration and braking. Junctions β€” any graph node where three or more edges meet β€” are mutual-exclusion locks: the first car to arrive claims it, everyone else decelerates to a stop line and waits their turn, and claims time out so a stalled vehicle can never deadlock the city. The result is queues on the approach and cars filtering through one at a time.

The developer window

Render tuning, lighting, the map picker, hot-reload status and counters live in a separate OS window (F1, the πŸ›  Dev button, or --debug) rather than on top of the city. It is a second winit window with its own wgpu surface and egui instance on the same device. The game window carries only the HUD a player should see, so shipping is simply a matter of never opening the dev window.

The dev loop

Anything that does not need rustc lives in assets/ and reloads while the game is running:

EditEffect
assets/shaders/*.wgslPipelines rebuild. A syntax error prints the message, keeps the last good pipelines, and shows it in the HUD β€” the game never dies.
assets/config/theme.ronTerrain, road and water colours; the whole tilt-shift rig (including the tilt band) and lighting. Meshes rebuild in place.
assets/config/buildings.ronBuilding archetypes. Add an entry and it appears in the build menu; change a colour or feature and every existing building of that type regenerates.
assets/config/maps.ronMap presets. The current map regenerates.
assets/config/sim.ronNeed drain rates, wages, speeds, tax. Running citizens pick the new numbers up immediately.

The workspace is split into five crates (engine, world, gen, sim, game) so a change to the simulation does not recompile the renderer, and [profile.dev] optimises dependencies once while keeping our own code cheap to build. A touch-and-rebuild of the game crate is a couple of seconds.

cargo run -- --demo                          # starter town, windowed
cargo run -- --map 3 --hour 19               # Sable Pass at dusk
cargo run -- --demo --screenshot shot.png    # render a PNG and exit
cargo test                                   # configs parse, every archetype
                                             # generates, every map is habitable

--screenshot renders entirely offscreen with a fixed timestep, so it works with the window hidden and the same --frames always simulates the same amount of city life. It is the fastest way to check a visual change.

FlagMeaning
--demoSeed a starter town: avenues, a street grid, a tram line, a railway, zoning and the core civic buildings
--debugOpen the developer window at startup
--map <n>Map preset to load, 0-based
--hour <0..24>Time of day
--dist <metres>Camera distance
--screenshot <path>Render a PNG and exit
--frames <n>Frames to simulate before capturing

Controls

InputAction
WASD / arrowsPan (hold shift to move faster)
Middle-drag, Q / EOrbit
ScrollZoom
Left clickUse the current tool
Right-dragPan
Enter / right clickFinish the way you are drawing
EscCancel
SpacePause
1–4Street, avenue, tram, railway
B / IBulldoze / inspect
F1Open/close the developer window

Draw a road by clicking control points and pressing Enter. Paint a zone next to it and buildings will start appearing along the frontage; the mix and density depend on the zone and how big the city has grown. Place a fire station and watch the coverage bar fill.

Layout

crates/engine   wgpu device, camera, meshes, shadow + HDR + tilt-shift passes,
                asset paths, the file watcher
crates/world    heightfield, map generators, splines, the transport graph,
                ribbon meshing, zoning
crates/gen      building specs and the procedural mesh generator
crates/sim      citizens, needs, A* and the route cache, road traffic and
                junction locking, transit, the clock
crates/game     tools, growth, the egui HUD, the frame loop
assets/shaders  WGSL, stitched together with a tiny `#include` resolver
assets/config   RON: theme, maps, buildings, simulation tuning

Known limits

  • Roads cannot cross deep water; there are no bridges or tunnels yet.
  • Junctions are first-come-first-served locks rather than modelled priority rules or signals, so there is no concept of a main road having right of way.
  • One shadow cascade, sized to the camera's focus, so distant shadows fade out.
  • Growth picks lots along road frontage at random rather than filling blocks in a considered order.

Version control

This repository is tracked with Oak, not git.

oak status
oak diff --print
oak log --oneline