Metadata-Version: 2.4
Name: pyworldgen
Version: 0.1.0
Summary: Deterministic procedural world generation: biomes, noise, roads, and smooth multi-biome composition.
Author-email: YOUR NAME <you@example.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/YOUR-GH-USER/pyworldgen
Project-URL: Repository, https://github.com/YOUR-GH-USER/pyworldgen
Project-URL: Issues, https://github.com/YOUR-GH-USER/pyworldgen/issues
Project-URL: Changelog, https://github.com/YOUR-GH-USER/pyworldgen/blob/main/CHANGELOG.md
Keywords: procedural-generation,worldgen,noise,perlin,simplex,terrain,voxel,biomes
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Topic :: Games/Entertainment
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.23
Requires-Dist: scipy>=1.9
Requires-Dist: matplotlib>=3.6
Requires-Dist: pillow>=9.0
Requires-Dist: imageio>=2.0
Provides-Extra: render
Requires-Dist: pyvista>=0.43; extra == "render"
Provides-Extra: fps
Requires-Dist: fpsample>=1.0; extra == "fps"
Provides-Extra: viz
Requires-Dist: scikit-image>=0.20; extra == "viz"
Provides-Extra: geometry
Requires-Dist: shapely>=2; extra == "geometry"
Requires-Dist: trimesh>=4; extra == "geometry"
Requires-Dist: mapbox-earcut>=1; extra == "geometry"
Provides-Extra: all
Requires-Dist: pyworldgen[fps,geometry,render,viz]; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: opensimplex>=0.4; extra == "dev"

# pyworldgen

A unified, deterministic procedural biome-generation library. It brings three
previously separate code bases under one roof and makes them share a single
foundation:

- a chunked voxel **world** with a generation-pass framework (formerly `procgen3d`);
- pure-function **biome** generators — caves, forests, landscapes (formerly `biomegen_lab`);
- the streamed implicit **cave** with its cross-language hash.


## The idea that unifies everything

The three code bases looked different on the surface — one was an array-backed
chunk engine, the others were pure mathematical fields — but they all obey the
same principle: **a compact deterministic source of truth is sampled into
arrays, which are exported as views.** A seed plus parameters *is* the world;
voxel grids, meshes, point clouds and saved chunks are all regenerable views of
that source. `pyworldgen` makes that principle explicit and builds three shared
mechanisms on top of it.

### 1. One canonical cross-language hash

Every generator's randomness now flows through a single module,
`pyworldgen.core.hash`. It is an integer multiply-and-mix hash (xxHash-style
constants, `Math.imul` semantics) whose only floating-point work happens *after*
hashing, so a world is reproducible bit-for-bit from a seed on any backend —
Python, JavaScript, GLSL, C++. The shader idiom `frac(sin(x)*43758.5453)` is
deliberately avoided because `sin` disagrees on its last bits across platforms
and the hash amplifies that into a different world.

This replaced **three duplicated hash implementations**. In doing so it fixed a
real latent bug: the original `cavegen` and `landscapegen` copies performed the
32-bit multiply in signed `int64` and relied on silent overflow wraparound,
while `forestgen` used `uint64`. The three did not always agree. The unified
module standardises on the correct `uint64` path (masked to 32 bits), and a test
pins the output against an independent pure-Python reference of the same
construction so the NumPy path can never drift from the cross-language contract.

### 2. The field abstraction — where the two frameworks meet

`pyworldgen.fields` is the conceptual bridge. A biome can be expressed as a pure
function `is_solid(x, y, z)` with no stored grid (this is exactly what the
implicit cave is). The same function can also *fill chunks* in the array-backed
world:

- `voxelize(field, ...)` samples a field into a dense occupancy array;
- `fill_chunk_from_field(field, chunk, ...)` samples it straight into a chunk;
- `FieldSolidPass` wraps a field as a generation pass.

So a streamed implicit cave drops into the same scheduler as hand-authored
terrain, and can be meshed, saved and streamed like any other chunk. The cave's
spec↔world coordinate swap lives inside its own `is_solid`, so the generic fill
logic stays agnostic.

### 3. Compact source, heavy views

Seeds, parameters, tree graphs and river polylines are the source of truth.
Meshes and OBJ files are produced on demand from them. For the pure fields you
often don't need to store geometry at all — only measured or hand-edited data
needs persisting.

## Layer map

