def riverErosion(self, river): '''Simulate erosion in heightmap based on river path. * current location must be equal to or less than previous location * riverbed is carved out by % of volume/flow * sides of river are also eroded to slope into riverbed. ''' # erosion of riverbed itself maxElevation = 1.0 minElevation = 0.0 for r in river: rx, ry = r if self.heightmap[rx, ry] < maxElevation: maxElevation = self.heightmap[rx, ry] minElevation = maxElevation * 0.99 maxElevation = random.uniform(minElevation, maxElevation) self.heightmap[rx, ry] = maxElevation # erosion around river, create river valley for r in river: rx, ry = r radius = 2 for x in range(rx - radius, rx + radius): for y in range(ry - radius, ry + radius): if not self.wrap and utilities.outOfBounds([x, y], self.size): # ignore edges of map continue x, y = utilities.overflow(x, self.worldW), utilities.overflow(y, self.worldH) curve = 1.0 if [x, y] == [0, 0]: # ignore center continue if [x, y] in river: # ignore river itself continue if self.heightmap[x, y] <= self.heightmap[rx, ry]: # ignore areas lower than river itself continue if not utilities.inCircle(radius, rx, ry, x, y): # ignore things outside a circle continue adx, ady = math.fabs(rx - x), math.fabs(ry - y) if adx == 1 or ady == 1: curve = 0.2 elif adx == 2 or ady == 2: curve = 0.05 diff = self.heightmap[rx, ry] - self.heightmap[x, y] newElevation = self.heightmap[x, y] + (diff * curve) if newElevation <= self.heightmap[rx, ry]: print('newElevation is <= than river, fix me...') newElevation = self.heightmap[x, y] self.heightmap[x, y] = newElevation return
def findLowerElevation(self, source): '''Try to find a lower elevation with in a range of an increasing circle's radius and try to find the best path and return it''' x, y = source currentRadius = 1 maxRadius = 40 lowestElevation = self.heightmap[x, y] destination = [] notFound = True isWrapped = False wrapped = [] while notFound and currentRadius <= maxRadius: for cx in range(-currentRadius, currentRadius + 1): for cy in range(-currentRadius, currentRadius + 1): rx, ry = x + cx, y + cy # are we within bounds? if not self.wrap and utilities.outOfBounds([rx, ry], self.size): continue # are we within a circle? if not utilities.inCircle(currentRadius, x, y, rx, ry): continue rx, ry = utilities.overflow(rx, self.worldW), utilities.overflow(ry, self.worldH) # if utilities.outOfBounds([x+cx, y+cy], self.size): # print "Fixed:",x ,y, rx, ry elevation = self.heightmap[rx, ry] if elevation < lowestElevation: # have we found a lower elevation? lowestElevation = elevation destination = [rx, ry] notFound = False if utilities.outOfBounds([x+cx, y+cy], self.size): wrapped.append(destination) currentRadius += 1 if destination in wrapped: isWrapped = True # print "Wrapped lower elevation found:", rx, ry, "!" return isWrapped, destination
def isRiverNearby(self, radius, tryX, tryY): ''' return true if there is a river in the range of radius from source ''' for x in range(-radius, radius + 1): for y in range(-radius, radius + 1): # are we within bounds? if utilities.outOfBounds({'x': tryX + x, 'y': tryY + y}, self.size): continue # are we within a circle? if not utilities.inCircle(radius, tryX, tryY, tryX + x, tryY + y): continue # found a river? if self.riverMap[tryX + x, tryY + y] > 0.0: return True # no river found return False
def riverFlow(self, source): '''simulate fluid dynamics by using starting point and flowing to the lowest available point''' currentLocation = source path = [source] # start the flow while True: x, y = currentLocation lowerElevation = None quickSection = None isWrapped = False for dx, dy in DIR_NEIGHBORS: # is there a river nearby, flow into it ax, ay = x + dx, y + dy if self.wrap: ax, ay = utilities.overflow(ax, self.worldW), utilities.overflow(ay, self.worldH) for river in self.riverList: if [ax, ay] in river: #print "Found another river at:", x, y, " -> ", ax, ay, " Thus, using that river's path." merge = False for rx, ry in river: if [ax, ay] == [rx, ry]: merge = True path.append([rx, ry]) elif merge == True: path.append([rx, ry]) return path # skip the rest, return path # found a sea? #print "Flowing to...",x,y if self.heightmap[x, y] <= self.seaLevel: break # find our immediate lowest elevation and flow there quickSection = self.findQuickPath(currentLocation) if quickSection: path.append(quickSection) currentLocation = quickSection continue # stop here and enter back into loop isWrapped, lowerElevation = self.findLowerElevation(currentLocation) if lowerElevation and not isWrapped: lowerPath = None lowerPath = aStar.pathFinder().find(self.heightmap, currentLocation, lowerElevation) if lowerPath: path += lowerPath currentLocation = path[-1] else: break elif lowerElevation and isWrapped: #TODO: make this more natural maxRadius = 40 wrappedX = wrappedY = False # print 'Found a lower elevation on wrapped path, searching path!' # print 'We go from',currentLocation,'to',lowerElevation cx,cy = currentLocation lx,ly = lowerElevation nx,ny = lowerElevation if (x < 0 or y < 0 or x > self.worldW or y > self.worldH): print("BUG: fix me... we shouldn't be here:", currentLocation, lowerElevation) break if not utilities.inCircle(maxRadius, cx, cy, lx, cy): # are we wrapping on x axis? #print "We found wrapping along x-axis" if cx-lx < 0: lx = 0 # move to left edge nx = self.worldW-1 # next step is wrapped around else: lx = self.worldW-1 # move to right edge nx = 0 # next step is wrapped around ly = ny = int( (cy+ly)/2 ) # move halfway elif not utilities.inCircle(maxRadius, cx, cy, cx, ly): # are we wrapping on y axis? # print "We found wrapping along y-axis" if cy-ly < 0: ly = 0 # move to top edge ny = self.worldH-1 # next step is wrapped around else: ly = self.worldH-1 # move to bottom edge ny = 0 # next step is wrapped around lx = nx = int( (cx+lx)/2 ) # move halfway else: # print "BUG: fix me... we are not in circle:", currentLocation, lowerElevation break # find our way to the edge edgePath = None edgePath = aStar.pathFinder().find(self.heightmap, [cx,cy], [lx,ly]) if not edgePath: # print "We've reached the end of this river, we cannot get through." # can't find another other path, make it a lake self.lakeList.append(currentLocation) break path += edgePath # add our newly found path path.append([nx,ny]) # finally add our overflow to other side currentLocation = path[-1] # print "Path found from ", [cx,cy], 'to', [lx,ly], 'via:' # print edgePath # print "We then wrap on: ", [nx, ny] # find our way to lowest position original found lowerPath = aStar.pathFinder().find(self.heightmap, currentLocation, lowerElevation) path += lowerPath currentLocation = path[-1] # print "We then go to our destination: ", lowerElevation # print lowerPath # print " " # print "Full path begin and end ", path[0], path[-1] hx,hy = path[0] hlx,hly = path[-1] # print "Elevations: ", self.heightmap[hx,hy], self.heightmap[hlx,hly] # print "Erosion: ", self.erosionMap[hx,hy], self.erosionMap[hlx,hly] # print " " #break else: # can't find any other path, make it a lake self.lakeList.append(currentLocation) break # end of river if utilities.outOfBounds(currentLocation, self.size): print("Why are we here:",currentLocation) return path
def riverSources(self): '''Find places on map where sources of river can be found''' riverSourceList = [] if self.waterFlow is None: # Version 1, is 'good enough' but we can do better. square = 32 # square = int(math.log(self.worldH, 2)**2) # iterate through map and mark river sources for x in range(0, self.worldW - 1, square): for y in range(0, self.worldH - 1, square): # print 'Ranges: ', x, y, square+x, square+y # find random location sources = [] for sx in range(x, x + square): for sy in range(y, y + square): if not self.wrap and utilities.outOfBounds([sx, sy], self.size): continue # convert to wrap around sx, sy = utilities.overflow(sx, self.worldW), utilities.overflow(sy, self.worldH) if self.heightmap[sx, sy] < BIOME_ELEVATION_HILLS or \ self.heightmap[sx, sy] > BIOME_ELEVATION_MOUNTAIN_LOW: continue sources.append([sx, sy]) # print len(sources), sources if sources: # print "Possible sources: ", len(sources) source = sources[random.randint(0, len(sources))] # print "River source: ", source riverSourceList.append(source) else: # Version 2, with rainfall # Using the wind and rainfall data, create river 'seeds' by # flowing rainfall along paths until a 'flow' threshold is reached # and we have a beginning of a river... trickle->stream->river->sea # step one: Using flow direction, follow the path for each cell # adding the previous cell's flow to the current cell's flow. # step two: We loop through the water flow map looking for cells # above the water flow threshold. These are our river sources and # we mark them as rivers. While looking, the cells with no # out-going flow, above water flow threshold and are still # above sea level are marked as 'sources'. for x in range(0, self.worldW - 1): for y in range(0, self.worldH - 1): rainFall = self.rainMap[x, y] self.waterFlow[x, y] = rainFall if self.waterPath[x, y] == 0: continue # ignore cells without flow direction cx, cy = x, y # begin with starting location neighbourSeedFound = False while not neighbourSeedFound: # follow flow path to where it may lead # have we found a seed? if self.heightmap[cx, cy] >= BIOME_ELEVATION_HILLS_LOW and \ self.heightmap[cx, cy] <= BIOME_ELEVATION_MOUNTAIN_LOW and \ self.waterFlow[cx, cy] >= 10.0: # try not to create seeds around other seeds for seed in riverSourceList: sx, sy = seed if utilities.inCircle(9, cx, cy, sx, sy): neighbourSeedFound = True if neighbourSeedFound: break # we do not want seeds for neighbors riverSourceList.append([cx, cy]) # river seed # self.riverMap[cx,cy] = self.waterFlow[cx,cy] #temp: mark it on map to see 'seed' break # no path means dead end... if self.waterPath[cx, cy] == 0: break # break out of loop # follow path, add water flow from previous cell dx, dy = DIR_NEIGHBORS_CENTER[self.waterPath[cx, cy]] nx, ny = cx + dx, cy + dy # calculate next cell self.waterFlow[nx, ny] += rainFall cx, cy = nx, ny # set current cell to next cell return riverSourceList