def fetch(self, startTime, endTime=None): now = int(time.time()) if endTime is None: endTime = now else: endTime = magic_time(endTime) startTime = magic_time(startTime) delta = int(endTime) - int(startTime) tick = delta / self.step noise = SimplexNoise(1024) noise.randomize() timeInfos = startTime, endTime, self.step return timeInfos, [noise.noise2(1, a) for a in range(tick)]
def __init__(self, world, seed): super(TerrainGeneratorSimple, self).__init__(seed) self.world = world self.seed = G.SEED #seed self.rand = random.Random(seed) perm = range(255) self.rand.shuffle(perm) self.noise = SimplexNoise(permutation_table=perm).noise2 #self.noise = PerlinNoise(seed).noise self.PERSISTENCE = 2.1379201 #AKA lacunarity self.H = 0.836281 self.biome_generator = BiomeGenerator(seed) #Fun things to adjust self.OCTAVES = 9 #Higher linearly increases calc time; increases apparent 'randomness' self.height_range = 32 #If you raise this, you should shrink zoom_level equally self.height_base = 32 #The lowest point the perlin terrain will generate (below is "underground") self.island_shore = 38 #below this is sand, above is grass .. island only self.water_level = 36 # have water 2 block higher than base, allowing for some rivers... self.zoom_level = 0.002 #Smaller will create gentler, softer transitions. Larger is more mountainy # ores avaliable on the lowest level, closet to bedrock self.lowlevel_ores = ((B.stone_block, ) * 75 + (B.diamondore_block, ) * 2 + (B.sapphireore_block, ) * 2) # ores in the 'mid-level' .. also, the common ore blocks self.midlevel_ores = ((B.stone_block, ) * 80 + (B.rubyore_block, ) * 2 + (B.coalore_block, ) * 4 + (B.gravel_block, ) * 5 + (B.ironore_block, ) * 5 + (B.lapisore_block, ) * 2) # ores closest to the top level dirt and ground self.highlevel_ores = ((B.stone_block, ) * 85 + (B.gravel_block, ) * 5 + (B.coalore_block, ) * 3 + (B.quartz_block, ) * 5) self.underwater_blocks = ((B.sand_block, ) * 70 + (B.gravel_block, ) * 20 + (B.clay_block, ) * 10) #self.world_type_trees = (N.oak_tree, N.birch_tree, N.water_melon, N.pumpkin, N.y_flowers, N.potato, N.carrot, N.rose) self.world_type_trees = (N.oak_tree, N.birch_tree, N.jungle_tree) self.world_type_plants = (N.pumpkin, N.potato, N.carrot, N.water_melon) self.world_type_grass = (N.y_flowers, N.tall_grass, N.rose, N.tall_grass0, N.tall_grass1, N.cactus, N.tall_grass2, N.tall_cactus, N.tall_grass3, N.tall_grass4, N.tall_grass5, N.tall_grass6, N.tall_grass7, N.dead_bush, N.desert_grass) #This is a list of blocks that may leak over from adjacent sectors and whose presence doesn't mean the sector is generated self.autogenerated_blocks = N.VEGETATION_BLOCKS self.nether = (B.nether_block, B.soulsand_block, B.netherore_block, B.air_block) #self.nether = ((B.nether_block,) * 80 + (B.soulsand_block,) * 15 + (B.netherore_block,) * 5 + (B.air_block,) * 10) self.weights = [ self.PERSISTENCE**(-self.H * n) for n in xrange(self.OCTAVES) ]
def get_noise(loc: Vec, noise_func: SimplexNoise): """Generate a number between 0 and 1. This is used to determine where tiles are placed. """ # Average between the neighbouring locations, to smooth out changes. return sum( # + 1 / 2 fixes the value range (originally -1,1 -> 0,1) (noise_func.noise3(loc.x + x, loc.y + y, loc.z) + 1) / 2 for x in (-1, 0, 1) for y in (-1, 0, 1)) / 9
def __init__(self, seed, octaves=6, zoom_level=0.002): # octaves = 6, perm = range(255) random.Random(seed).shuffle(perm) self.noise = SimplexNoise(permutation_table=perm).noise2 self.PERSISTENCE = 2.1379201 # AKA lacunarity self.H = 0.836281 self.OCTAVES = octaves # Higher linearly increases calc time; increases apparent 'randomness' self.weights = [self.PERSISTENCE ** (-self.H * n) for n in xrange(self.OCTAVES)] self.zoom_level = zoom_level # Smaller will create gentler, softer transitions. Larger is more mountainy
def get_noise(loc: Vec, noise_func: SimplexNoise): """Generate a number between 0 and 1. This is used to determine where tiles are placed. """ # Average between the neighbouring locations, to smooth out changes. return sum( # + 1 / 2 fixes the value range (originally -1,1 -> 0,1) (noise_func.noise3(loc.x + x, loc.y + y, loc.z) + 1) / 2 for x in (-1, 0, 1) for y in (-1, 0, 1) ) / 9
def __init__(self): # current builder state message self.message: str = '' self.completion: int = 0 # out of 100% # current map status self.map_ready: bool = False # map being worked on self.wmap: WorldMap = None # map builder tools noise_generator = SimplexNoise() self.floor_shaper = FloorShaper(noise_generator) self.cavern_shaper = CavernShaper(noise_generator) self.forest_shaper = ForestShaper(noise_generator) self.ore_shaper = OreShaper(noise_generator) self.single_wall_spawner = SingleWallSpawner() self.single_wall_replacer = SingleWallReplacer() self.plank_shack_generator = PlankShackGenerator() self.mountain_fortress_generator = MountainFortressGenerator()
def res_cutout_tile(vmf: srctools.VMF, res: Property): """Generate random quarter tiles, like in Destroyed or Retro maps. - `MarkerItem` is the instance file to look for (`<ITEM_BEE2_CUTOUT_TILE>`) - `floor_chance`: The percentage change for a segment in the middle of the floor to be a normal tile. - `floor_glue_chance`: The chance for any tile to be glue - this should be higher than the regular chance, as that overrides this. - `rotateMax` is the maximum angle to rotate squarebeam models. - `squarebeamsSkin` sets the skin to use for the squarebeams floor frame. - `dispBase`, if true makes the floor a displacement with random alpha. - `Materials` blocks specify the possible materials to use: - `squarebeams` is the squarebeams variant to use. - `ceilingwalls` are the sides of the ceiling section. - `floorbase` is the texture under floor sections. If `dispBase` is True this is a displacement material. - `tile_glue` is used on top of a thinner tile segment. - `clip` is the player_clip texture used over floor segments. (This allows customising the surfaceprop.) """ marker_filenames = instanceLocs.resolve(res['markeritem']) # TODO: Reimplement cutout tiles. for inst in vmf.by_class['func_instance']: if inst['file'].casefold() in marker_filenames: inst.remove() return x: float y: float max_x: float max_y: float INST_LOCS = {} # Map targetnames -> surface loc CEIL_IO = [] # Pairs of ceil inst corners to cut out. FLOOR_IO = [] # Pairs of floor inst corners to cut out. overlay_ids = {} # When we replace brushes, we need to fix any overlays # on that surface. MATS.clear() floor_edges = [] # Values to pass to add_floor_sides() at the end sign_locs = set() # If any signage is present in the map, we need to force tiles to # appear at that location! for over in vmf.by_class['info_overlay']: if (over['material'].casefold() in FORCE_TILE_MATS and # Only check floor/ceiling overlays over['basisnormal'] in ('0 0 1', '0 0 -1')): add_signage_loc(sign_locs, Vec.from_str(over['origin'])) for item in connections.ITEMS.values(): for ind_pan in item.ind_panels: loc = Vec(0, 0, -64) loc.localise( Vec.from_str(ind_pan['origin']), Vec.from_str(ind_pan['angles']), ) add_signage_loc(sign_locs, loc) SETTINGS = { 'floor_chance': srctools.conv_int(res['floorChance', '100'], 100), 'ceil_chance': srctools.conv_int(res['ceilingChance', '100'], 100), 'floor_glue_chance': srctools.conv_int(res['floorGlueChance', '0']), 'ceil_glue_chance': srctools.conv_int(res['ceilingGlueChance', '0']), 'rotate_beams': int(srctools.conv_float(res['rotateMax', '0']) * BEAM_ROT_PRECISION), 'beam_skin': res['squarebeamsSkin', '0'], 'base_is_disp': srctools.conv_bool(res['dispBase', '0']), 'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2', 'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2', } random.seed(vbsp.MAP_RAND_SEED + '_CUTOUT_TILE_NOISE') noise = SimplexNoise(period=4 * 40) # 4 tiles/block, 50 blocks max # We want to know the number of neighbouring tile cutouts before # placing tiles - blocks away from the sides generate fewer tiles. # all_floors[z][x,y] = count floor_neighbours = defaultdict( dict) # type: Dict[float, Dict[Tuple[float, float], int]] for mat_prop in res.find_key('Materials', []): MATS[mat_prop.name].append(mat_prop.value) if SETTINGS['base_is_disp']: # We want the normal brushes to become nodraw. MATS['floorbase_disp'] = MATS['floorbase'] MATS['floorbase'] = ['tools/toolsnodraw'] # Since this uses random data for initialisation, the alpha and # regular will use slightly different patterns. alpha_noise = SimplexNoise(period=4 * 50) else: alpha_noise = None for key, default in TEX_DEFAULT: if key not in MATS: MATS[key] = [default] # Find our marker ents for inst in vmf.by_class['func_instance']: if inst['file'].casefold() not in marker_filenames: continue targ = inst['targetname'] normal = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0']) # Check the orientation of the marker to figure out what to generate if normal == (0, 0, 1): io_list = FLOOR_IO else: io_list = CEIL_IO # Reuse orient to calculate where the solid face will be. loc = Vec.from_str(inst['origin']) - 64 * normal INST_LOCS[targ] = loc item = connections.ITEMS[targ] item.delete_antlines() if item.outputs: for conn in list(item.outputs): if conn.to_item.inst['file'].casefold() in marker_filenames: io_list.append((targ, conn.to_item.name)) else: LOGGER.warning('Cutout tile connected to non-cutout!') conn.remove() # Delete the connection. else: # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 128x128 tile segment. io_list.append((targ, targ)) # Remove all traces of this item (other than in connections lists). inst.remove() del connections.ITEMS[targ] for start_floor, end_floor in FLOOR_IO: box_min = Vec(INST_LOCS[start_floor]) box_min.min(INST_LOCS[end_floor]) box_max = Vec(INST_LOCS[start_floor]) box_max.max(INST_LOCS[end_floor]) if box_min.z != box_max.z: continue # They're not in the same level! z = box_min.z if SETTINGS['rotate_beams']: # We have to generate 1 model per 64x64 block to do rotation... gen_rotated_squarebeams( vmf, box_min - (64, 64, 0), box_max + (64, 64, -8), skin=SETTINGS['beam_skin'], max_rot=SETTINGS['rotate_beams'], ) else: # Make the squarebeams props, using big models if possible gen_squarebeams(vmf, box_min + (-64, -64, 0), box_max + (64, 64, -8), skin=SETTINGS['beam_skin']) # Add a player_clip brush across the whole area vmf.add_brush( vmf.make_prism( p1=box_min - (64, 64, FLOOR_DEPTH), p2=box_max + (64, 64, 0), mat=MATS['clip'][0], ).solid) # Add a noportal_volume covering the surface, in case there's # room for a portal. noportal_solid = vmf.make_prism( # Don't go all the way to the sides, so it doesn't affect wall # brushes. p1=box_min - (63, 63, 9), p2=box_max + (63, 63, 0), mat='tools/toolsinvisible', ).solid noportal_ent = vmf.create_ent( classname='func_noportal_volume', origin=box_min.join(' '), ) noportal_ent.solids.append(noportal_solid) if SETTINGS['base_is_disp']: # Use displacements for the base instead. make_alpha_base( vmf, box_min + (-64, -64, 0), box_max + (64, 64, 0), noise=alpha_noise, ) for x, y in utils.iter_grid( min_x=int(box_min.x), max_x=int(box_max.x) + 1, min_y=int(box_min.y), max_y=int(box_max.y) + 1, stride=128, ): # Build the set of all positions.. floor_neighbours[z][x, y] = -1 # Mark borders we need to fill in, and the angle (for func_instance) # The wall is the face pointing inwards towards the bottom brush, # and the ceil is the ceiling of the block above the bordering grid # points. for x in range(int(box_min.x), int(box_max.x) + 1, 128): # North floor_edges.append( BorderPoints( wall=Vec(x, box_max.y + 64, z - 64), ceil=Vec_tuple(x, box_max.y + 128, z), rot=270, )) # South floor_edges.append( BorderPoints( wall=Vec(x, box_min.y - 64, z - 64), ceil=Vec_tuple(x, box_min.y - 128, z), rot=90, )) for y in range(int(box_min.y), int(box_max.y) + 1, 128): # East floor_edges.append( BorderPoints( wall=Vec(box_max.x + 64, y, z - 64), ceil=Vec_tuple(box_max.x + 128, y, z), rot=180, )) # West floor_edges.append( BorderPoints( wall=Vec(box_min.x - 64, y, z - 64), ceil=Vec_tuple(box_min.x - 128, y, z), rot=0, )) # Now count boundaries near tiles, then generate them. # Do it separately for each z-level: for z, xy_dict in floor_neighbours.items(): for x, y in xy_dict: # We want to count where there aren't any tiles xy_dict[x, y] = (((x - 128, y - 128) not in xy_dict) + ((x - 128, y + 128) not in xy_dict) + ((x + 128, y - 128) not in xy_dict) + ((x + 128, y + 128) not in xy_dict) + ((x - 128, y) not in xy_dict) + ((x + 128, y) not in xy_dict) + ((x, y - 128) not in xy_dict) + ((x, y + 128) not in xy_dict)) max_x = max_y = 0 weights = {} # Now the counts are all correct, compute the weight to apply # for tiles. # Adding the neighbouring counts will make a 5x5 area needed to set # the center to 0. for (x, y), cur_count in xy_dict.items(): max_x = max(x, max_x) max_y = max(y, max_y) # Orthrogonal is worth 0.2, diagonal is worth 0.1. # Not-present tiles would be 8 - the maximum tile_count = (0.8 * cur_count + 0.1 * xy_dict.get( (x - 128, y - 128), 8) + 0.1 * xy_dict.get( (x - 128, y + 128), 8) + 0.1 * xy_dict.get( (x + 128, y - 128), 8) + 0.1 * xy_dict.get( (x + 128, y + 128), 8) + 0.2 * xy_dict.get( (x - 128, y), 8) + 0.2 * xy_dict.get( (x, y - 128), 8) + 0.2 * xy_dict.get( (x, y + 128), 8) + 0.2 * xy_dict.get( (x + 128, y), 8)) # The number ranges from 0 (all tiles) to 12.8 (no tiles). # All tiles should still have a small chance to generate tiles. weights[x, y] = min((tile_count + 0.5) / 8, 1) # Share the detail entity among same-height tiles.. detail_ent = vmf.create_ent(classname='func_detail', ) for x, y in xy_dict: convert_floor( vmf, Vec(x, y, z), overlay_ids, MATS, SETTINGS, sign_locs, detail_ent, noise_weight=weights[x, y], noise_func=noise, ) add_floor_sides(vmf, floor_edges) return conditions.RES_EXHAUSTED
def generate_world(self, num_foods): """ generate_world(int, int) -> None Initializes the world grid to a grid of cells with smoothly varying height and the specified number of randomly placed foods and water sources. @todo read in a real bay model (@DataBay_MD) """ # Fill grid cells - picking the specified number of food and water sources noise = SimplexNoise() noise.randomize(16) # First generate world full of plain land cells index = 0 if False: for row_num in range(self.world_height): row_list = [] for col_num in range(self.world_width): # Uses Perlin noise to generate elevation # https://pypi.python.org/pypi/noise elev = (1.0 + noise.noise2(col_num / TERRAIN_SMOOTHNESS, row_num / TERRAIN_SMOOTHNESS)) * 5.0 / 2 cell = LandCell(self, [row_num, col_num], elev) row_list.append(cell) index += 1 self.grid.append(row_list) else: lines = open(DATA_FILE).readlines() nxy = self.world_height*self.world_width if len(lines) != nxy: print "bad file ",nxy,len(lines) for row_num in range(self.world_height): row_list = [] for col_num in range(self.world_width): # lines have X,Y,ELEV, for now we ignore X,Y and assume listed in the right order elev = float(lines[index].strip().split()[2]) cell = LandCell(self, [row_num, col_num], elev) row_list.append(cell) index += 1 self.grid.append(row_list) # Calculate elevation extrema for row_num in range(self.world_height): for col_num in range(self.world_width): c = self.grid[row_num][col_num] if c.elevation < self.elevation_min: self.elevation_min = c.elevation if c.elevation > self.elevation_max: self.elevation_max = c.elevation # write to a dump file if False: for row_num in range(self.world_height): for col_num in range(self.world_width): c = self.grid[row_num][col_num] print row_num, col_num, c.elevation # Add some buildings (replace some cells with buildings) num_buildings = NUM_BUILDINGS while num_buildings > 0: row_num = random.randint(0, self.world_height - 1) col_num = random.randint(0, self.world_width - 1) cell = self.grid[row_num][col_num] elevation = cell.get_elevation() if INIT_WATER_LEVEL < elevation < INIT_WATER_LEVEL + 1: building_cell = BuildingCell(self, [row_num, col_num], elevation) self.grid[row_num][col_num] = building_cell num_buildings -= 1 # Add some plants (replace some cells with plants) while num_foods > 0: row_num = random.randint(0, self.world_height - 1) col_num = random.randint(0, self.world_width - 1) cell = self.grid[row_num][col_num] elevation = cell.get_elevation() if INIT_WATER_LEVEL < elevation < self.elevation_max - 1: plant_cell = ArableLandCell(self, [row_num, col_num], elevation) self.grid[row_num][col_num] = plant_cell num_foods -= 1 # Add water sources (replace some cells with water) for water_source_loc in WATER_SOURCES: row_num = water_source_loc[ROW_INDEX] col_num = water_source_loc[COL_INDEX] cell = self.grid[row_num][col_num] elevation = cell.get_elevation() if INIT_WATER_LEVEL < elevation: water_cell = WaterSourceCell(self, [row_num, col_num], elevation) self.grid[row_num][col_num] = water_cell # Add the crabs (replace some water cells with creatures) num_crabs = NUM_CRABS while num_crabs > 0: row_num = random.randint(0, self.world_height - 1) col_num = random.randint(0, self.world_width - 1) cell = self.grid[row_num][col_num] elevation = cell.get_elevation() if elevation < INIT_WATER_LEVEL - 1: crab = Crab([row_num, col_num]) cell.crab = crab self.num_crabs += 1 num_crabs -= 1
def res_cutout_tile(res: Property): """Generate random quarter tiles, like in Destroyed or Retro maps. - "MarkerItem" is the instance to look for. - "TileSize" can be "2x2" or "4x4". - rotateMax is the amount of degrees to rotate squarebeam models. Materials: - "squarebeams" is the squarebeams variant to use. - "ceilingwalls" are the sides of the ceiling section. - "floorbase" is the texture under floor sections. - "tile_glue" is used on top of a thinner tile segment. - "clip" is the player_clip texture used over floor segments. (This allows customising the surfaceprop.) - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to override the textures used. """ item = instanceLocs.resolve(res['markeritem']) INST_LOCS = {} # Map targetnames -> surface loc CEIL_IO = [] # Pairs of ceil inst corners to cut out. FLOOR_IO = [] # Pairs of floor inst corners to cut out. overlay_ids = {} # When we replace brushes, we need to fix any overlays # on that surface. MATS.clear() floor_edges = [] # Values to pass to add_floor_sides() at the end sign_loc = set(FORCE_LOCATIONS) # If any signage is present in the map, we need to force tiles to # appear at that location! for over in conditions.VMF.by_class['info_overlay']: if (over['material'].casefold() in FORCE_TILE_MATS and # Only check floor/ceiling overlays over['basisnormal'] in ('0 0 1', '0 0 -1')): loc = Vec.from_str(over['origin']) # Sometimes (light bridges etc) a sign will be halfway between # tiles, so in that case we need to force 2 tiles. loc_min = (loc - (15, 15, 0)) // 32 * 32 # type: Vec loc_max = (loc + (15, 15, 0)) // 32 * 32 # type: Vec loc_min += (16, 16, 0) loc_max += (16, 16, 0) FORCE_LOCATIONS.add(loc_min.as_tuple()) FORCE_LOCATIONS.add(loc_max.as_tuple()) SETTINGS = { 'floor_chance': srctools.conv_int(res['floorChance', '100'], 100), 'ceil_chance': srctools.conv_int(res['ceilingChance', '100'], 100), 'floor_glue_chance': srctools.conv_int(res['floorGlueChance', '0']), 'ceil_glue_chance': srctools.conv_int(res['ceilingGlueChance', '0']), 'rotate_beams': int(srctools.conv_float(res['rotateMax', '0']) * BEAM_ROT_PRECISION), 'beam_skin': res['squarebeamsSkin', '0'], 'base_is_disp': srctools.conv_bool(res['dispBase', '0']), 'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2', 'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2', } random.seed(vbsp.MAP_RAND_SEED + '_CUTOUT_TILE_NOISE') noise = SimplexNoise(period=4 * 40) # 4 tiles/block, 50 blocks max # We want to know the number of neighbouring tile cutouts before # placing tiles - blocks away from the sides generate fewer tiles. floor_neighbours = defaultdict(dict) # all_floors[z][x,y] = count for mat_prop in res['Materials', []]: MATS[mat_prop.name].append(mat_prop.value) if SETTINGS['base_is_disp']: # We want the normal brushes to become nodraw. MATS['floorbase_disp'] = MATS['floorbase'] MATS['floorbase'] = ['tools/toolsnodraw'] # Since this uses random data for initialisation, the alpha and # regular will use slightly different patterns. alpha_noise = SimplexNoise(period=4 * 50) else: alpha_noise = None for key, default in TEX_DEFAULT: if key not in MATS: MATS[key] = [default] # Find our marker ents for inst in conditions.VMF.by_class['func_instance']: # type: VLib.Entity if inst['file'].casefold() not in item: continue targ = inst['targetname'] orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0']) # Check the orientation of the marker to figure out what to generate if orient == (0, 0, 1): io_list = FLOOR_IO else: io_list = CEIL_IO # Reuse orient to calculate where the solid face will be. loc = (orient * -64) + Vec.from_str(inst['origin']) INST_LOCS[targ] = loc for out in inst.output_targets(): io_list.append((targ, out)) if not inst.outputs and inst.fixup['$connectioncount'] == '0': # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 128x128 tile segment. io_list.append((targ, targ)) inst.remove() # Remove the instance itself from the map. for start_floor, end_floor in FLOOR_IO: if end_floor not in INST_LOCS: # Not a marker - remove this and the antline. for toggle in conditions.VMF.by_target[end_floor]: conditions.remove_ant_toggle(toggle) continue box_min = Vec(INST_LOCS[start_floor]) box_min.min(INST_LOCS[end_floor]) box_max = Vec(INST_LOCS[start_floor]) box_max.max(INST_LOCS[end_floor]) if box_min.z != box_max.z: continue # They're not in the same level! z = box_min.z if SETTINGS['rotate_beams']: # We have to generate 1 model per 64x64 block to do rotation... gen_rotated_squarebeams( box_min - (64, 64, 0), box_max + (64, 64, -8), skin=SETTINGS['beam_skin'], max_rot=SETTINGS['rotate_beams'], ) else: # Make the squarebeams props, using big models if possible gen_squarebeams(box_min + (-64, -64, 0), box_max + (64, 64, -8), skin=SETTINGS['beam_skin']) # Add a player_clip brush across the whole area conditions.VMF.add_brush( conditions.VMF.make_prism( p1=box_min - (64, 64, FLOOR_DEPTH), p2=box_max + (64, 64, 0), mat=MATS['clip'][0], ).solid) # Add a noportal_volume covering the surface, in case there's # room for a portal. noportal_solid = conditions.VMF.make_prism( # Don't go all the way to the sides, so it doesn't affect wall # brushes. p1=box_min - (63, 63, 9), p2=box_max + (63, 63, 0), mat='tools/toolsinvisible', ).solid noportal_ent = conditions.VMF.create_ent( classname='func_noportal_volume', origin=box_min.join(' '), ) noportal_ent.solids.append(noportal_solid) if SETTINGS['base_is_disp']: # Use displacements for the base instead. make_alpha_base( box_min + (-64, -64, 0), box_max + (64, 64, 0), noise=alpha_noise, ) for x, y in utils.iter_grid( min_x=int(box_min.x), max_x=int(box_max.x) + 1, min_y=int(box_min.y), max_y=int(box_max.y) + 1, stride=128, ): # Build the set of all positions.. floor_neighbours[z][x, y] = -1 # Mark borders we need to fill in, and the angle (for func_instance) # The wall is the face pointing inwards towards the bottom brush, # and the ceil is the ceiling of the block above the bordering grid # points. for x in range(int(box_min.x), int(box_max.x) + 1, 128): # North floor_edges.append( BorderPoints( wall=Vec(x, box_max.y + 64, z - 64), ceil=Vec_tuple(x, box_max.y + 128, z), rot=270, )) # South floor_edges.append( BorderPoints( wall=Vec(x, box_min.y - 64, z - 64), ceil=Vec_tuple(x, box_min.y - 128, z), rot=90, )) for y in range(int(box_min.y), int(box_max.y) + 1, 128): # East floor_edges.append( BorderPoints( wall=Vec(box_max.x + 64, y, z - 64), ceil=Vec_tuple(box_max.x + 128, y, z), rot=180, )) # West floor_edges.append( BorderPoints( wall=Vec(box_min.x - 64, y, z - 64), ceil=Vec_tuple(box_min.x - 128, y, z), rot=0, )) # Now count boundries near tiles, then generate them. # Do it seperately for each z-level: for z, xy_dict in floor_neighbours.items(): # type: float, dict for x, y in xy_dict: # type: float, float # We want to count where there aren't any tiles xy_dict[x, y] = (((x - 128, y - 128) not in xy_dict) + ((x - 128, y + 128) not in xy_dict) + ((x + 128, y - 128) not in xy_dict) + ((x + 128, y + 128) not in xy_dict) + ((x - 128, y) not in xy_dict) + ((x + 128, y) not in xy_dict) + ((x, y - 128) not in xy_dict) + ((x, y + 128) not in xy_dict)) max_x = max_y = 0 weights = {} # Now the counts are all correct, compute the weight to apply # for tiles. # Adding the neighbouring counts will make a 5x5 area needed to set # the center to 0. for (x, y), cur_count in xy_dict.items(): max_x = max(x, max_x) max_y = max(y, max_y) # Orthrogonal is worth 0.2, diagonal is worth 0.1. # Not-present tiles would be 8 - the maximum tile_count = (0.8 * cur_count + 0.1 * xy_dict.get( (x - 128, y - 128), 8) + 0.1 * xy_dict.get( (x - 128, y + 128), 8) + 0.1 * xy_dict.get( (x + 128, y - 128), 8) + 0.1 * xy_dict.get( (x + 128, y + 128), 8) + 0.2 * xy_dict.get( (x - 128, y), 8) + 0.2 * xy_dict.get( (x, y - 128), 8) + 0.2 * xy_dict.get( (x, y + 128), 8) + 0.2 * xy_dict.get( (x + 128, y), 8)) # The number ranges from 0 (all tiles) to 12.8 (no tiles). # All tiles should still have a small chance to generate tiles. weights[x, y] = min((tile_count + 0.5) / 8, 1) # Share the detail entity among same-height tiles.. detail_ent = conditions.VMF.create_ent(classname='func_detail', ) for x, y in xy_dict: convert_floor( Vec(x, y, z), overlay_ids, MATS, SETTINGS, sign_loc, detail_ent, noise_weight=weights[x, y], noise_func=noise, ) add_floor_sides(floor_edges) conditions.reallocate_overlays(overlay_ids) return conditions.RES_EXHAUSTED
import ImageFilter from perlin import SimplexNoise OUTPUT_DIR = "../heightmaps/" OUTPUT_PATH = OUTPUT_DIR+"generated.png" IMAGE_WIDTH = 1024 IMAGE_HEIGHT = 1024 frequencies = [300.0, 100.0, 75.0, 50.0, 5.0, 1.0] amplitudes = [1.0, 0.6, 0.3, 0.2, 0.1, 0.05] octaveData = zip(frequencies, amplitudes) img = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), "black") pixelBuffer = img.load() noise = SimplexNoise() noise.randomize() print ("Generating heightmap") for x in range(IMAGE_WIDTH): for y in range(IMAGE_HEIGHT): def octaveContributionToPixel(frequencyAmplitudePair): frequency, amplitude = frequencyAmplitudePair xCoordinate = x / frequency yCoordinate = y / frequency return noise.noise2(xCoordinate, yCoordinate) * amplitude contributionsToPixel = map(octaveContributionToPixel, octaveData) # each amplitude is the maximum amount that octave can contribute to # the pixel, so this gives a pixelValue in the range [-1, 1] pixelValue = sum(contributionsToPixel) / sum(amplitudes)