| Layer | Lines | What's in it |
|---|---|---|
| `core` | 527 | canonical hash + noise, RNG seeding, vec/AABB/ray, chunk coordinates |
| `spatial` | 833 | SpatialHashGrid, BVH, voxel DDA, Quadtree, Octree, Graph+Dijkstra, **KDTree**, **RTree**, **farthest point sampling** |
| `fields` | 71 | `SolidField3D` protocol, voxelize, chunk fill |
| `voxel` | 99 | block registry, sunlight pass |
| `world` | 315 | block storage, chunk, chunk map, deferred edits, world, queries |
| `mesh` | 392 | naive + greedy chunk meshers, **dense-volume mesher** |
| `generation` | 294 | pass base, dependency-checked scheduler, terrain/biome/cave/feature/structure passes |
| `runtime` | 95 | chunk streaming, grid A* navigation |
| `biomes` | 1456 | the signature generators — cave, forest, landscape, **flat terrain** |
| `roads` | 723 | **weighted anisotropic least-cost roads (Galin et al.): path segment masks, line-integral cost, bridges, curvature, terrain grading** |
| `compose` | 356 | **multi-biome composition: partition-of-unity blending, smooth transitions, integrated forest + roads** |
| `pointcloud` | 164 | **mesh surface sampling, FPS downsampling, point→voxel rasterisation** |
| `rendering` | 762 | **numpy pinhole camera, orbit/keyframe paths, software renderer, PyVista/matplotlib backends, GIF/MP4 export** |
| `io` | 445 | chunk (de)serialization, region store, OBJ / **MagicaVoxel .vox** / **PLY / XYZ** export |

The two spatial structures the reference "core structures" guide lists but
neither original project had — a **KDTree** (nearest-neighbour over static
points) and an STR-bulk-loaded **RTree** (map/polygon window queries) — were
added and tested against brute force.

## The three biomes and their invariants

Each generator preserves the defining property of its original, and each has a
`validate_*` function and tests that assert the invariant directly:

- **Implicit cave** (`biomes.cave`) — a pure positional field with a
  *guaranteed always-open* wandering main corridor, unioned with worm tunnels
  and caverns, roughened walls and optional speleothems. The corridor's openness
  is strictly positive along its axis, so the whole streamed length stays
  navigable. Bridges to the world via `ImplicitCave`, a valid `SolidField3D`.
- **Forest** (`biomes.forest`) — a Voronoi canopy graph. Every canopy cell has
  exactly one owner tree, so crowns never overlap *by construction*; each tree is
  a recursive root→trunk→branch→terminal graph with bottom-up branch radii
  (`r_parent^γ = Σ r_child^γ`) that stays inside its owned cell. An optional
  `canopy_diffusion` (default `0.0`, i.e. hard power-diagram edges) domain-warps
  the ownership so neighbouring crowns interlock with organic, diffuse boundaries
  — still exactly one owner per cell, and `0.0` reproduces the original output.
- **Landscape** (`biomes.landscape`) — a river-valley plain. Every river bed is
  monotonically downhill from spring to mouth, enforced with
  `np.minimum.accumulate` rather than hoped for; tributaries join their main
  river, valleys and channels are carved, ponds fill low ground, and materials
  are classified water→upland.
- **Flat terrain** (`biomes.flat`) — the neutral default: a heightfield that is
  perfectly flat unless you add tilt or undulation. It exposes the same
  `height`/`x`/`y`/`resolution` interface as the landscape, so roads, the terrain
  mesher, and biome composition all work on it unchanged.

## Roads

Roads (`pyworldgen.roads`) are laid over any terrain (`FlatTerrain`,
`LandscapeWorld`, or a raw height grid) as **weighted anisotropic least-cost
paths**, following Galin et al., *Procedural Generation of Roads* (Eurographics
2010). The routing of a single connection is a faithful port of the paper; laying
a *network* of hubs (minimum spanning tree plus a few loop edges) is the
pyworldgen extension on top of it.

The paper's two central ideas are both implemented:

