units
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
#!/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/project/tiletest/images/rivermap.png", preview_path="/home/bart/project/tiletest/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/project/tiletest/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/project/tiletest/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/project/tiletest/images/minimap.png")
|
||||
print("Saved orthographic hex minimap to /home/bart/project/tiletest/images/minimap.png")
|
||||
except Exception as ex:
|
||||
print(f"Minimap/preview generation skipped: {ex}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
build_rivermap()
|
||||
Reference in New Issue
Block a user