Skip to content

Commit

Permalink
Add tile matrix type
Browse files Browse the repository at this point in the history
  • Loading branch information
albin-johansson committed Mar 21, 2024
1 parent 3b4c837 commit 6077f8c
Show file tree
Hide file tree
Showing 3 changed files with 498 additions and 0 deletions.
163 changes: 163 additions & 0 deletions source/core/inc/tactile/core/layer/tile_matrix.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#pragma once

#include <ostream> // ostream

#include "tactile/base/container/vector.hpp"
#include "tactile/base/id.hpp"
#include "tactile/base/prelude.hpp"

namespace tactile {

/**
* Represents a location in a two-dimensional matrix.
*/
struct MatrixIndex final {
usize row; ///< The row index.
usize col; ///< The column index.

[[nodiscard]] auto operator==(const MatrixIndex&) const noexcept -> bool = default;
};

/**
* Outputs a matrix index to a stream.
*
* \param stream The output stream.
* \param index The matrix index to emit.
*
* \return
* The provided stream.
*/
auto operator<<(std::ostream& stream, const MatrixIndex& index) -> std::ostream&;

/**
* Represents the size of a two-dimensional matrix.
*/
struct MatrixExtent final {
usize rows; ///< The number of rows.
usize cols; ///< The number of columns.

[[nodiscard]] auto operator==(const MatrixExtent&) const noexcept -> bool = default;
};

/**
* Outputs a matrix extent to a stream.
*
* \param stream The output stream.
* \param extent The matrix extent to emit.
*
* \return
* The provided stream.
*/
auto operator<<(std::ostream& stream, const MatrixExtent& extent) -> std::ostream&;

/**
* Represents a two-dimensional grid of tile identifiers.
*/
class TileMatrix final {
public:
/**
* Creates an empty tile matrix with extent (0, 0).
*/
TileMatrix() noexcept = default;

/**
* Creates an empty tile matrix with a given extent.
*
* \param extent The initial extent.
*/
explicit TileMatrix(const MatrixExtent& extent);

TACTILE_DEFAULT_COPY(TileMatrix);
TACTILE_DEFAULT_MOVE(TileMatrix);

~TileMatrix() noexcept = default;

/**
* Changes the extent of the matrix.
*
* \param new_extent The new extent.
*/
void resize(const MatrixExtent& new_extent);

/**
* Sets the number of rows in the matrix.
*
* \param rows The new number of rows.
*/
void set_row_count(usize rows);

/**
* Sets the number of columns in the matrix.
*
* \param cols The new number of columns.
*/
void set_column_count(usize cols);

/**
* Returns the tile identifier at a given index.
*
* \param index The index of the desired tile.
*
* \return
* A tile identifier.
*
* \throw Exception if the index is invalid.
*/
[[nodiscard]]
auto at(MatrixIndex index) -> TileID&;

/**
* \copydoc at
*/
[[nodiscard]]
auto at(MatrixIndex index) const -> TileID;

/**
* Returns the tile identifier at a given index.
*
* \note
* This function performs no bounds checking.
*
* \pre
* The provided index must be valid.
*
* \param index The index of the desired tile.
*
* \return
* A tile identifier.
*/
[[nodiscard]] auto operator[](MatrixIndex index) noexcept -> TileID&;

/**
* \copydoc operator[]
*/
[[nodiscard]] auto operator[](MatrixIndex index) const noexcept -> TileID;

/**
* Indicates whether an index is valid, i.e., whether it refers to a tile in the matrix.
*
* \param index The index that will be checked.
*
* \return
* True if the index is valid; false otherwise.
*/
[[nodiscard]]
auto is_valid(const MatrixIndex& index) const noexcept -> bool;

/**
* Returns the current extent of the matrix.
*
* \return
* A matrix extent.
*/
[[nodiscard]]
auto get_extent() const noexcept -> const MatrixExtent&;

private:
MatrixExtent mExtent {0, 0};
Vector<Vector<TileID>> mRows {};
};

} // namespace tactile
144 changes: 144 additions & 0 deletions source/core/src/tactile/core/layer/tile_matrix.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#include "tactile/core/layer/tile_matrix.hpp"

