6352e8e319
all at once
490 lines
19 KiB
C++
490 lines
19 KiB
C++
#ifndef STAR_TILE_SECTOR_ARRAY_HPP
|
|
#define STAR_TILE_SECTOR_ARRAY_HPP
|
|
|
|
#include "StarRect.hpp"
|
|
#include "StarSectorArray2D.hpp"
|
|
|
|
namespace Star {
|
|
|
|
// Storage container for world tiles that understands the sector based
|
|
// non-euclidean nature of the World.
|
|
//
|
|
// All RectI regions in this class are assumed to be right/top exclusive, so
|
|
// each tile covered by a RectI region must be strictly contained within the
|
|
// region to be included.
|
|
template <typename TileT, unsigned SectorSizeT>
|
|
class TileSectorArray {
|
|
public:
|
|
typedef TileT Tile;
|
|
static unsigned const SectorSize = SectorSizeT;
|
|
|
|
typedef SectorArray2D<Tile, SectorSize> SectorArray;
|
|
typedef typename SectorArray::Sector Sector;
|
|
typedef typename SectorArray::Array Array;
|
|
typedef typename SectorArray::ArrayPtr ArrayPtr;
|
|
|
|
TileSectorArray();
|
|
TileSectorArray(Vec2U const& size, Tile defaultTile = Tile());
|
|
|
|
void init(Vec2U const& size, Tile defaultTile = Tile());
|
|
|
|
Vec2U size() const;
|
|
Tile defaultTile() const;
|
|
|
|
// Returns true if this sector is within the size bounds, regardless of
|
|
// loaded / unloaded status.
|
|
bool sectorValid(Sector const& sector) const;
|
|
|
|
Sector sectorFor(Vec2I const& pos) const;
|
|
|
|
// Return all valid sectors within a given range, regardless of loaded /
|
|
// unloaded status.
|
|
List<Sector> validSectorsFor(RectI const& region) const;
|
|
|
|
// Returns the region for this sector, which is SectorSize x SectorSize
|
|
// large.
|
|
RectI sectorRegion(Sector const& sector) const;
|
|
|
|
// Returns adjacent sectors in any given integral movement, in sectors.
|
|
Sector adjacentSector(Sector const& sector, Vec2I const& sectorMovement);
|
|
|
|
// Load a sector into the active sector array.
|
|
void loadSector(Sector const& sector, ArrayPtr array);
|
|
// Load with a sector full of the default tile.
|
|
void loadDefaultSector(Sector const& sector);
|
|
// Make a copy of a sector
|
|
ArrayPtr copySector(Sector const& sector);
|
|
// Take a sector out of the sector array.
|
|
ArrayPtr unloadSector(Sector const& sector);
|
|
|
|
bool sectorLoaded(Sector sector) const;
|
|
List<Sector> loadedSectors() const;
|
|
size_t loadedSectorCount() const;
|
|
|
|
// Will return null if the sector is unloaded.
|
|
Array const* sectorArray(Sector sector) const;
|
|
Array* sectorArray(Sector sector);
|
|
|
|
bool tileLoaded(Vec2I const& pos) const;
|
|
|
|
Tile const& tile(Vec2I const& pos) const;
|
|
|
|
// Will return nullptr if the position is invalid.
|
|
Tile* modifyTile(Vec2I const& pos);
|
|
|
|
// Function signature here is (Vec2I const&, Tile const&). Will be called
|
|
// for the entire region, valid or not. If tile positions are not valid,
|
|
// they will be called with the defaultTile.
|
|
template <typename Function>
|
|
void tileEach(RectI const& region, Function&& function) const;
|
|
|
|
// Behaves like tileEach, but gathers the results of calling the function into
|
|
// a MultiArray
|
|
template <typename Function>
|
|
MultiArray<std::result_of_t<Function(Vec2I, Tile)>, 2> tileEachResult(RectI const& region, Function&& function) const;
|
|
|
|
// Fastest way to copy data from the tile array to a given target array.
|
|
// Takes a multi-array and a region and a function, resizes the multi-array
|
|
// to be the size of the given region, and then calls the given function on
|
|
// each tile in the region with this signature:
|
|
// function(MultiArray::Element& target, Vec2I const& position, Tile const& source);
|
|
// Called with the defaultTile for out of range positions.
|
|
template <typename MultiArray, typename Function>
|
|
void tileEachTo(MultiArray& results, RectI const& region, Function&& function) const;
|
|
|
|
// Function signature here is (Vec2I const&, Tile&). If a tile position
|
|
// within this range is not valid, the function *will not* be called for that
|
|
// position.
|
|
template <typename Function>
|
|
void tileEval(RectI const& region, Function&& function);
|
|
|
|
// Will not be called for parts of the region that are not valid positions.
|
|
template <typename Function>
|
|
void tileEachColumns(RectI const& region, Function&& function) const;
|
|
template <typename Function>
|
|
void tileEvalColumns(RectI const& region, Function&& function);
|
|
|
|
// Searches for a tile that satisfies a given condition in a block-area.
|
|
// Returns true on the first instance found. Passed in function must accept
|
|
// (Vec2I const&, Tile const&).
|
|
template <typename Function>
|
|
bool tileSatisfies(RectI const& region, Function&& function) const;
|
|
// Same, but uses a radius of 'distance', which is inclusive on all sides.
|
|
// In other words, calling tileSatisfies({0, 0}, 1, <func>) should be
|
|
// equivalent to calling tileSatisfies({-1, -1}, {3, 3}, <func>).
|
|
template <typename Function>
|
|
bool tileSatisfies(Vec2I const& pos, unsigned distance, Function&& function) const;
|
|
|
|
private:
|
|
struct SplitRect {
|
|
RectI rect;
|
|
int xOffset;
|
|
};
|
|
|
|
// function must return bool to continue iteration
|
|
template <typename Function>
|
|
bool tileEachAbortable(RectI const& region, Function&& function) const;
|
|
|
|
// Splits rects along the world wrap line and wraps the x coordinate for each
|
|
// rect into world space. Also returns the integral x offset to transform
|
|
// back into the input rect range.
|
|
StaticList<SplitRect, 2> splitRect(RectI rect) const;
|
|
|
|
// Clamp the rect to entirely within valid tile spaces in y dimension
|
|
RectI yClampRect(RectI const& r) const;
|
|
|
|
Vec2U m_worldSize;
|
|
Tile m_default;
|
|
SectorArray m_tileSectors;
|
|
};
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
unsigned const TileSectorArray<Tile, SectorSize>::SectorSize;
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
TileSectorArray<Tile, SectorSize>::TileSectorArray() {}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
TileSectorArray<Tile, SectorSize>::TileSectorArray(Vec2U const& size, Tile defaultTile) {
|
|
init(size, move(defaultTile));
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
void TileSectorArray<Tile, SectorSize>::init(Vec2U const& size, Tile defaultTile) {
|
|
m_worldSize = size;
|
|
// Initialize to enough sectors to fit world size at least.
|
|
m_tileSectors.init((size[0] + SectorSize - 1) / SectorSize, (size[1] + SectorSize - 1) / SectorSize);
|
|
m_default = move(defaultTile);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
Vec2U TileSectorArray<Tile, SectorSize>::size() const {
|
|
return m_worldSize;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
Tile TileSectorArray<Tile, SectorSize>::defaultTile() const {
|
|
return m_default;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::sectorFor(Vec2I const& pos) const -> Sector {
|
|
return m_tileSectors.sectorFor((unsigned)pmod<int>(pos[0], m_worldSize[0]), (unsigned)pos[1]);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
bool TileSectorArray<Tile, SectorSize>::sectorValid(Sector const& sector) const {
|
|
return m_tileSectors.sectorValid(sector);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::validSectorsFor(RectI const& region) const -> List<Sector> {
|
|
List<Sector> sectors;
|
|
for (auto const& split : splitRect(yClampRect(region))) {
|
|
auto sectorRange = m_tileSectors.sectorRange(split.rect.xMin(), split.rect.yMin(), split.rect.width(), split.rect.height());
|
|
sectors.reserve(sectors.size() + (sectorRange.max[0] - sectorRange.min[0]) * (sectorRange.max[1] - sectorRange.min[1]));
|
|
for (size_t x = sectorRange.min[0]; x < sectorRange.max[0]; ++x) {
|
|
for (size_t y = sectorRange.min[1]; y < sectorRange.max[1]; ++y)
|
|
sectors.append({x, y});
|
|
}
|
|
}
|
|
return sectors;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
RectI TileSectorArray<Tile, SectorSize>::sectorRegion(Sector const& sector) const {
|
|
Vec2I sectorCorner(m_tileSectors.sectorCorner(sector));
|
|
return RectI::withSize(sectorCorner, {min<int>(SectorSize, m_worldSize[0] - sectorCorner[0]), min<int>(SectorSize, m_worldSize[1] - sectorCorner[1])});
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::adjacentSector(Sector const& sector, Vec2I const& sectorMovement) -> Sector {
|
|
// This works because the only smaller than SectorSize sectors are on the
|
|
// world wrap point, and there is only one vertical line of them, but it's
|
|
// very not-obvious that it works.
|
|
Vec2I corner(m_tileSectors.sectorCorner(sector));
|
|
corner += sectorMovement * SectorSize;
|
|
return sectorFor(corner);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
void TileSectorArray<Tile, SectorSize>::loadSector(Sector const& sector, ArrayPtr tile) {
|
|
if (sectorValid(sector))
|
|
m_tileSectors.loadSector(sector, move(tile));
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
void TileSectorArray<Tile, SectorSize>::loadDefaultSector(Sector const& sector) {
|
|
if (sectorValid(sector))
|
|
m_tileSectors.loadSector(sector, make_unique<Array>(m_default));
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::copySector(Sector const& sector) -> ArrayPtr {
|
|
if (sectorValid(sector))
|
|
return m_tileSectors.copySector(sector);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::unloadSector(Sector const& sector) -> ArrayPtr {
|
|
if (sectorValid(sector))
|
|
return m_tileSectors.takeSector(sector);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
bool TileSectorArray<Tile, SectorSize>::sectorLoaded(Sector sector) const {
|
|
if (sectorValid(sector))
|
|
return m_tileSectors.sectorLoaded(sector);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::loadedSectors() const -> List<Sector> {
|
|
return m_tileSectors.loadedSectors();
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
size_t TileSectorArray<Tile, SectorSize>::loadedSectorCount() const {
|
|
return m_tileSectors.loadedSectorCount();
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::sectorArray(Sector sector) const -> Array const * {
|
|
if (sectorValid(sector))
|
|
return m_tileSectors.sector(sector);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::sectorArray(Sector sector) -> Array * {
|
|
if (sectorValid(sector))
|
|
return m_tileSectors.sector(sector);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
bool TileSectorArray<Tile, SectorSize>::tileLoaded(Vec2I const& pos) const {
|
|
if (pos[1] < 0 || pos[1] >= (int)m_worldSize[1])
|
|
return false;
|
|
|
|
unsigned xind = (unsigned)pmod<int>(pos[0], m_worldSize[0]);
|
|
unsigned yind = (unsigned)pos[1];
|
|
|
|
return m_tileSectors.get(xind, yind) != nullptr;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
Tile const& TileSectorArray<Tile, SectorSize>::tile(Vec2I const& pos) const {
|
|
if (pos[1] < 0 || pos[1] >= (int)m_worldSize[1])
|
|
return m_default;
|
|
|
|
unsigned xind = (unsigned)pmod<int>(pos[0], m_worldSize[0]);
|
|
unsigned yind = (unsigned)pos[1];
|
|
|
|
Tile const* tile = m_tileSectors.get(xind, yind);
|
|
if (tile)
|
|
return *tile;
|
|
else
|
|
return m_default;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
Tile* TileSectorArray<Tile, SectorSize>::modifyTile(Vec2I const& pos) {
|
|
if (pos[1] < 0 || pos[1] >= (int)m_worldSize[1])
|
|
return nullptr;
|
|
|
|
unsigned xind = (unsigned)pmod<int>(pos[0], m_worldSize[0]);
|
|
unsigned yind = (unsigned)pos[1];
|
|
|
|
return m_tileSectors.get(xind, yind);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
void TileSectorArray<Tile, SectorSize>::tileEach(RectI const& region, Function&& function) const {
|
|
tileEachAbortable(region,
|
|
[&](Vec2I const& pos, Tile const& tile) {
|
|
function(pos, tile);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
MultiArray<std::result_of_t<Function(Vec2I, Tile)>, 2> TileSectorArray<Tile, SectorSize>::tileEachResult(RectI const& region, Function&& function) const {
|
|
MultiArray<std::result_of_t<Function(Vec2I, Tile)>, 2> res;
|
|
tileEachTo(res, region, [&](auto& res, Vec2I const& pos, Tile const& tile) { res = function(pos, tile); });
|
|
return res;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename MultiArray, typename Function>
|
|
void TileSectorArray<Tile, SectorSize>::tileEachTo(MultiArray& results, RectI const& region, Function&& function) const {
|
|
if (region.isEmpty()) {
|
|
results.setSize({0, 0});
|
|
return;
|
|
}
|
|
|
|
int xArrayOffset = -region.xMin();
|
|
int yArrayOffset = -region.yMin();
|
|
results.setSize(Vec2S(region.size()));
|
|
|
|
for (auto const& split : splitRect(region)) {
|
|
auto clampedRect = yClampRect(split.rect);
|
|
if (!clampedRect.isEmpty()) {
|
|
m_tileSectors.evalColumns(clampedRect.xMin(), clampedRect.yMin(), clampedRect.width(), clampedRect.height(), [&](size_t x, size_t y, Tile const* column, size_t columnSize) {
|
|
size_t arrayColumnIndex = (x + split.xOffset + xArrayOffset) * results.size(1) + y + yArrayOffset;
|
|
if (column) {
|
|
for (size_t i = 0; i < columnSize; ++i)
|
|
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y), column[i]);
|
|
} else {
|
|
for (size_t i = 0; i < columnSize; ++i)
|
|
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y), m_default);
|
|
}
|
|
return true;
|
|
}, true);
|
|
}
|
|
|
|
// Call with default tile for tiles outside of the y-range (to ensure that
|
|
// every index in the rect gets called)
|
|
for (int x = split.rect.xMin(); x < split.rect.xMax(); ++x) {
|
|
for (int y = split.rect.yMin(); y < min<int>(split.rect.yMax(), 0); ++y)
|
|
function(results(x + split.xOffset + xArrayOffset, y + yArrayOffset), Vec2I(x + split.xOffset, y), m_default);
|
|
}
|
|
|
|
for (int x = split.rect.xMin(); x < split.rect.xMax(); ++x) {
|
|
for (int y = max<int>(split.rect.yMin(), m_worldSize[1]); y < split.rect.yMax(); ++y)
|
|
function(results(x + split.xOffset + xArrayOffset, y + yArrayOffset), Vec2I(x + split.xOffset, y), m_default);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
void TileSectorArray<Tile, SectorSize>::tileEval(RectI const& region, Function&& function) {
|
|
for (auto const& split : splitRect(region)) {
|
|
auto clampedRect = yClampRect(split.rect);
|
|
if (!clampedRect.isEmpty()) {
|
|
// If non-const variant, do not call function if tile not loaded (pass
|
|
// false to evalEmpty in sector array)
|
|
auto fwrapper = [&](unsigned x, unsigned y, Tile* tile) {
|
|
function(Vec2I((int)x + split.xOffset, (int)y), *tile);
|
|
return true;
|
|
};
|
|
m_tileSectors.eval(clampedRect.xMin(), clampedRect.yMin(), clampedRect.width(), clampedRect.height(), fwrapper, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
void TileSectorArray<Tile, SectorSize>::tileEachColumns(RectI const& region, Function&& function) const {
|
|
const_cast<TileSectorArray*>(this)->tileEvalColumns(
|
|
region, [&](Vec2I const& pos, Tile* tiles, size_t size) { function(pos, (Tile const*)tiles, size); });
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
void TileSectorArray<Tile, SectorSize>::tileEvalColumns(RectI const& region, Function&& function) {
|
|
for (auto const& split : splitRect(region)) {
|
|
auto clampedRect = yClampRect(split.rect);
|
|
if (!clampedRect.isEmpty()) {
|
|
auto fwrapper = [&](size_t x, size_t y, Tile* column, size_t columnSize) {
|
|
function(Vec2I((int)x + split.xOffset, (int)y), column, columnSize);
|
|
return true;
|
|
};
|
|
m_tileSectors.evalColumns(clampedRect.xMin(), clampedRect.yMin(), clampedRect.width(), clampedRect.height(), fwrapper, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
bool TileSectorArray<Tile, SectorSize>::tileSatisfies(Vec2I const& pos, unsigned distance, Function&& function) const {
|
|
return tileSatisfies(RectI::withSize(pos - Vec2I::filled(distance), Vec2I::filled(distance * 2 + 1)), function);
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
bool TileSectorArray<Tile, SectorSize>::tileSatisfies(RectI const& region, Function&& function) const {
|
|
return !tileEachAbortable(region, [&](Vec2I const& pos, Tile const& tile) { return !function(pos, tile); });
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
template <typename Function>
|
|
bool TileSectorArray<Tile, SectorSize>::tileEachAbortable(RectI const& region, Function&& function) const {
|
|
for (auto const& split : splitRect(region)) {
|
|
auto clampedRect = yClampRect(split.rect);
|
|
if (!clampedRect.isEmpty()) {
|
|
// If const variant, call function with default tile if not loaded.
|
|
auto fwrapper = [&](unsigned x, unsigned y, Tile const* tile) {
|
|
if (!tile)
|
|
tile = &m_default;
|
|
return function(Vec2I((int)x + split.xOffset, y), *tile);
|
|
};
|
|
if (!m_tileSectors.eval(clampedRect.xMin(), clampedRect.yMin(), clampedRect.width(), clampedRect.height(), fwrapper, true))
|
|
return false;
|
|
}
|
|
|
|
// Call with default tile for tiles outside of the y-range (to ensure
|
|
// that every index in the rect gets called)
|
|
for (int x = split.rect.xMin(); x < split.rect.xMax(); ++x) {
|
|
for (int y = split.rect.yMin(); y < min<int>(split.rect.yMax(), 0); ++y) {
|
|
if (!function(Vec2I(x + split.xOffset, y), m_default))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (int x = split.rect.xMin(); x < split.rect.xMax(); ++x) {
|
|
for (int y = max<int>(split.rect.yMin(), m_worldSize[1]); y < split.rect.yMax(); ++y)
|
|
if (!function(Vec2I(x + split.xOffset, y), m_default))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
auto TileSectorArray<Tile, SectorSize>::splitRect(RectI rect) const -> StaticList<SplitRect, 2> {
|
|
// TODO: Offset here does not support rects outside of -m_worldSize[0] to 2 * m_worldSize[0]!
|
|
starAssert(rect.xMin() >= -(int)m_worldSize[0] && rect.xMax() <= 2 * (int)m_worldSize[0]);
|
|
|
|
// any rect at least the width of the world is equivalent to a rect that spans the width of the world exactly
|
|
if (rect.width() >= (int)m_worldSize[0])
|
|
return{SplitRect{RectI(0, rect.yMin(), m_worldSize[0], rect.yMax()), 0}};
|
|
|
|
if (rect.isEmpty())
|
|
return {};
|
|
|
|
int width = rect.width();
|
|
int xMin = pmod<int>(rect.xMin(), m_worldSize[0]);
|
|
int xOffset = rect.xMin() - xMin;
|
|
rect.setXMin(xMin);
|
|
rect.setXMax(xMin + width);
|
|
|
|
if (rect.xMin() < (int)m_worldSize[0] && rect.xMax() > (int)m_worldSize[0]) {
|
|
return {
|
|
SplitRect{RectI(rect.xMin(), rect.yMin(), m_worldSize[0], rect.yMax()), xOffset},
|
|
SplitRect{RectI(0, rect.yMin(), rect.xMax() - m_worldSize[0], rect.yMax()), xOffset + (int)m_worldSize[0]}
|
|
};
|
|
} else {
|
|
return {SplitRect{rect, xOffset}};
|
|
}
|
|
}
|
|
|
|
template <typename Tile, unsigned SectorSize>
|
|
RectI TileSectorArray<Tile, SectorSize>::yClampRect(RectI const& r) const {
|
|
return RectI(r.xMin(), clamp<int>(r.yMin(), 0, m_worldSize[1]), r.xMax(), clamp<int>(r.yMax(), 0, m_worldSize[1]));
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|