187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
|
|
"""Advanced: Volcanic Crater Region
|
||
|
|
|
||
|
|
Combines: Hills (mountains) + dig_hill (craters) + Voronoi (lava flows) + Erosion + Noise
|
||
|
|
Creates a volcanic landscape with active lava, ash fields, and rocky terrain.
|
||
|
|
"""
|
||
|
|
import mcrfpy
|
||
|
|
from mcrfpy import automation
|
||
|
|
|
||
|
|
GRID_WIDTH, GRID_HEIGHT = 64, 48
|
||
|
|
CELL_SIZE = 16
|
||
|
|
|
||
|
|
def volcanic_color(elevation, lava_intensity, ash_level):
|
||
|
|
"""Color based on elevation, lava presence, and ash coverage."""
|
||
|
|
# Lava overrides everything
|
||
|
|
if lava_intensity > 0.6:
|
||
|
|
t = (lava_intensity - 0.6) / 0.4
|
||
|
|
return mcrfpy.Color(
|
||
|
|
int(200 + t * 55),
|
||
|
|
int(80 + t * 80),
|
||
|
|
int(20 + t * 30)
|
||
|
|
)
|
||
|
|
elif lava_intensity > 0.4:
|
||
|
|
# Cooling lava
|
||
|
|
t = (lava_intensity - 0.4) / 0.2
|
||
|
|
return mcrfpy.Color(
|
||
|
|
int(80 + t * 120),
|
||
|
|
int(30 + t * 50),
|
||
|
|
int(20)
|
||
|
|
)
|
||
|
|
|
||
|
|
# Check for crater interior (very low elevation)
|
||
|
|
if elevation < 0.15:
|
||
|
|
t = elevation / 0.15
|
||
|
|
return mcrfpy.Color(int(40 + t * 30), int(20 + t * 20), int(10 + t * 15))
|
||
|
|
|
||
|
|
# Ash coverage
|
||
|
|
if ash_level > 0.6:
|
||
|
|
t = (ash_level - 0.6) / 0.4
|
||
|
|
base = int(60 + t * 40)
|
||
|
|
return mcrfpy.Color(base, base - 5, base - 10)
|
||
|
|
|
||
|
|
# Normal terrain by elevation
|
||
|
|
if elevation < 0.3:
|
||
|
|
# Volcanic plain
|
||
|
|
t = (elevation - 0.15) / 0.15
|
||
|
|
return mcrfpy.Color(int(50 + t * 30), int(40 + t * 25), int(35 + t * 20))
|
||
|
|
elif elevation < 0.5:
|
||
|
|
# Rocky slopes
|
||
|
|
t = (elevation - 0.3) / 0.2
|
||
|
|
return mcrfpy.Color(int(70 + t * 20), int(60 + t * 15), int(50 + t * 15))
|
||
|
|
elif elevation < 0.7:
|
||
|
|
# Mountain sides
|
||
|
|
t = (elevation - 0.5) / 0.2
|
||
|
|
return mcrfpy.Color(int(85 + t * 25), int(75 + t * 20), int(65 + t * 20))
|
||
|
|
elif elevation < 0.85:
|
||
|
|
# High slopes
|
||
|
|
t = (elevation - 0.7) / 0.15
|
||
|
|
return mcrfpy.Color(int(100 + t * 30), int(90 + t * 25), int(80 + t * 25))
|
||
|
|
else:
|
||
|
|
# Peaks
|
||
|
|
t = (elevation - 0.85) / 0.15
|
||
|
|
return mcrfpy.Color(int(130 + t * 50), int(120 + t * 50), int(115 + t * 50))
|
||
|
|
|
||
|
|
def run_demo(runtime):
|
||
|
|
# Step 1: Create base terrain with noise
|
||
|
|
noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=42)
|
||
|
|
|
||
|
|
terrain = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.3)
|
||
|
|
terrain.add_noise(noise, world_size=(12, 10), mode='fbm', octaves=4, scale=0.2)
|
||
|
|
|
||
|
|
# Step 2: Add volcanic mountains
|
||
|
|
# Main volcano
|
||
|
|
terrain.add_hill((GRID_WIDTH // 2, GRID_HEIGHT // 2), 20, 0.7)
|
||
|
|
terrain.add_hill((GRID_WIDTH // 2, GRID_HEIGHT // 2), 12, 0.3) # Steep peak
|
||
|
|
|
||
|
|
# Secondary volcanoes
|
||
|
|
terrain.add_hill((15, 15), 10, 0.4)
|
||
|
|
terrain.add_hill((GRID_WIDTH - 12, GRID_HEIGHT - 15), 8, 0.35)
|
||
|
|
terrain.add_hill((10, GRID_HEIGHT - 10), 6, 0.25)
|
||
|
|
|
||
|
|
# Step 3: Create craters
|
||
|
|
terrain.dig_hill((GRID_WIDTH // 2, GRID_HEIGHT // 2), 6, 0.1) # Main crater
|
||
|
|
terrain.dig_hill((15, 15), 4, 0.15) # Secondary crater
|
||
|
|
terrain.dig_hill((GRID_WIDTH - 12, GRID_HEIGHT - 15), 3, 0.18) # Third crater
|
||
|
|
|
||
|
|
# Step 4: Create lava flow pattern using voronoi
|
||
|
|
lava = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
|
||
|
|
lava.add_voronoi(num_points=12, coefficients=(1.0, -0.8), seed=77)
|
||
|
|
lava.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
# Lava originates from craters - enhance around crater centers
|
||
|
|
lava.add_hill((GRID_WIDTH // 2, GRID_HEIGHT // 2), 8, 0.5)
|
||
|
|
lava.add_hill((15, 15), 5, 0.3)
|
||
|
|
|
||
|
|
# Lava flows downhill - multiply by inverted terrain
|
||
|
|
terrain_inv = terrain.inverse()
|
||
|
|
terrain_inv.normalize(0.0, 1.0)
|
||
|
|
lava.multiply(terrain_inv)
|
||
|
|
lava.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
# Step 5: Create ash distribution using noise
|
||
|
|
ash_noise = mcrfpy.NoiseSource(dimensions=2, algorithm='perlin', seed=123)
|
||
|
|
ash = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
|
||
|
|
ash.add_noise(ash_noise, world_size=(8, 6), mode='turbulence', octaves=3)
|
||
|
|
ash.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
# Ash settles on lower areas
|
||
|
|
ash.multiply(terrain_inv)
|
||
|
|
|
||
|
|
# Step 6: Apply erosion for realistic channels
|
||
|
|
terrain.rain_erosion(drops=1500, erosion=0.1, sedimentation=0.03, seed=42)
|
||
|
|
terrain.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
# Step 7: Add lava rivers from craters
|
||
|
|
lava.dig_bezier(
|
||
|
|
points=((GRID_WIDTH // 2, GRID_HEIGHT // 2 + 5),
|
||
|
|
(GRID_WIDTH // 2 - 5, GRID_HEIGHT // 2 + 15),
|
||
|
|
(GRID_WIDTH // 3, GRID_HEIGHT - 10),
|
||
|
|
(10, GRID_HEIGHT - 5)),
|
||
|
|
start_radius=2, end_radius=3,
|
||
|
|
start_height=0.9, end_height=0.7
|
||
|
|
)
|
||
|
|
|
||
|
|
lava.dig_bezier(
|
||
|
|
points=((GRID_WIDTH // 2 + 3, GRID_HEIGHT // 2 + 3),
|
||
|
|
(GRID_WIDTH // 2 + 15, GRID_HEIGHT // 2 + 8),
|
||
|
|
(GRID_WIDTH - 15, GRID_HEIGHT // 2 + 5),
|
||
|
|
(GRID_WIDTH - 5, GRID_HEIGHT // 2 + 10)),
|
||
|
|
start_radius=1.5, end_radius=2.5,
|
||
|
|
start_height=0.85, end_height=0.65
|
||
|
|
)
|
||
|
|
|
||
|
|
# Step 8: Render
|
||
|
|
for y in range(GRID_HEIGHT):
|
||
|
|
for x in range(GRID_WIDTH):
|
||
|
|
elev = terrain.get((x, y))
|
||
|
|
lava_val = lava.get((x, y))
|
||
|
|
ash_val = ash.get((x, y))
|
||
|
|
|
||
|
|
color_layer.set(((x, y)), volcanic_color(elev, lava_val, ash_val))
|
||
|
|
|
||
|
|
# Add smoke/steam particles around crater rims
|
||
|
|
crater_centers = [
|
||
|
|
(GRID_WIDTH // 2, GRID_HEIGHT // 2, 6),
|
||
|
|
(15, 15, 4),
|
||
|
|
(GRID_WIDTH - 12, GRID_HEIGHT - 15, 3)
|
||
|
|
]
|
||
|
|
|
||
|
|
import math
|
||
|
|
for cx, cy, radius in crater_centers:
|
||
|
|
for angle in range(0, 360, 30):
|
||
|
|
rad = math.radians(angle)
|
||
|
|
px = int(cx + math.cos(rad) * radius)
|
||
|
|
py = int(cy + math.sin(rad) * radius)
|
||
|
|
if 0 <= px < GRID_WIDTH and 0 <= py < GRID_HEIGHT:
|
||
|
|
# Smoke color
|
||
|
|
color_layer.set(((px, py)), mcrfpy.Color(150, 140, 130, 180))
|
||
|
|
|
||
|
|
# Title
|
||
|
|
title = mcrfpy.Caption(text="Volcanic Region: Hills + Craters + Voronoi Lava + Erosion", 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("volcanic")
|
||
|
|
|
||
|
|
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_34_advanced_volcanic.png")
|
||
|
|
print("Screenshot saved: procgen_34_advanced_volcanic.png")
|