Spaces:
Runtime error
Runtime error
| import numpy as np | |
| from scipy.spatial import Voronoi | |
| from skimage.draw import polygon | |
| from PIL import Image | |
| from noise import snoise3 | |
| from skimage import exposure | |
| from scipy.interpolate import interp1d | |
| import cv2 | |
| from scipy.ndimage import gaussian_filter | |
| from scipy.ndimage import binary_dilation | |
| from argparse import ArgumentParser | |
| def save_height_map(height_map, file_name): | |
| #input height map should be float, raw output of noise map | |
| normalized_height_map = (((height_map - height_map.min()) / (height_map.max() - height_map.min()))*255).astype(np.uint8) | |
| cv2.imwrite(file_name, normalized_height_map) | |
| np.save(file_name[:-4] + '.npy', height_map) | |
| def get_boundary(vor_map, size, kernel=1): | |
| boundary_map = np.zeros_like(vor_map, dtype=bool) | |
| n, m = vor_map.shape | |
| clip = lambda x: max(0, min(size-1, x)) | |
| def check_for_mult(a): | |
| b = a[0] | |
| for i in range(len(a)-1): | |
| if a[i] != b: return 1 | |
| return 0 | |
| for i in range(n): | |
| for j in range(m): | |
| boundary_map[i, j] = check_for_mult(vor_map[ | |
| clip(i-kernel):clip(i+kernel+1), | |
| clip(j-kernel):clip(j+kernel+1), | |
| ].flatten()) | |
| return boundary_map | |
| def histeq(img, alpha=1): | |
| img_cdf, bin_centers = exposure.cumulative_distribution(img) | |
| img_eq = np.interp(img, bin_centers, img_cdf) | |
| img_eq = np.interp(img_eq, (0, 1), (-1, 1)) | |
| return alpha * img_eq + (1 - alpha) * img | |
| def voronoi(points, size): | |
| # Add points at edges to eliminate infinite ridges | |
| edge_points = size*np.array([[-1, -1], [-1, 2], [2, -1], [2, 2]]) | |
| new_points = np.vstack([points, edge_points]) | |
| # Calculate Voronoi tessellation | |
| vor = Voronoi(new_points) | |
| return vor | |
| def voronoi_map(vor, size): | |
| # Calculate Voronoi map | |
| vor_map = np.zeros((size, size), dtype=np.uint32) | |
| for i, region in enumerate(vor.regions): | |
| # Skip empty regions and infinte ridge regions | |
| if len(region) == 0 or -1 in region: continue | |
| # Get polygon vertices | |
| x, y = np.array([vor.vertices[i][::-1] for i in region]).T | |
| # Get pixels inside polygon | |
| rr, cc = polygon(x, y) | |
| # Remove pixels out of image bounds | |
| in_box = np.where((0 <= rr) & (rr < size) & (0 <= cc) & (cc < size)) | |
| rr, cc = rr[in_box], cc[in_box] | |
| # Paint image | |
| vor_map[rr, cc] = i | |
| return vor_map | |
| # Lloyd's relaxation | |
| def relax(points, size, k=10): | |
| new_points = points.copy() | |
| for _ in range(k): | |
| vor = voronoi(new_points, size) | |
| new_points = [] | |
| for i, region in enumerate(vor.regions): | |
| if len(region) == 0 or -1 in region: continue | |
| poly = np.array([vor.vertices[i] for i in region]) | |
| center = poly.mean(axis=0) | |
| new_points.append(center) | |
| new_points = np.array(new_points).clip(0, size) | |
| return new_points | |
| def noise_map(size, res, seed, octaves=1, persistence=0.5, lacunarity=2.0): | |
| scale = size/res | |
| return np.array([[ | |
| snoise3( | |
| (x+0.1)/scale, | |
| y/scale, | |
| seed, | |
| octaves=octaves, | |
| persistence=persistence, | |
| lacunarity=lacunarity | |
| ) | |
| for x in range(size)] | |
| for y in range(size) | |
| ]) | |
| def average_cells(vor, data): | |
| """Returns the average value of data inside every voronoi cell""" | |
| size = vor.shape[0] | |
| count = np.max(vor)+1 | |
| sum_ = np.zeros(count) | |
| count = np.zeros(count) | |
| for i in range(size): | |
| for j in range(size): | |
| p = vor[i, j] | |
| count[p] += 1 | |
| sum_[p] += data[i, j] | |
| average = sum_/ (count + 1e-3) | |
| average[count==0] = 0 | |
| return average | |
| def fill_cells(vor, data): | |
| size = vor.shape[0] | |
| image = np.zeros((size, size)) | |
| for i in range(size): | |
| for j in range(size): | |
| p = vor[i, j] | |
| image[i, j] = data[p] | |
| return image | |
| def color_cells(vor, data, dtype=int): | |
| size = vor.shape[0] | |
| image = np.zeros((size, size, 3)) | |
| for i in range(size): | |
| for j in range(size): | |
| p = vor[i, j] | |
| image[i, j] = data[p] | |
| return image.astype(dtype) | |
| def quantize(data, n): | |
| bins = np.linspace(-1, 1, n+1) | |
| return (np.digitize(data, bins) - 1).clip(0, n-1) | |
| def bezier(x1, y1, x2, y2, a): | |
| p1 = np.array([0, 0]) | |
| p2 = np.array([x1, y1]) | |
| p3 = np.array([x2, y2]) | |
| p4 = np.array([1, a]) | |
| return lambda t: ((1-t)**3 * p1 + 3*(1-t)**2*t * p2 + 3*(1-t)*t**2 * p3 + t**3 * p4) | |
| def bezier_lut(x1, y1, x2, y2, a): | |
| t = np.linspace(0, 1, 256) | |
| f = bezier(x1, y1, x2, y2, a) | |
| curve = np.array([f(t_) for t_ in t]) | |
| return interp1d(*curve.T) | |
| def filter_map(h_map, smooth_h_map, x1, y1, x2, y2, a, b): | |
| f = bezier_lut(x1, y1, x2, y2, a) | |
| output_map = b*h_map + (1-b)*smooth_h_map | |
| output_map = f(output_map.clip(0, 1)) | |
| return output_map | |
| def filter_inbox(pts, size): | |
| inidx = np.all(pts < size, axis=1) | |
| return pts[inidx] | |
| def generate_trees(n, size): | |
| trees = np.random.randint(0, size-1, (n, 2)) | |
| trees = relax(trees, size, k=10).astype(np.uint32) | |
| trees = filter_inbox(trees, size) | |
| return trees | |
| def place_trees(river_land_mask, adjusted_height_river_map, n, mask, size, a=0.5): | |
| trees= generate_trees(n, size) | |
| rr, cc = trees.T | |
| output_trees = np.zeros((size, size), dtype=bool) | |
| output_trees[rr, cc] = True | |
| output_trees = output_trees*(mask>a)*river_land_mask*(adjusted_height_river_map<0.5) | |
| output_trees = np.array(np.where(output_trees == 1))[::-1].T | |
| return output_trees | |
| def PCGGen(map_size, nbins = 256, seed = 3407): | |
| biome_names = [ | |
| # sand and rock | |
| "desert", | |
| # grass gravel rock stone | |
| "savanna", # mixed woodland and grassland | |
| # trees flower | |
| "tropical_woodland", # rainforest | |
| # dirt grass gravel rock stone | |
| "tundra", # no trees | |
| # trees flower | |
| "seasonal_forest", | |
| # trees | |
| "rainforest", | |
| # trees | |
| "temperate_forest", | |
| # trees | |
| "temperate_rainforest", | |
| # snow rock tree | |
| "boreal_forest" # taiga, snow forest | |
| ] | |
| biome_colors = [ | |
| [255, 255, 178], | |
| [184, 200, 98], | |
| [188, 161, 53], | |
| [190, 255, 242], | |
| [106, 144, 38], | |
| [33, 77, 41], | |
| [86, 179, 106], | |
| [34, 61, 53], | |
| [35, 114, 94] | |
| ] | |
| size = map_size | |
| n = nbins | |
| map_seed = seed | |
| # start generation | |
| points = np.random.randint(0, size, (514, 2)) | |
| points = relax(points, size, k=100) | |
| vor = voronoi(points, size) | |
| vor_map = voronoi_map(vor, size) | |
| boundary_displacement = 8 | |
| boundary_noise = np.dstack([noise_map(size, 32, 200 + map_seed, octaves=8), noise_map(size, 32, 250 + map_seed, octaves=8)]) | |
| boundary_noise = np.indices((size, size)).T + boundary_displacement*boundary_noise | |
| boundary_noise = boundary_noise.clip(0, size-1).astype(np.uint32) | |
| blurred_vor_map = np.zeros_like(vor_map) | |
| for x in range(size): | |
| for y in range(size): | |
| j, i = boundary_noise[x, y] | |
| blurred_vor_map[x, y] = vor_map[i, j] | |
| vor_map = blurred_vor_map | |
| temperature_map = noise_map(size, 2, 10 + map_seed) | |
| precipitation_map = noise_map(size, 2, 20 + map_seed) | |
| uniform_temperature_map = histeq(temperature_map, alpha=0.33) | |
| uniform_precipitation_map = histeq(precipitation_map, alpha=0.33) | |
| temperature_map = uniform_temperature_map | |
| precipitation_map = uniform_precipitation_map | |
| temperature_cells = average_cells(vor_map, temperature_map) | |
| precipitation_cells = average_cells(vor_map, precipitation_map) | |
| quantize_temperature_cells = quantize(temperature_cells, n) | |
| quantize_precipitation_cells = quantize(precipitation_cells, n) | |
| quantize_temperature_map = fill_cells(vor_map, quantize_temperature_cells) | |
| quantize_precipitation_map = fill_cells(vor_map, quantize_precipitation_cells) | |
| temperature_cells = quantize_temperature_cells | |
| precipitation_cells = quantize_precipitation_cells | |
| temperature_map = quantize_temperature_map | |
| precipitation_map = quantize_precipitation_map | |
| im = np.array(Image.open("./assets/biome_image.png"))[:, :, :3] | |
| im = cv2.resize(im, (256, 256)) | |
| biomes = np.zeros((256, 256)) | |
| for i, color in enumerate(biome_colors): | |
| indices = np.where(np.all(im == color, axis=-1)) | |
| biomes[indices] = i | |
| biomes = np.flip(biomes, axis=0).T | |
| n = len(temperature_cells) | |
| biome_cells = np.zeros(n, dtype=np.uint32) | |
| for i in range(n): | |
| temp, precip = temperature_cells[i], precipitation_cells[i] | |
| biome_cells[i] = biomes[temp, precip] | |
| biome_map = fill_cells(vor_map, biome_cells).astype(np.uint32) | |
| biome_color_map = color_cells(biome_map, biome_colors) | |
| height_map = noise_map(size, 4, 0 + map_seed, octaves=6, persistence=0.5, lacunarity=2) | |
| land_mask = height_map > 0 | |
| smooth_height_map = noise_map(size, 4, 0 + map_seed, octaves=1, persistence=0.5, lacunarity=2) | |
| biome_height_maps = [ | |
| # Desert | |
| filter_map(height_map, smooth_height_map, 0.75, 0.2, 0.95, 0.2, 0.2, 0.5), | |
| # Savanna | |
| filter_map(height_map, smooth_height_map, 0.5, 0.1, 0.95, 0.1, 0.1, 0.2), | |
| # Tropical Woodland | |
| filter_map(height_map, smooth_height_map, 0.33, 0.33, 0.95, 0.1, 0.1, 0.75), | |
| # Tundra | |
| filter_map(height_map, smooth_height_map, 0.5, 1, 0.25, 1, 1, 1), | |
| # Seasonal Forest | |
| filter_map(height_map, smooth_height_map, 0.75, 0.5, 0.4, 0.4, 0.33, 0.2), | |
| # Rainforest | |
| filter_map(height_map, smooth_height_map, 0.5, 0.25, 0.66, 1, 1, 0.5), | |
| # Temperate forest | |
| filter_map(height_map, smooth_height_map, 0.75, 0.5, 0.4, 0.4, 0.33, 0.33), | |
| # Temperate Rainforest | |
| filter_map(height_map, smooth_height_map, 0.75, 0.5, 0.4, 0.4, 0.33, 0.33), | |
| # Boreal | |
| filter_map(height_map, smooth_height_map, 0.8, 0.1, 0.9, 0.05, 0.05, 0.1) | |
| ] | |
| biome_count = len(biome_names) | |
| biome_masks = np.zeros((biome_count, size, size)) | |
| for i in range(biome_count): | |
| biome_masks[i, biome_map==i] = 1 | |
| biome_masks[i] = gaussian_filter(biome_masks[i], sigma=16) | |
| # Remove ocean from masks | |
| blurred_land_mask = land_mask | |
| blurred_land_mask = binary_dilation(land_mask, iterations=32).astype(np.float64) | |
| blurred_land_mask = gaussian_filter(blurred_land_mask, sigma=16) | |
| # biome mask - [9, size, size] | |
| biome_masks = biome_masks*blurred_land_mask | |
| adjusted_height_map = height_map.copy() | |
| for i in range(len(biome_height_maps)): | |
| adjusted_height_map = (1-biome_masks[i])*adjusted_height_map + biome_masks[i]*biome_height_maps[i] | |
| # add rivers | |
| biome_bound = get_boundary(biome_map, size, kernel=5) | |
| cell_bound = get_boundary(vor_map, size, kernel=2) | |
| river_mask = noise_map(size, 4, 4353 + map_seed, octaves=6, persistence=0.5, lacunarity=2) > 0 | |
| new_biome_bound = biome_bound*(adjusted_height_map<0.5)*land_mask | |
| new_cell_bound = cell_bound*(adjusted_height_map<0.05)*land_mask | |
| rivers = np.logical_or(new_biome_bound, new_cell_bound)*river_mask | |
| loose_river_mask = binary_dilation(rivers, iterations=8) | |
| rivers_height = gaussian_filter(rivers.astype(np.float64), sigma=2)*loose_river_mask | |
| adjusted_height_river_map = adjusted_height_map*(1-rivers_height) - 0.05*rivers | |
| sea_color = np.array([12, 14, 255]) | |
| river_land_mask = adjusted_height_river_map >= 0 | |
| land_mask_color = np.repeat(river_land_mask[:, :, np.newaxis], 3, axis=-1) | |
| rivers_biome_color_map = land_mask_color*biome_color_map + (1-land_mask_color)*sea_color | |
| rivers_biome_map = river_land_mask * biome_map + (1 - river_land_mask) * biome_count # use biome count=9 as water indicator | |
| semantic_map = rivers_biome_map | |
| semantic_map_color = rivers_biome_color_map | |
| height_map = adjusted_height_river_map | |
| tree_densities = [4000, 1500, 8000, 1000, 10000, 25000, 10000, 20000, 5000] | |
| trees = [np.array(place_trees(river_land_mask, adjusted_height_river_map, tree_densities[i], biome_masks[i], size)) for i in range(len(biome_names))] | |
| canvas = np.ones((size, size)) * 255 | |
| for k in range(len(biome_names)): | |
| canvas[trees[k][:, 1], trees[k][:, 0]] = k | |
| tree_map = canvas | |
| return height_map, semantic_map, tree_map, semantic_map_color | |
| if __name__ == '__main__': | |
| import os | |
| parser = ArgumentParser() | |
| parser.add_argument('--size', type=int, required=True) | |
| parser.add_argument('--nbins', type=int, default=256) | |
| parser.add_argument('--seed', type=int, default=3407) | |
| parser.add_argument('--outdir', type=str, required=True) | |
| args = parser.parse_args() | |
| outdir = args.outdir | |
| heightmap, semanticmap, treemap, colormap = PCGGen(args.size, args.nbins, args.seed) | |
| save_height_map(heightmap, os.path.join(outdir, 'heightmap.png')) | |
| cv2.imwrite(os.path.join(outdir, 'semanticmap.png'), semanticmap.astype(np.uint8)) | |
| cv2.imwrite(os.path.join(outdir, 'colormap.png'), colormap[..., [2, 1, 0]].astype(np.uint8)) | |
| cv2.imwrite(os.path.join(outdir, 'treemap.png'), treemap) | |