#include "tactile/core/debug/assert.hpp"
#include "tactile/core/debug/exception.hpp"

namespace tactile {
namespace {

void _add_rows(Vector<Vector<TileID>>& rows, const usize n, const usize cols)
{
rows.reserve(rows.size() + n);
for (usize i = 0; i < n; ++i) {
rows.emplace_back(cols, kEmptyTile);
}
}

void _add_columns(Vector<Vector<TileID>>& rows, const usize n)
{
for (auto& row : rows) {
row.reserve(row.size() + n);
for (usize i = 0; i < n; ++i) {
row.push_back(kEmptyTile);
}
}
}

void _remove_rows(Vector<Vector<TileID>>& rows, const usize n)
{
for (usize i = 0; i < n; ++i) {
TACTILE_ASSERT(!rows.empty());
rows.pop_back();
}
}

void _remove_columns(Vector<Vector<TileID>>& rows, const usize n)
{
for (auto& row : rows) {
for (usize i = 0; i < n; ++i) {
TACTILE_ASSERT(!row.empty());
row.pop_back();
}
}
}

} // namespace

auto operator<<(std::ostream& stream, const MatrixIndex& index) -> std::ostream&
{
stream << '(' << index.row << ';' << index.col << ')';
return stream;
}

auto operator<<(std::ostream& stream, const MatrixExtent& extent) -> std::ostream&
{
stream << '(' << extent.rows << ';' << extent.cols << ')';
return stream;
}

TileMatrix::TileMatrix(const MatrixExtent& extent)
: mExtent {extent}
{
mRows.reserve(mExtent.rows);
mRows.assign(mExtent.rows, Vector<TileID>(mExtent.cols, kEmptyTile));
}

void TileMatrix::resize(const MatrixExtent& new_extent)
{
set_column_count(new_extent.cols);
set_row_count(new_extent.rows);
}

void TileMatrix::set_row_count(const usize rows)
{
if (rows != mExtent.rows) {
if (rows > mExtent.rows) {
_add_rows(mRows, rows - mExtent.rows, mExtent.cols);
}
else {
_remove_rows(mRows, mExtent.rows - rows);
}

mExtent.rows = rows;
}
}

void TileMatrix::set_column_count(const usize cols)
{
if (cols != mExtent.cols) {
if (cols > mExtent.cols) {
_add_columns(mRows, cols - mExtent.cols);
}
else {
_remove_columns(mRows, mExtent.cols - cols);
}

mExtent.cols = cols;
}
}

auto TileMatrix::at(const MatrixIndex index) -> TileID&
{
if (is_valid(index)) [[likely]] {
return operator[](index);
}

throw Exception {"bad matrix index"};
}

auto TileMatrix::at(const MatrixIndex index) const -> TileID
{
if (is_valid(index)) [[likely]] {
return operator[](index);
}

throw Exception {"bad matrix index"};
}

auto TileMatrix::operator[](const MatrixIndex index) noexcept -> TileID&
{
TACTILE_ASSERT_MSG(index.row < mExtent.rows, "bad row index");
TACTILE_ASSERT_MSG(index.col < mExtent.cols, "bad column index");
return mRows[index.row][index.col];
}

auto TileMatrix::operator[](const MatrixIndex index) const noexcept -> TileID
{
TACTILE_ASSERT_MSG(index.row < mExtent.rows, "bad row index");
TACTILE_ASSERT_MSG(index.col < mExtent.cols, "bad column index");
return mRows[index.row][index.col];
}

auto TileMatrix::is_valid(const MatrixIndex& index) const noexcept -> bool
{
return (index.row < mExtent.rows) && (index.col < mExtent.cols);
}

auto TileMatrix::get_extent() const noexcept -> const MatrixExtent&
{
return mExtent;
}

} // namespace tactile
Loading

0 comments on commit 6077f8c

Please sign in to comment.