McRogueFace/docs/cookbook/procgen/31_advanced_island.py
2026-01-13 19:42:37 -05:00

140 lines
4.8 KiB
Python

"""Advanced: Island Terrain Generation
Combines: Noise (base terrain) + Voronoi (biomes) + Hills + Erosion + Bezier (rivers)
Creates a tropical island with varied biomes and water features.
"""
import mcrfpy
from mcrfpy import automation
GRID_WIDTH, GRID_HEIGHT = 64, 48
CELL_SIZE = 16
def biome_color(elevation, moisture):
"""Determine color based on elevation and moisture."""
if elevation < 0.25:
# Water
t = elevation / 0.25
return mcrfpy.Color(int(30 + t * 30), int(80 + t * 40), int(160 + t * 40))
elif elevation < 0.32:
# Beach
return mcrfpy.Color(220, 200, 150)
elif elevation < 0.5:
# Lowland - varies by moisture
if moisture < 0.3:
return mcrfpy.Color(180, 170, 110) # Desert/savanna
elif moisture < 0.6:
return mcrfpy.Color(80, 140, 60) # Grassland
else:
return mcrfpy.Color(40, 100, 50) # Rainforest
elif elevation < 0.7:
# Highland
if moisture < 0.4:
return mcrfpy.Color(100, 90, 70) # Dry hills
else:
return mcrfpy.Color(50, 90, 45) # Forest
elif elevation < 0.85:
# Mountain
return mcrfpy.Color(110, 105, 100)
else:
# Peak
return mcrfpy.Color(220, 225, 230)
def run_demo(runtime):
# Step 1: Create base elevation using noise
noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=42)
elevation = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
elevation.add_noise(noise, world_size=(12, 10), mode='fbm', octaves=5)
elevation.normalize(0.0, 1.0)
# Step 2: Create island shape using radial falloff
cx, cy = GRID_WIDTH / 2, GRID_HEIGHT / 2
max_dist = min(cx, cy) * 0.85
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
dist = ((x - cx) ** 2 + (y - cy) ** 2) ** 0.5
falloff = max(0, 1 - (dist / max_dist) ** 1.5)
current = elevation.get((x, y))
elevation.fill(current * falloff, pos=(x, y), size=(1, 1))
# Step 3: Add central mountain range
elevation.add_hill((GRID_WIDTH // 2, GRID_HEIGHT // 2), 15, 0.5)
elevation.add_hill((GRID_WIDTH // 2 - 8, GRID_HEIGHT // 2 + 3), 8, 0.3)
elevation.add_hill((GRID_WIDTH // 2 + 10, GRID_HEIGHT // 2 - 5), 6, 0.25)
# Step 4: Create moisture map using different noise
moisture_noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=123)
moisture = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
moisture.add_noise(moisture_noise, world_size=(8, 8), mode='fbm', octaves=3)
moisture.normalize(0.0, 1.0)
# Step 5: Add voronoi for biome boundaries
biome_regions = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
biome_regions.add_voronoi(num_points=8, coefficients=(0.5, -0.3), seed=77)
biome_regions.normalize(0.0, 1.0)
# Blend voronoi into moisture
moisture.lerp(biome_regions, 0.4)
# Step 6: Apply erosion to elevation
elevation.rain_erosion(drops=2000, erosion=0.08, sedimentation=0.04, seed=42)
elevation.normalize(0.0, 1.0)
# Step 7: Carve rivers from mountains to sea
# Main river
elevation.dig_bezier(
points=((GRID_WIDTH // 2, GRID_HEIGHT // 2 - 5),
(GRID_WIDTH // 2 - 10, GRID_HEIGHT // 2),
(GRID_WIDTH // 4, GRID_HEIGHT // 2 + 5),
(5, GRID_HEIGHT // 2 + 8)),
start_radius=0.5, end_radius=2,
start_height=0.3, end_height=0.15
)
# Secondary river
elevation.dig_bezier(
points=((GRID_WIDTH // 2 + 5, GRID_HEIGHT // 2),
(GRID_WIDTH // 2 + 15, GRID_HEIGHT // 3),
(GRID_WIDTH - 15, GRID_HEIGHT // 4),
(GRID_WIDTH - 5, GRID_HEIGHT // 4 + 3)),
start_radius=0.5, end_radius=1.5,
start_height=0.32, end_height=0.18
)
# Step 8: Render
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
elev = elevation.get((x, y))
moist = moisture.get((x, y))
color_layer.set(((x, y)), biome_color(elev, moist))
# Title
title = mcrfpy.Caption(text="Island Terrain: Noise + Voronoi + Hills + Erosion + Rivers", pos=(10, 10))
title.fill_color = mcrfpy.Color(255, 255, 255)
title.outline = 1
title.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(title)
# Setup
scene = mcrfpy.Scene("island")
grid = mcrfpy.Grid(
grid_size=(GRID_WIDTH, GRID_HEIGHT),
pos=(0, 0),
size=(GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE),
layers={}
)
grid.fill_color = mcrfpy.Color(0, 0, 0)
color_layer = grid.add_layer("color", z_index=-1)
scene.children.append(grid)
scene.activate()
# Run the demo
run_demo(0)
# Take screenshot
automation.screenshot("procgen_31_advanced_island.png")
print("Screenshot saved: procgen_31_advanced_island.png")