- **Path segment masks `M_k`** (`path_segment_mask`) — instead of 4/8-connectivity
  (which limits road directions to multiples of 45°, the "limit-on-direction
  problem"), each grid point connects to every `(i, j) ∈ [-k, k]²` with
  `gcd(|i|, |j|) = 1`. The mask sizes reproduce the paper's Table 1 exactly
  (`k=1`→8 directions, `k=3`→32, `k=5`→80), and the tests assert it. Larger `k`
  gives finer angle resolution and straighter roads; `mask_radius` (default 3)
  selects it.
- **Line-integral segment cost** with **transfer functions** — because a mask
  segment spans several cells, its cost is the integral of a local cost sampled
  along it. Each characteristic (slope *along the travel direction*, water depth)
  passes through a transfer function with a threshold `κ₀` beyond which the cost
  is `∞` (a forbidden region): grades steeper than `max_grade` can't be climbed,
  water deeper than `max_water_depth` can't be forded.

On top of that: **bridges** — deep water is crossed at a `bridge_cost` surcharge
rather than forbidden (a long mask segment over a river *is* a bridge), and the
ribbon geometry lifts the bridge deck onto the water surface; **curvature** —
setting `curvature_weight > 0` augments the search state with the incoming
direction (the paper's oriented Ω×[0,2π] grid) so sharp turns are penalised
(opt-in, and slower, as in the paper's Table 3); **corridor reuse** — later roads
are discounted along earlier ones so the network bundles; and **excavation /
embankment** — `carve_road_terrain` cuts/fills the ground to the road within the
asphalt half-width and blends back to the terrain over a shoulder region (§6.2),
leaving bridge spans untouched.

```python
from pyworldgen.biomes.landscape import generate_landscape
from pyworldgen.roads import generate_road_network, road_ribbon_mesh, carve_road_terrain

land  = generate_landscape(seed="rhine")                 # rivers to bridge
roads = generate_road_network(land, seed="roads", num_hubs=11, mask_radius=4)

roads.road_mask            # (ny, nx) boolean road footprint
roads.bridge_mask()        # road cells carried over water on a bridge
verts, faces = road_ribbon_mesh(roads)      # deck lifted over water, ready for OBJ / render
graded = carve_road_terrain(roads)          # terrain with cuts, fills and shoulders
```

`bf.generate("roads", seed=..., num_hubs=...)` is a convenience that lays roads
over a flat base. The result is a `RoadNetwork`: hubs, smoothed centreline
segments, the rasterised mask, the water it bridges, and helpers for 3D polylines
and ribbon meshes. Routed roads are much straighter than 8-connectivity (checked
against the mask table) and follow the terrain at a far gentler grade than a
straight line — both asserted in the tests.

Honest gaps versus the paper: smoothing uses Chaikin corner-cutting rather than
clothoid splines (§6.1), tunnels are not implemented (only bridges), and the
stochastic bridge/tunnel sampling of §5.4 is not used — all noted as future work.

## Composition: many biomes in one world

`pyworldgen.compose` blends several biomes into a single coherent world with
**smooth transitions**, then lays the forest and roads over the *same* composed
terrain. The algorithm is a partition-of-unity blend:

1. **Region seeds + classification.** Scatter region centres (seeded dart-throw)
   and classify each into a biome by its *niche* in a low-frequency
   (elevation, moisture) control field — a Whittaker-style assignment, so
   mountains cluster on high ground and water in the basins.
2. **Smooth weights.** Each biome type is a *global* terrain profile `h_T(x, y)`.
   For every cell, a domain-warped Gaussian gives a weight to each seed; weights
   are summed per biome and normalised so they **sum to 1 everywhere** (a
   partition of unity). This is what makes the result smooth — every blended
   field is a convex combination of continuous fields, so there are no seams.
3. **Blend.** `height = Σ_T W_T · h_T`, and likewise water depth, a tree-density
   field, and biome colour. Transitions read as foothills between mountains and
   plains, and shorelines around lakes.
4. **Integrated overlays.** Forest is scattered on the composed terrain, gated by
   the blended density field (so it fades out at biome edges) and re-draped onto
   the composed height; roads are routed over the composed `(height, water_depth)`
   — which is exactly the interface the road generator already consumes, so they
   bridge the composed lakes and avoid the composed mountains with no special
   casing.

```python
from pyworldgen.compose import compose_world

world = compose_world(seed="atlas", num_regions=16, num_hubs=12)
world.height, world.water_depth     # blended terrain (same interface as a biome)
world.weights["forest"]             # per-biome partition-of-unity weight field
world.color, world.dominant         # blended biome colour / dominant-biome map
world.trees, world.roads            # forest + road network on the composed terrain
```

`bf.generate("composite", seed=...)` returns a `CompositeWorld`, which exposes the
`height`/`water_depth`/`x`/`y`/`resolution` terrain interface — so the terrain and
water meshers and the road generator all consume it directly. The built-in
palette is water, plains, forest, hills and mountains; a custom `palette` of
`BiomeType`s (each a terrain profile, colour, tree density and niche) can be
passed in. The weights are verified in the tests to be a true partition of unity
with no hard seams, trees to sit exactly on the composed terrain and never in
water, and mountains to dominate higher ground than water.

The current palette models water as lakes/wetlands (basins filled to a level);
per-region river hydrology from the landscape generator can be slotted in through
the same `(height, water_depth)` interface as a future extension.

## Meshes, voxels and point clouds

Every biome can be exported in all three geometry representations, because each
is just another *view* of the compact source. The three are interchangeable:

- **Voxels.** The cave is native voxels (a `DenseVoxelVolume`); the chunked
  world is voxels too. Export either to **MagicaVoxel `.vox`** (readable by
  MagicaVoxel, Blender, Godot, three.js) or a portable `.npz`.
- **Meshes.** A `DenseVoxelVolume` meshes with a vectorised naive culled
  mesher (`volume_to_mesh`); the forest and landscape mesh from their graph /
  height field. All go to Wavefront `.obj`.
- **Point clouds.** `sample_mesh_surface` scatters points over any mesh with
  **area-weighted** density (uniform regardless of tessellation), each carrying
  its triangle's normal. The cloud thins to an even, blue-noise-like subset with
  **farthest point sampling**, then exports to `.ply` or `.xyz`.

So the pipeline **biome → mesh → point cloud → FPS-downsampled cloud** works for
any biome:

```python
from pyworldgen.mesh import volume_to_mesh
from pyworldgen.pointcloud import sample_mesh_surface
from pyworldgen.io import volume_to_vox, mesh_to_obj, write_ply

cave = bf.generate("implicit_cave", seed="lux", length=90.0)

volume_to_vox("cave.vox", cave)                 # voxels
mesh = volume_to_mesh(cave)
mesh_to_obj("cave.obj", mesh)                    # mesh

cloud = sample_mesh_surface(mesh, 40_000, seed=1)  # dense surface cloud
even  = cloud.farthest_point_downsample(4_096)     # evenly spread subset
write_ply("cave_points.ply", even)               # point cloud (with normals)
```

Farthest point sampling ships with both a self-contained, deterministic NumPy
backend built on the same layer as the KD-tree and the
[`fpsample`](https://pypi.org/project/fpsample/) Rust QuickFPS/NPDU backend for
very large clouds. The NumPy backend is the default. The reverse direction,
point cloud → voxels, is `points_to_occupancy`, so a mesh-native biome (forest,
landscape) can also be voxelised via a surface sampling.

## Rendering and fly-throughs

Any geometry view can be rendered to images and animations. A render is just
another view of the source, so the same biome flows all the way through:
source → mesh / point cloud → camera → frames.

- **Camera.** A numpy pinhole camera (`Camera.look_at`, `project`, `lerp`) with
  intrinsics from field of view — the standard self-driving pinhole model,
  without a torch/Lie-group dependency, so interpolating a path is a plain lerp.
- **Paths.** `orbit` / `turntable` spin around a point; `flythrough` interpolates
  a smooth path through `Keyframe` waypoints; `frame_scene` and `orbit_scene`
  auto-fit the camera to a scene's bounding box.
- **Renderers.** The default `SoftwareRenderer` is pure numpy — a z-buffered
  point splatter (depth / normal / colour shading) and a flat-shaded triangle
  rasteriser. `PyVistaRenderer` provides solid VTK renders, and
  `MatplotlibRenderer` provides projected scatter renders.
- **Output.** `save_png`, `save_gif` (Pillow), `save_mp4` (imageio), plus a
  dependency-free PPM writer.

A turntable fly-through of a biome is one call:

```python
import pyworldgen as bf
import pyworldgen.rendering as rr
from pyworldgen.mesh import volume_to_mesh
from pyworldgen.pointcloud import sample_mesh_surface

cave  = bf.generate("implicit_cave", seed="lux", length=70.0)
cloud = sample_mesh_surface(volume_to_mesh(cave), 60_000, seed=1)

frames = rr.render_turntable(cloud, n_frames=48, elevation=18)  # fly-around
rr.save_gif(frames, "cave_orbit.gif", fps=20)

# solid mesh still from a chosen angle
still = rr.SoftwareRenderer().render(volume_to_mesh(cave),
                                     rr.frame_scene(cave, azimuth=40, elevation=30))
rr.save_png(still, "cave.png")
```

Point-cloud fly-throughs are fast (tens of milliseconds per frame); the triangle
rasteriser is best for solid stills. For very large meshes and extra polish,
pass `renderer=rr.PyVistaRenderer()`.

## Quick start

```python
import pyworldgen as bf

bf.list_generators()          # ['composite', 'flat', 'forest', 'implicit_cave', 'landscape', 'roads']

forest = bf.generate("forest",        seed="grenoble",   target_tree_count=60)
cave   = bf.generate("implicit_cave", seed="luxembourg", length=90.0)
land   = bf.generate("landscape",     seed="rhine")
```

Drive the chunked world with a pipeline of passes:

```python
from pyworldgen.world import World
from pyworldgen.core.coords import ChunkCoord
from pyworldgen.generation import (
    GenerationScheduler, SimpleTerrainPass, SimpleBiomePass,
    SimpleCavePass, SimpleTreeFeaturePass, ApplyDeferredEditsPass,
)
from pyworldgen.voxel.lighting import SimpleSunlightPass

world = World.create("demo")
scheduler = GenerationScheduler([
    SimpleTerrainPass(), SimpleBiomePass(), SimpleCavePass(),
    SimpleTreeFeaturePass(), ApplyDeferredEditsPass(), SimpleSunlightPass(),
])
chunk = world.chunks.get_or_create(ChunkCoord(0, 0, 0))
scheduler.generate_chunk(world, chunk)
```

Stream a whole implicit cave into chunks through the field bridge:

```python
from pyworldgen.biomes.cave import ImplicitCave
from pyworldgen.generation import FieldSolidPass, GenerationScheduler

cave = ImplicitCave.from_seed("luxembourg")
scheduler = GenerationScheduler([FieldSolidPass(cave, name="cave")])
```


## Visualization scripts

Camera-based stills, multi-view sheets, turntables, fly-throughs, biome debug maps, and a noise gallery are available in `scripts/`.

```bash
python scripts/visualize_all.py --quick --out sample_visualizations
python scripts/visualize_all.py --quick --animations --out sample_visualizations
python scripts/render_biome.py composite --seed atlas --quick
```

## Install and dependencies

```bash
pip install -e .            # installs runtime dependencies
pip install -e ".[dev]"     # adds pytest
```

Runtime dependencies include `numpy`, `scipy`, `fpsample`, `matplotlib`,
`pillow`, `imageio`, and `pyvista`. `scikit-image` (`[viz]`) and
`shapely`/`trimesh`/`mapbox-earcut` (`[geometry]`) remain extras for marching
cubes and structure-generation extension points.

## Testing


Coverage spans the hash's cross-language bit-contract and the absence of integer
overflow, the spatial structures (KDTree/RTree checked against brute force),
block storage and world queries, both meshers plus the dense-volume mesher, the
field↔chunk bridge, the generation scheduler's dependency validation and
reproducibility, all three biome invariants, serialization round-trips, grid A*,
the geometry exporters, and the rendering layer — farthest point sampling is
checked for exact behaviour on a 1-D line and for spreading points better than a
random subset; surface sampling for lying on the mesh and for area-weighting; the
`.vox`, `.ply` and `.xyz` writers by parsing their output back; the camera's
projection axes, orbit radius, fly-through endpoints, the renderer's z-buffer
(nearest wins), and PNG/GIF/PPM output; and the roads layer — the path segment
mask sizes against the paper's Table 1, staircase removal (a `(3,1)` run with
zero direction changes), least-cost routing that detours around a ridge and
follows terrain at a gentler grade than a straight line, bridges that cross deep
water only when allowed, curvature that reduces turns, terrain grading that
touches ground near roads but not far from them, network connectivity across all
hubs, endpoint pinning, and determinism, plus the flat terrain being flat by
default; and the composition layer — weights that form a true partition of unity
with no hard colour seams, coherent niche classification (mountains above water),
trees draped exactly on the composed terrain and never in water, roads consuming
the composed terrain interface, and determinism.

## What was ported compactly (transparency)

To keep the merge coherent and tested, some elaborate paths from the originals
were folded down rather than dropped:

- The forest's per-species table is folded into a single parameter set;
  multi-species is a documented extension point (per-site branch depth/radii).
- The landscape's multi-layer hydrology solver is expressed as a single carve
  pass driven by the drainage graph; catchment/erosion iteration is an
  extension point.
- Marching-cubes cave meshing and the CLI from the originals are kept out of the
  tested core; geometry output is covered by the OBJ, MagicaVoxel `.vox` and
  PLY/XYZ exporters and a vectorised naive voxel mesher (rather than marching
  cubes), and the rendering layer ships a self-contained numpy renderer plus
  PyVista/matplotlib backends.
- Legacy grid/morphology cave variants (which needed `scipy`) are omitted in
  favour of the faithful implicit field.

The road/building/story structure generators from the PCG reference are not
implemented here; the graph and spatial layers they would build on are in place.
