McRogueFace/docs/cookbook/procgen/08_heightmap_thresholds.py

148 lines
5.8 KiB
Python
Raw Normal View History

2026-01-13 19:42:37 -05:00
"""HeightMap Thresholds and ColorLayer Integration Demo
Demonstrates: threshold, threshold_binary, inverse, count_in_range
Also: ColorLayer.apply_ranges for multi-threshold coloring
Shows terrain classification and visualization techniques.
"""
import mcrfpy
from mcrfpy import automation
GRID_WIDTH, GRID_HEIGHT = 64, 48
CELL_SIZE = 16
def run_demo(runtime):
panel_w = GRID_WIDTH // 3
panel_h = GRID_HEIGHT // 2
# Create source terrain
noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=42)
source = mcrfpy.HeightMap((panel_w, panel_h), fill=0.0)
source.add_noise(noise, world_size=(10, 10), mode='fbm', octaves=5)
source.add_hill((panel_w // 2, panel_h // 2), 8, 0.3)
source.normalize(0.0, 1.0)
# Create derived heightmaps
water_mask = source.threshold((0.0, 0.3)) # Returns NEW heightmap with values only in range
land_binary = source.threshold_binary((0.3, 1.0), value=1.0) # Binary mask
inverted = source.inverse() # Inverted values
# Count cells in ranges for classification stats
water_count = source.count_in_range((0.0, 0.3))
land_count = source.count_in_range((0.3, 0.7))
mountain_count = source.count_in_range((0.7, 1.0))
# IMPORTANT: Render apply_ranges FIRST since it affects the whole layer
# Panel 6: Using ColorLayer.apply_ranges (bottom-right)
# Create a full-size heightmap and copy source data to correct position
panel6_hmap = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=-1.0) # -1 won't match any range
for y in range(panel_h):
for x in range(panel_w):
h = source.get((x, y))
panel6_hmap.fill(h, pos=(panel_w * 2 + x, panel_h + y), size=(1, 1))
# apply_ranges colors cells based on height ranges
# Cells with -1.0 won't match any range and stay unchanged
color_layer.apply_ranges(panel6_hmap, [
((0.0, 0.2), (30, 80, 160)), # Deep water
((0.2, 0.3), ((60, 120, 180), (120, 160, 140))), # Gradient: shallow to shore
((0.3, 0.5), (80, 150, 60)), # Lowland
((0.5, 0.7), ((60, 120, 40), (100, 100, 80))), # Gradient: forest to hills
((0.7, 0.85), (130, 120, 110)), # Rock
((0.85, 1.0), ((180, 180, 190), (250, 250, 255))), # Gradient: rock to snow
])
# Now render the other 5 panels (they will overwrite only their regions)
# Panel 1 (top-left): Original grayscale
for y in range(panel_h):
for x in range(panel_w):
h = source.get((x, y))
v = int(h * 255)
color_layer.set(((x, y)), mcrfpy.Color(v, v, v))
# Panel 2 (top-middle): threshold() - shows only values in range 0.0-0.3
for y in range(panel_h):
for x in range(panel_w):
h = water_mask.get((x, y))
if h > 0:
# Values were preserved in 0.0-0.3 range
t = h / 0.3
color_layer.set(((panel_w + x, y)), mcrfpy.Color(
int(30 + t * 40), int(60 + t * 60), int(150 + t * 50)))
else:
# Outside threshold range - dark
color_layer.set(((panel_w + x, y)), mcrfpy.Color(20, 20, 30))
# Panel 3 (top-right): threshold_binary() - land mask
for y in range(panel_h):
for x in range(panel_w):
h = land_binary.get((x, y))
if h > 0:
color_layer.set(((panel_w * 2 + x, y)), mcrfpy.Color(80, 140, 60)) # Land
else:
color_layer.set(((panel_w * 2 + x, y)), mcrfpy.Color(40, 80, 150)) # Water
# Panel 4 (bottom-left): inverse()
for y in range(panel_h):
for x in range(panel_w):
h = inverted.get((x, y))
v = int(h * 255)
color_layer.set(((x, panel_h + y)), mcrfpy.Color(v, int(v * 0.8), int(v * 0.6)))
# Panel 5 (bottom-middle): Classification using count_in_range results
for y in range(panel_h):
for x in range(panel_w):
h = source.get((x, y))
if h < 0.3:
color_layer.set(((panel_w + x, panel_h + y)), mcrfpy.Color(50, 100, 180)) # Water
elif h < 0.7:
color_layer.set(((panel_w + x, panel_h + y)), mcrfpy.Color(70, 140, 50)) # Land
else:
color_layer.set(((panel_w + x, panel_h + y)), mcrfpy.Color(140, 130, 120)) # Mountain
# Labels
labels = [
("Original (grayscale)", 5, 5),
("threshold(0-0.3)", panel_w * CELL_SIZE + 5, 5),
("threshold_binary(land)", panel_w * 2 * CELL_SIZE + 5, 5),
("inverse()", 5, panel_h * CELL_SIZE + 5),
(f"Classified (W:{water_count} L:{land_count} M:{mountain_count})", panel_w * CELL_SIZE + 5, panel_h * CELL_SIZE + 5),
("apply_ranges (biome)", panel_w * 2 * CELL_SIZE + 5, panel_h * CELL_SIZE + 5),
]
for text, x, y in labels:
label = mcrfpy.Caption(text=text, pos=(x, y))
label.fill_color = mcrfpy.Color(255, 255, 255)
label.outline = 1
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)
# Grid divider lines
for y in range(GRID_HEIGHT):
color_layer.set(((panel_w - 1, y)), mcrfpy.Color(80, 80, 80))
color_layer.set(((panel_w * 2 - 1, y)), mcrfpy.Color(80, 80, 80))
for x in range(GRID_WIDTH):
color_layer.set(((x, panel_h - 1)), mcrfpy.Color(80, 80, 80))
# Setup
scene = mcrfpy.Scene("thresholds_demo")
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_08_heightmap_thresholds.png")
print("Screenshot saved: procgen_08_heightmap_thresholds.png")