An Implementation of City Generation by Leaf Venation
From RogueBasin
from Tkinter import * import random import math RADIUS_INCREASE = 10.0 NEW_SITE_DENSITY = 0.0002 AUXIN_BIRTH_THRESHOLD = 2.0 VEIN_BIRTH_THRESHOLD = 2.0 VEIN_DEATH_THRESHOLD = 1.0 NODE_DISTANCE = 1.0 CENTRE_X = 400 CENTRE_Y = 400 SCALE = 15 ITERATIONS = 20 class Leaf: # DATA auxin_sites = {} vein_sites = {} used_vein_ids = 1 used_auxin_ids = 0 radius = 10.0 # UTILITY FUNCTIONS def new_auxin_id(self): auxin_id = self.used_auxin_ids self.used_auxin_ids += 1 return auxin_id def new_vein_id(self): vein_id = self.used_vein_ids self.used_vein_ids += 1 return vein_id # MAIN FUNCTIONS def __init__(self): start_id = self.new_vein_id() self.vein_sites[start_id] = {'ID': start_id, 'CO-ORDS': (0, 0), 'ROOT': [], 'TAGS': []} def iteration(self): self.modify_shape() self.generate_new_auxin() new_veins = self.generate_new_vein() self.kill_veins(new_veins) # SECONDARY FUNCTIONS def modify_shape(self): self.radius += RADIUS_INCREASE def pick_site(self): r = random.random() * self.radius theta = random.random() * 2 * math.pi - math.pi x = math.sqrt(r) * math.cos(theta) y = math.sqrt(r) * math.sin(theta) return (x, y) def new_sites(self): return int(NEW_SITE_DENSITY * math.pi * (self.radius**2)) def generate_new_auxin(self): # Generate new auxin nodes for i in range(self.new_sites()): site = self.pick_site() # Test each auxin node to see if we are too close failed = False for auxin_id in self.auxin_sites: auxin = self.auxin_sites[auxin_id] dx = site[0] - auxin['CO-ORDS'][0] dy = site[1] - auxin['CO-ORDS'][1] if math.sqrt(dx**2 + dy**2) < AUXIN_BIRTH_THRESHOLD: failed = True break if failed: continue # Test each vein node to see if we are too close for vein_id in self.vein_sites: vein = self.vein_sites[vein_id] dx = site[0] - vein['CO-ORDS'][0] dy = site[1] - vein['CO-ORDS'][1] if math.sqrt(dx**2 + dy**2) < VEIN_BIRTH_THRESHOLD: failed = True break if failed: continue # Create new auxin site new_id = self.new_auxin_id() self.auxin_sites[new_id] = {'ID': new_id, 'CO-ORDS': site, 'TAG': False, 'LINKED': []} def generate_new_vein(self): # Create new veins new_veins = {} for vein_id in self.vein_sites: vein = self.vein_sites[vein_id] # Identify close auxin nodes linked_auxin = [] for auxin_id in self.auxin_sites: auxin = self.auxin_sites[auxin_id] # Only allow tagged veins to grow towards dead auxin if auxin['TAG'] and auxin_id not in vein['TAGS']: continue # Check to see if there are any closer veins dx = auxin['CO-ORDS'][0] - vein['CO-ORDS'][0] dy = auxin['CO-ORDS'][1] - vein['CO-ORDS'][1] distance_squared = dx**2 + dy**2 for test_vein_id in self.vein_sites: if test_vein_id is vein_id: continue test_vein = self.vein_sites[test_vein_id] d1x = auxin['CO-ORDS'][0] - test_vein['CO-ORDS'][0] d1y = auxin['CO-ORDS'][1] - test_vein['CO-ORDS'][1] distance1_squared = d1x**2 + d1y**2 d2x = vein['CO-ORDS'][0] - test_vein['CO-ORDS'][0] d2y = vein['CO-ORDS'][1] - test_vein['CO-ORDS'][1] distance2_squared = d2x**2 + d2y**2 if distance1_squared < distance_squared and distance2_squared < distance_squared: break else: linked_auxin.append(auxin_id) auxin['LINKED'].append(vein_id) # Tag cleanup self.vein_sites[vein_id]['TAGS'] = filter(lambda tag: tag in linked_auxin, vein['TAGS']) # Calculate the new co-ords if not linked_auxin: continue sum_x = 0 sum_y = 0 for auxin_id in linked_auxin: auxin = self.auxin_sites[auxin_id] dx = auxin['CO-ORDS'][0] - vein['CO-ORDS'][0] dy = auxin['CO-ORDS'][1] - vein['CO-ORDS'][1] scale = NODE_DISTANCE / math.sqrt(dx**2 + dy**2) sum_x += dx * scale sum_y += dy * scale total = len(linked_auxin) new_coords = (sum_x / total + vein['CO-ORDS'][0], sum_y / total + vein['CO-ORDS'][1]) # Create the new vein node new_id = self.new_vein_id() new_veins[new_id] = {'ID': new_id, 'CO-ORDS': new_coords, 'ROOT': [vein_id], 'TAGS': vein['TAGS']} self.vein_sites[vein_id]['TAGS'] = [] self.vein_sites.update(new_veins) return new_veins.keys() def kill_veins(self, new_veins): for auxin_id in self.auxin_sites: auxin = self.auxin_sites[auxin_id] # Find overly close auxin nodes if not auxin['TAG']: for vein_id in self.vein_sites: vein = self.vein_sites[vein_id] dx = auxin['CO-ORDS'][0] - vein['CO-ORDS'][0] dy = auxin['CO-ORDS'][1] - vein['CO-ORDS'][1] if math.sqrt(dx**2 + dy**2) < VEIN_DEATH_THRESHOLD: # Kill the node new_id = self.new_vein_id() self.vein_sites[new_id] = {'ID': new_id, 'CO-ORDS': auxin['CO-ORDS'], 'ROOT': [], 'TAGS': []} auxin['VEIN'] = new_id auxin['TAG'] = True affected_veins = filter(lambda ID: ID in auxin['LINKED'], self.vein_sites) for new_vein_id in new_veins: new_vein = self.vein_sites[new_vein_id] if len(set(affected_veins) & set(new_vein['ROOT'])) > 0: new_vein['TAGS'].append(auxin_id) break # Process dead auxin nodes if auxin['TAG']: for vein_id in self.vein_sites: vein = self.vein_sites[vein_id] if auxin_id not in vein['TAGS']: continue dx = auxin['CO-ORDS'][0] - vein['CO-ORDS'][0] dy = auxin['CO-ORDS'][1] - vein['CO-ORDS'][1] if math.sqrt(dx**2 + dy**2) < VEIN_DEATH_THRESHOLD: vein['TAGS'].remove(auxin_id) self.vein_sites[auxin['VEIN']]['ROOT'].append(vein_id) self.vein_sites[auxin['VEIN']]['TAGS'].extend(vein['TAGS']) vein['TAGS'] = [] # Clear for next cycle auxin['LINKED'] = [] # Create the city leaf = Leaf() for i in range(ITERATIONS): leaf.iteration() # Create the window window = Tk() canvas = Canvas(window, width=1200, height=800) canvas.pack() # Draw the city for vein_id in leaf.vein_sites: coords = leaf.vein_sites[vein_id]['CO-ORDS'] x = CENTRE_X + int(coords[0] * SCALE) y = CENTRE_Y + int(coords[1] * SCALE) for root_id in leaf.vein_sites[vein_id]['ROOT']: root_coords = leaf.vein_sites[root_id]['CO-ORDS'] root_x = CENTRE_X + int(root_coords[0] * SCALE) root_y = CENTRE_Y + int(root_coords[1] * SCALE) canvas.create_line(root_x, root_y, x, y) # Display mainloop()