def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of overhead when working with large mesh-grids. Args: mgrid (numpy.ndarray): A mesh-grid array of points to sample. A contiguous array of type `numpy.float32` is preferred. Returns: numpy.ndarray: An array of sampled points. This array has the shape: ``mgrid.shape[:-1]``. The ``dtype`` is `numpy.float32`. """ mgrid = np.ascontiguousarray(mgrid, np.float32) if mgrid.shape[0] != self.dimensions: raise ValueError( "mgrid.shape[0] must equal self.dimensions, " "%r[0] != %r" % (mgrid.shape, self.dimensions) ) out = np.ndarray(mgrid.shape[1:], np.float32) if mgrid.shape[1:] != out.shape: raise ValueError( "mgrid.shape[1:] must equal out.shape, " "%r[1:] != %r" % (mgrid.shape, out.shape) ) lib.NoiseSampleMeshGrid( self._tdl_noise_c, out.size, ffi.from_buffer("float*", mgrid), ffi.from_buffer("float*", out), ) return out
def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> np.ndarray: """Sample an open mesh-grid array and return the result. Args ogrid (Sequence[Sequence[float]]): An open mesh-grid. Returns: numpy.ndarray: An array of sampled points. The ``shape`` is based on the lengths of the open mesh-grid arrays. The ``dtype`` is `numpy.float32`. """ if len(ogrid) != self.dimensions: raise ValueError( "len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions) ) ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid] out = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( self._tdl_noise_c, len(ogrids), out.shape, [ffi.from_buffer("float*", array) for array in ogrids], ffi.from_buffer("float*", out), ) return out
def __getitem__(self, indexes: Any) -> np.ndarray: """Sample a noise map through NumPy indexing. This follows NumPy's advanced indexing rules, but allows for floating point values. .. versionadded:: 11.16 """ if not isinstance(indexes, tuple): indexes = (indexes,) if len(indexes) > self.dimensions: raise IndexError( "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) ) indexes = np.broadcast_arrays(*indexes) c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): if index.dtype.type == np.object_: raise TypeError("Index arrays can not be of dtype np.object_.") indexes[i] = np.ascontiguousarray(index, dtype=np.float32) c_input[i] = ffi.from_buffer("float*", indexes[i]) out = np.empty(indexes[0].shape, dtype=np.float32) if self.implementation == Implementation.SIMPLE: lib.TCOD_noise_get_vectorized( self.noise_c, self.algorithm, out.size, *c_input, ffi.from_buffer("float*", out), ) elif self.implementation == Implementation.FBM: lib.TCOD_noise_get_fbm_vectorized( self.noise_c, self.algorithm, self.octaves, out.size, *c_input, ffi.from_buffer("float*", out), ) elif self.implementation == Implementation.TURBULENCE: lib.TCOD_noise_get_turbulence_vectorized( self.noise_c, self.algorithm, self.octaves, out.size, *c_input, ffi.from_buffer("float*", out), ) else: raise TypeError("Unexpected %r" % self.implementation) return out
def __init__(self, array: Any): self._array = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape if depth != 3: raise TypeError( "Array must have RGB channels. Shape is: %r" % (self._array.shape,) ) self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) self._mipmaps = ffi.new( "struct TCOD_mipmap_*", { "width": width, "height": height, "fwidth": width, "fheight": height, "buf": self._buffer, "dirty": True, }, ) self.image_c = ffi.new( "TCOD_Image*", { "nb_mipmaps": 1, "mipmaps": self._mipmaps, "has_key_color": False, }, )
def path_from(self, index: Tuple[int, ...]) -> np.ndarray: """Return the shortest path from `index` to the nearest root. The return value is inclusive, including both the starting and ending points on the path. If the root point is unreachable or `index` is already at a root then `index` will be the only point returned. This automatically calls :any:`resolve` if the pathfinder has not yet reached `index`. A common usage is to slice off the starting point and convert the array into a list. """ self.resolve(index) assert len(index) == self._graph._ndim length = _check( lib.get_travel_path( self._graph._ndim, self._travel_p, index, ffi.NULL, )) path = np.ndarray((length, self._graph._ndim), dtype=np.intc) _check( lib.get_travel_path( self._graph._ndim, self._travel_p, index, ffi.from_buffer("int*", path), )) return path
def set_tile(self, codepoint: int, tile: np.ndarray) -> None: """Upload a tile into this array. The tile can be in 32-bit color (height, width, rgba), or grey-scale (height, width). The tile should have a dtype of ``np.uint8``. This data may need to be sent to graphics card memory, this is a slow operation. """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: full_tile = np.empty(self.tile_shape + (4,), dtype=np.uint8) full_tile[:, :, :3] = 255 full_tile[:, :, 3] = tile return self.set_tile(codepoint, full_tile) required = self.tile_shape + (4,) if tile.shape != required: raise ValueError( "Tile shape must be %r or %r, got %r." % (required, self.tile_shape, tile.shape) ) lib.TCOD_tileset_set_tile_( self._tileset_p, codepoint, ffi.from_buffer("struct TCOD_ColorRGBA*", tile), )
def hillclimb2d(distance: np.array, start: Tuple[int, int], cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, *, edge_map: Any = None) -> np.array: """Return a path on a grid from `start` to the lowest point. `distance` should be a fully computed distance array. This kind of array is returned by :any:`dijkstra2d`. `start` is a 2-item tuple with starting coordinates. The axes if these coordinates should match the axis of the `distance` array. An out-of-bounds `start` index will raise an IndexError. At each step nodes adjacent toe current will be checked for a value lower than the current one. Which directions are checked is decided by the boolean values `cardinal` and `diagonal`. This process is repeated until all adjacent nodes are equal to or larger than the last point on the path. If `edge_map` was used with :any:`tcod.path.dijkstra2d` then it should be reused for this function. Keep in mind that `edge_map` must be bidirectional since hill-climbing will traverse the map backwards. The returned array is a 2D NumPy array with the shape: (length, axis). This array always includes both the starting and ending point and will always have at least one item. Typical uses of the returned array will be to either convert it into a list which can be popped from, or transpose it and convert it into a tuple which can be used to index other arrays using NumPy's advanced indexing rules. .. versionadded:: 11.2 .. versionchanged:: 11.13 Added `edge_map` parameter. """ x, y = start dist = np.asarray(distance) if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) c_dist = _export(dist) if edge_map is not None: if cardinal is not None or diagonal is not None: raise TypeError("`edge_map` can not be set at the same time as" " `cardinal` or `diagonal`.") c_edges, n_edges = _compile_bool_edges(edge_map) func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges, c_edges) else: func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal) length = _check(func(ffi.NULL)) path = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.from_buffer("int*", path) _check(func(c_path)) return path
def __as_cdata(self) -> Any: return ffi.new( "struct TCOD_Map*", ( self.width, self.height, self.width * self.height, ffi.from_buffer("struct TCOD_MapCell*", self.__buffer), ), )
def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> np.ndarray: """Return a thin Bresenham line as a NumPy array of shape (length, 2). `start` and `end` are the endpoints of the line. The result always includes both endpoints, and will always contain at least one index. You might want to use the results as is, convert them into a list with :any:`numpy.ndarray.tolist` or transpose them and use that to index another 2D array. Example:: >>> import tcod >>> tcod.los.bresenham((3, 5),(7, 7)).tolist() # Convert into list. [[3, 5], [4, 5], [5, 6], [6, 6], [7, 7]] >>> tcod.los.bresenham((0, 0), (0, 0)) array([[0, 0]]...) >>> tcod.los.bresenham((0, 0), (4, 4))[1:-1] # Clip both endpoints. array([[1, 1], [2, 2], [3, 3]]...) >>> array = np.zeros((5, 5), dtype=np.int8) >>> array array([[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], dtype=int8) >>> tcod.los.bresenham((0, 0), (3, 4)).T # Transposed results. array([[0, 1, 1, 2, 3], [0, 1, 2, 3, 4]]...) >>> indexes_ij = tuple(tcod.los.bresenham((0, 0), (3, 4)).T) >>> array[indexes_ij] = np.arange(len(indexes_ij[0])) >>> array array([[0, 0, 0, 0, 0], [0, 1, 2, 0, 0], [0, 0, 0, 3, 0], [0, 0, 0, 0, 4], [0, 0, 0, 0, 0]], dtype=int8) >>> array[indexes_ij] array([0, 1, 2, 3, 4], dtype=int8) .. versionadded:: 11.14 """ x1, y1 = start x2, y2 = end length = lib.bresenham(x1, y1, x2, y2, 0, ffi.NULL) array = np.ndarray((length, 2), dtype=np.intc) lib.bresenham(x1, y1, x2, y2, length, ffi.from_buffer("int*", array)) return array
def _heightmap_cdata(array: np.ndarray) -> ffi.CData: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. """ if array.flags["F_CONTIGUOUS"]: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: raise ValueError("array must be a contiguous segment.") if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) height, width = array.shape pointer = ffi.from_buffer("float *", array) return ffi.new("TCOD_heightmap_t *", (width, height, pointer))
def get_tile(self, codepoint: int) -> np.ndarray: """Return a copy of a tile for the given codepoint. If the tile does not exist yet then a blank array will be returned. The tile will have a shape of (height, width, rgba) and a dtype of uint8. Note that most grey-scale tiles will only use the alpha channel and will usually have a solid white color channel. """ tile = np.zeros(self.tile_shape + (4, ), dtype=np.uint8) lib.TCOD_tileset_get_tile_( self._tileset_p, codepoint, ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) return tile
def __setstate__(self, state: Any) -> None: self._key_color = None if "_tiles" not in state: tiles = np.ndarray((self.height, self.width), dtype=self.DTYPE) tiles["ch"] = state["_ch"] tiles["fg"][..., :3] = state["_fg"] tiles["fg"][..., 3] = 255 tiles["bg"][..., :3] = state["_bg"] tiles["bg"][..., 3] = 255 state["_tiles"] = tiles del state["_ch"] del state["_fg"] del state["_bg"] self.__dict__.update(state) self._console_data["tiles"] = ffi.from_buffer( "struct TCOD_ConsoleTile*", self._tiles) self._console_data = self.console_c = ffi.new("struct TCOD_Console*", self._console_data)
def __init__( self, width: int, height: int, order: str = "C", buffer: Optional[np.ndarray] = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] self._order = tcod._internal.verify_order(order) if buffer is not None: if self._order == "F": buffer = buffer.transpose() self._tiles = np.ascontiguousarray(buffer, self.DTYPE) else: self._tiles = np.ndarray((height, width), dtype=self.DTYPE) # libtcod uses the root console for defaults. default_bg_blend = 0 default_alignment = 0 if lib.TCOD_ctx.root != ffi.NULL: default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag default_alignment = lib.TCOD_ctx.root.alignment self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", { "w": width, "h": height, "elements": width * height, "tiles": ffi.from_buffer( "struct TCOD_ConsoleTile*", self._tiles ), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, "fore": (255, 255, 255), "back": (0, 0, 0), }, ) if buffer is None: self.clear()
def render(self, console: tcod.console.Console) -> np.ndarray: """Render an RGBA array, using console with this tileset. `console` is the Console object to render, this can not be the root console. The output array will be a np.uint8 array with the shape of: ``(con_height * tile_height, con_width * tile_width, 4)``. .. versionadded:: 11.9 """ if not console: raise ValueError("'console' must not be the root console.") width = console.width * self.tile_width height = console.height * self.tile_height out = np.empty((height, width, 4), np.uint8) out[:] = 9 surface_p = ffi.gc( lib.SDL_CreateRGBSurfaceWithFormatFrom( ffi.from_buffer("void*", out), width, height, 32, out.strides[0], lib.SDL_PIXELFORMAT_RGBA32, ), lib.SDL_FreeSurface, ) with surface_p: with ffi.new("SDL_Surface**", surface_p) as surface_p_p: _check( lib.TCOD_tileset_render_to_surface( self._tileset_p, _console(console), ffi.NULL, surface_p_p, ) ) return out
def __init__(self, pixels: np.ndarray) -> None: self._array = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape, )) if 3 <= self._array.shape[2] <= 4: raise TypeError( "NumPy array must have RGB or RGBA channels. (got %r)" % (self._array.shape, )) self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( ffi.from_buffer("void*", self._array), self._array.shape[1], # Width. self._array.shape[0], # Height. self._array.shape[2] * 8, # Bit depth. self._array.strides[1], # Pitch. 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 if self._array.shape[2] == 4 else 0, ), lib.SDL_FreeSurface, )
def compute_fov( transparency: np.ndarray, pov: Tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, ) -> np.ndarray: """Return a boolean mask of the area covered by a field-of-view. `transparency` is a 2 dimensional array where all non-zero values are considered transparent. The returned array will match the shape of this array. `pov` is the point-of-view origin point. Areas are visible if they can be seen from this position. `pov` should be a 2D index matching the axes of the `transparency` array, and must be within the bounds of the `transparency` array. `radius` is the maximum view distance from `pov`. If this is zero then the maximum distance is used. If `light_walls` is True then visible obstacles will be returned, otherwise only transparent areas will be. `algorithm` is the field-of-view algorithm to run. The default value is `tcod.FOV_RESTRICTIVE`. The options are: * `tcod.FOV_BASIC`: Simple ray-cast implementation. * `tcod.FOV_DIAMOND` * `tcod.FOV_SHADOW`: Recursive shadow caster. * `tcod.FOV_PERMISSIVE(n)`: `n` starts at 0 (most restrictive) and goes up to 8 (most permissive.) * `tcod.FOV_RESTRICTIVE` .. versionadded:: 9.3 .. versionchanged:: 11.0 The parameters `x` and `y` have been changed to `pov`. Example: >>> explored = np.zeros((3, 5), dtype=bool, order="F") >>> transparency = np.ones((3, 5), dtype=bool, order="F") >>> transparency[:2, 2] = False >>> transparency # Transparent area. array([[ True, True, False, True, True], [ True, True, False, True, True], [ True, True, True, True, True]]...) >>> visible = tcod.map.compute_fov(transparency, (0, 0)) >>> visible # Visible area. array([[ True, True, True, False, False], [ True, True, True, False, False], [ True, True, True, True, False]]...) >>> explored |= visible # Keep track of an explored area. .. seealso:: :any:`numpy.where`: For selecting between two arrays using a boolean array, like the one returned by this function. :any:`numpy.select`: Select between arrays based on multiple conditions. """ transparency = np.asarray(transparency) if len(transparency.shape) != 2: raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape) if isinstance(pov, int): raise TypeError( "The tcod.map.compute_fov function has changed. The `x` and `y`" " parameters should now be given as a single tuple.") if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]): warnings.warn( "Given pov index %r is outside the array of shape %r." "\nThis will raise an error in future versions." % (pov, transparency.shape), RuntimeWarning, stacklevel=2, ) map_buffer = np.empty( transparency.shape, dtype=[("transparent", bool), ("walkable", bool), ("fov", bool)], ) map_cdata = ffi.new( "struct TCOD_Map*", ( map_buffer.shape[1], map_buffer.shape[0], map_buffer.shape[1] * map_buffer.shape[0], ffi.from_buffer("struct TCOD_MapCell*", map_buffer), ), ) map_buffer["transparent"] = transparency lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm) return map_buffer["fov"]