179 lines
7.2 KiB
Python
179 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
River generator and map builder for tiletest.
|
|
Encodes rivers onto images/rivermap.png such that rivers STRICTLY pass through
|
|
the exact midpoints of the 6 hexagon edges, ensuring 100% seamless connections.
|
|
"""
|
|
|
|
from PIL import Image, ImageDraw
|
|
import numpy as np
|
|
|
|
WIDTH = 3600
|
|
HEIGHT = 1500
|
|
SCALE_X = 8.0
|
|
SCALE_Y = 9.24045
|
|
LOD_0_SIZE_Y = 0.4328799197461722
|
|
|
|
# 6 edge midpoints in local tile space (radius R = 0.5)
|
|
MIDPOINTS = [
|
|
(-0.375, 0.21650635), # Edge 0 (Top-Left)
|
|
( 0.0, 0.43301270), # Edge 1 (Top)
|
|
( 0.375, 0.21650635), # Edge 2 (Top-Right)
|
|
( 0.375, -0.21650635), # Edge 3 (Bottom-Right)
|
|
( 0.0, -0.43301270), # Edge 4 (Bottom)
|
|
(-0.375, -0.21650635) # Edge 5 (Bottom-Left)
|
|
]
|
|
|
|
def tile_world_pos(tx, ty):
|
|
wx = tx * 0.75
|
|
wy = ty * (LOD_0_SIZE_Y * 2.0) + (0.0 if tx % 2 == 0 else LOD_0_SIZE_Y)
|
|
return wx, wy
|
|
|
|
def world_to_pixel(wx, wy):
|
|
px = int(round(wx * SCALE_X))
|
|
py = int(round(wy * SCALE_Y))
|
|
return px, HEIGHT - 1 - py
|
|
|
|
def get_hex_neighbor(x, y, edge):
|
|
even = (x % 2 == 0)
|
|
if edge == 0: return (x - 1, y if even else y + 1)
|
|
if edge == 1: return (x, y + 1)
|
|
if edge == 2: return (x + 1, y if even else y + 1)
|
|
if edge == 3: return (x + 1, y - 1 if even else y)
|
|
if edge == 4: return (x, y - 1)
|
|
if edge == 5: return (x - 1, y - 1 if even else y)
|
|
return (x, y)
|
|
|
|
def trace_river(start_tile, edge_steps):
|
|
"""Traces a river from a start tile following a sequence of exit edges."""
|
|
path = []
|
|
curr = start_tile
|
|
in_edge = None
|
|
for out_edge in edge_steps:
|
|
path.append((curr, in_edge, out_edge))
|
|
curr = get_hex_neighbor(curr[0], curr[1], out_edge)
|
|
in_edge = (out_edge + 3) % 6
|
|
path.append((curr, in_edge, None))
|
|
return path
|
|
|
|
def build_rivermap(output_path="/home/bart/git/Ascent-of-Man/images/rivermap.png", preview_path="/home/bart/git/Ascent-of-Man/images/rivermap_preview.png"):
|
|
river_img = Image.new('RGB', (WIDTH, HEIGHT), (0, 0, 0))
|
|
draw = ImageDraw.Draw(river_img)
|
|
|
|
# 1. Main River: starts high on mountain (36, 31) in snow, flows down to South Ocean (32, 14)
|
|
main_river_steps = [5, 5, 5, 5] + [4] * 15
|
|
main_river = trace_river((36, 31), main_river_steps)
|
|
|
|
# 2. Mountain Tributary: starts on ridge (36, 28) and merges with main river at (32, 26)
|
|
trib_river_steps = [5, 5, 5, 5]
|
|
tributary = trace_river((36, 28), trib_river_steps)
|
|
|
|
# 3. Canyon River in Prairie (Red Sand & Red Rock)
|
|
prairie_steps = [4, 4, 5, 4, 4, 3, 4, 4, 4]
|
|
prairie_river = trace_river((110, 32), prairie_steps)
|
|
|
|
rivers = [main_river, tributary, prairie_river]
|
|
river_color = (0, 180, 255)
|
|
|
|
for r in rivers:
|
|
for (tx, ty), e_in, e_out in r:
|
|
wx, wy = tile_world_pos(tx, ty)
|
|
cx, cy = world_to_pixel(wx, wy)
|
|
edges = [e for e in (e_in, e_out) if e is not None]
|
|
for e in edges:
|
|
mx, my = MIDPOINTS[e]
|
|
mpx, mpy = world_to_pixel(wx + mx, wy + my)
|
|
# Line between midpoint and tile center
|
|
draw.line([(mpx, mpy), (cx, cy)], fill=river_color, width=2)
|
|
# Small marker at midpoint for detection
|
|
draw.rectangle([mpx - 1, mpy - 1, mpx + 1, mpy + 1], fill=river_color)
|
|
|
|
river_img.save(output_path)
|
|
print(f"Saved rivermap to {output_path}")
|
|
|
|
# Build an overlay preview on top of typemap.png
|
|
try:
|
|
type_img = Image.open("/home/bart/git/Ascent-of-Man/images/typemap.png").convert("RGBA")
|
|
r_arr = np.array(river_img)
|
|
t_arr = np.array(type_img)
|
|
mask = (r_arr[:, :, 2] > 120) & (r_arr[:, :, 0] < 100)
|
|
t_arr[mask] = [0, 220, 255, 255]
|
|
preview = Image.fromarray(t_arr)
|
|
preview.save(preview_path)
|
|
print(f"Saved preview to {preview_path}")
|
|
|
|
# Build orthographic hex-tile minimap.png
|
|
h_arr = np.array(Image.open("/home/bart/git/Ascent-of-Man/images/heightmap.jpg"))
|
|
R = 8.0
|
|
W = 3608
|
|
H = 1740
|
|
hex_img = Image.new('RGB', (W, H), (15, 20, 30))
|
|
hdraw = ImageDraw.Draw(hex_img)
|
|
angles = [np.pi * 60.0 * i / 180.0 for i in range(6)]
|
|
corner_dx = [-R * np.cos(a) for a in angles]
|
|
corner_dy = [ R * np.sin(a) for a in angles]
|
|
|
|
for ty in range(125):
|
|
for tx in range(300):
|
|
wx = tx * 0.75
|
|
wy = ty * (LOD_0_SIZE_Y * 2.0) + (0.0 if tx % 2 == 0 else LOD_0_SIZE_Y)
|
|
cx = (wx + 0.5) * 16.0
|
|
cy = H - 1 - (wy + LOD_0_SIZE_Y) * 16.0
|
|
|
|
px = min(3599, max(0, int(round(wx * SCALE_X))))
|
|
py = min(1499, max(0, int(round(wy * SCALE_Y))))
|
|
h = h_arr[1500 - 1 - py, px]
|
|
tp = t_arr[1500 - 1 - py, px]
|
|
dist_pole = abs((wy / 108.22) - 0.5) * 2.0
|
|
|
|
if h <= 2:
|
|
color = (220, 235, 250) if dist_pole > 0.85 else (28, 95, 175) # Water (blue) / polar ice
|
|
elif h > 182 or (dist_pole > 0.80 and h > 20):
|
|
color = (245, 250, 255) # Snow (white) above 2500m
|
|
elif h > 73:
|
|
# Mountain rock (between 1000m and 2500m)
|
|
r, g, b = int(tp[0]), int(tp[1]), int(tp[2])
|
|
if r > 150 and g < 100:
|
|
color = (175, 75, 55) # Red rock mountain in prairie
|
|
else:
|
|
color = (125, 130, 135) # Gray alpine mountain rock
|
|
else:
|
|
# Biome ground below 1000m
|
|
r, g, b = int(tp[0]), int(tp[1]), int(tp[2])
|
|
sat = max(r, g, b) - min(r, g, b)
|
|
if r > 150 and g < 100:
|
|
color = (195, 75, 45) # Prairie / Red Rock
|
|
elif r > 150 and g > 150 and b < 100:
|
|
color = (225, 185, 65) # Sahara yellow
|
|
elif b > 50 and r < 50:
|
|
color = (35, 115, 50) # Forest green
|
|
elif sat < 40 and g < 140:
|
|
color = (110, 125, 95) # Tundra slate
|
|
else:
|
|
color = (85, 175, 55) # Grassland green
|
|
|
|
poly = [(cx + corner_dx[i], cy + corner_dy[i]) for i in range(6)]
|
|
hdraw.polygon(poly, fill=color, outline=(0, 0, 0, 45))
|
|
|
|
# Draw rivers on hex map
|
|
for r in rivers:
|
|
for (tx, ty), e_in, e_out in r:
|
|
wx, wy = tile_world_pos(tx, ty)
|
|
cx = (wx + 0.5) * 16.0
|
|
cy = H - 1 - (wy + LOD_0_SIZE_Y) * 16.0
|
|
edges = [e for e in (e_in, e_out) if e is not None]
|
|
for e in edges:
|
|
mx, my = MIDPOINTS[e]
|
|
mpx = (wx + mx + 0.5) * 16.0
|
|
mpy = H - 1 - (wy + my + LOD_0_SIZE_Y) * 16.0
|
|
hdraw.line([(mpx, mpy), (cx, cy)], fill=(0, 220, 255), width=3)
|
|
hdraw.ellipse([mpx - 2, mpy - 2, mpx + 2, mpy + 2], fill=(0, 240, 255))
|
|
|
|
hex_img.save("/home/bart/git/Ascent-of-Man/images/minimap.png")
|
|
print("Saved orthographic hex minimap to /home/bart/git/Ascent-of-Man/images/minimap.png")
|
|
except Exception as ex:
|
|
print(f"Minimap/preview generation skipped: {ex}")
|
|
|
|
if __name__ == "__main__":
|
|
build_rivermap()
|