Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
 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,
         },
     )
Exemple #5
0
    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
Exemple #6
0
    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),
        )
Exemple #7
0
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
Exemple #8
0
 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),
         ),
     )
Exemple #9
0
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
Exemple #10
0
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))
Exemple #11
0
    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
Exemple #12
0
    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)
Exemple #13
0
    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()
Exemple #14
0
    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
Exemple #15
0
 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,
     )
Exemple #16
0
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"]