148 lines
5.8 KiB
Python
148 lines
5.8 KiB
Python
|
|
"""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")
|