File coordinate.hpp
File List > lib > utilities > coordinate.hpp
Go to the documentation of this file
#pragma once
#include <array>
#include <cmath>
#include <iostream>
#include <ostream>
#include <type_traits>
#include <vector>
#include "assert/assert.hpp"
class Direction2D {
public:
enum Dir { UP, DOWN, LEFT, RIGHT, UR, UL, DR, DL };
private:
Dir m_dir;
public:
Direction2D(Dir dir) : m_dir(dir) {}
operator Dir() const { return m_dir; }
std::array<Direction2D, 2> orthogonal_dirs() const {
if (m_dir == DOWN || m_dir == UP) return {{LEFT, RIGHT}};
return {{UP, DOWN}};
}
bool orthogonal_to(Direction2D other) const {
return ((m_dir == UP || m_dir == DOWN) && (other.m_dir == LEFT || other.m_dir == RIGHT)) ||
((m_dir == LEFT || m_dir == RIGHT) && (other.m_dir == UP || other.m_dir == DOWN));
}
int dx() const {
switch (m_dir) {
case UP:
case DOWN:
return 0;
case LEFT:
case UL:
case DL:
return -1;
case RIGHT:
case UR:
case DR:
return 1;
}
unreachable();
}
int dy() const {
switch (m_dir) {
case UP:
case UR:
case UL:
return -1;
case DOWN:
case DR:
case DL:
return 1;
case LEFT:
case RIGHT:
return 0;
}
unreachable();
}
Direction2D opposite() const {
switch (m_dir) {
case UP:
return Direction2D(DOWN);
case DOWN:
return Direction2D(UP);
case LEFT:
return Direction2D(RIGHT);
case RIGHT:
return Direction2D(LEFT);
case UR:
return Direction2D(DL);
case UL:
return Direction2D(DR);
case DR:
return Direction2D(UL);
case DL:
return Direction2D(UR);
}
unreachable();
}
friend std::ostream& operator<<(std::ostream& os, const Direction2D& dir) {
switch (dir.m_dir) {
case UP:
os << "UP";
break;
case DOWN:
os << "DOWN";
break;
case LEFT:
os << "LEFT";
break;
case RIGHT:
os << "RIGHT";
break;
case UR:
os << "UR";
break;
case UL:
os << "UL";
break;
case DR:
os << "DR";
break;
case DL:
os << "DL";
break;
}
return os;
}
};
const std::array<Direction2D, 4> ORTHOGONAL_DIRECTIONS = {
Direction2D(Direction2D::UP), Direction2D(Direction2D::DOWN), Direction2D(Direction2D::LEFT),
Direction2D(Direction2D::RIGHT)};
const std::array<Direction2D, 8> ALL_DIRECTIONS = {
Direction2D(Direction2D::UP), Direction2D(Direction2D::DOWN), Direction2D(Direction2D::LEFT),
Direction2D(Direction2D::RIGHT), Direction2D(Direction2D::UR), Direction2D(Direction2D::UL),
Direction2D(Direction2D::DR), Direction2D(Direction2D::DL)};
template <typename T>
class Coordinate2D {
static_assert(std::is_arithmetic<T>::value, "Coordinate2D only supports arithmetic types");
std::array<T, 2> m_data;
public:
Coordinate2D(T x, T y) : m_data{{x, y}} {}
Coordinate2D() = default;
const T& x() const { return m_data[0]; }
const T& y() const { return m_data[1]; }
T& x() { return m_data[0]; }
T& y() { return m_data[1]; }
template <typename U>
operator Coordinate2D<U>() const {
return Coordinate2D<U>(x(), y());
}
Coordinate2D<double> offset_to_center() const {
return Coordinate2D<double>(x() + 0.5, y() + 0.5);
}
Coordinate2D<double> round_NW(double grid_size) const {
return Coordinate2D<double>(x() - fmod(x(), grid_size), y() + grid_size - fmod(y(), grid_size));
}
Coordinate2D<size_t> round() const { return Coordinate2D<size_t>(x() + 0.5, y() + 0.5); }
friend std::ostream& operator<<(std::ostream& os, const Coordinate2D& coord) {
os << "Coordinate2D(" << coord.x() << ", " << coord.y() << ")";
return os;
}
Coordinate2D operator+(Direction2D dir) const {
switch (dir) {
case Direction2D::UP:
return Coordinate2D(x(), y() - 1);
case Direction2D::DOWN:
return Coordinate2D(x(), y() + 1);
case Direction2D::LEFT:
return Coordinate2D(x() - 1, y());
case Direction2D::RIGHT:
return Coordinate2D(x() + 1, y());
case Direction2D::UR:
return Coordinate2D(x() + 1, y() - 1);
case Direction2D::UL:
return Coordinate2D(x() - 1, y() - 1);
case Direction2D::DR:
return Coordinate2D(x() + 1, y() + 1);
case Direction2D::DL:
return Coordinate2D(x() - 1, y() + 1);
}
unreachable();
}
Coordinate2D operator+(Coordinate2D o) const { return Coordinate2D(x() + o.x(), y() + o.y()); }
Coordinate2D operator-(Coordinate2D o) const { return Coordinate2D(x() - o.x(), y() - o.y()); }
bool operator==(const Coordinate2D& o) const { return x() == o.x() && y() == o.y(); }
T magnitude_sqd() const { return x() * x() + y() * y(); }
T magnitude() const { return std::sqrt(magnitude_sqd()); }
};
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
std::array<Coordinate2D<T>, 4> orthogonal_neighbors(const Coordinate2D<T>& coord) {
return {Coordinate2D<T>(coord.x() - 1, coord.y()), Coordinate2D<T>(coord.x() + 1, coord.y()),
Coordinate2D<T>(coord.x(), coord.y() - 1), Coordinate2D<T>(coord.x(), coord.y() + 1)};
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
class LineCoord2D : public Coordinate2D<T> {
Direction2D m_dir;
public:
LineCoord2D(T x, T y, Direction2D dir) : Coordinate2D<T>(x, y), m_dir(dir) {}
LineCoord2D(const Coordinate2D<T>& coord, Direction2D dir) : Coordinate2D<T>(coord), m_dir(dir) {}
Coordinate2D<T> start() const { return *this; }
Coordinate2D<T> end() const { return *this + m_dir; }
Direction2D dir() const { return m_dir; }
friend std::ostream& operator<<(std::ostream& os, const LineCoord2D& line_coord) {
os << "LineCoord2D(" << line_coord.start() << ", " << line_coord.dir() << ")";
return os;
}
};
template <typename T>
class Coordinate3D : public Coordinate2D<T> {
double m_z;
public:
Coordinate3D(T x, T y, T z) : Coordinate2D<T>(x, y), m_z(z) {}
Coordinate3D() = default;
const T z() const { return m_z; }
T& z() { return m_z; }
};
template <typename T>
class LineCoord2DCrossing : public LineCoord2D<T> {
Direction2D m_crossing_dir;
public:
LineCoord2DCrossing(T x, T y, Direction2D dir, Direction2D crossing_dir)
: LineCoord2D<T>(x, y, dir), m_crossing_dir(crossing_dir) {}
LineCoord2DCrossing(const LineCoord2D<T>& line_coord, Direction2D crossing_dir)
: LineCoord2D<T>(line_coord), m_crossing_dir(crossing_dir) {}
Direction2D crossing_dir() const { return m_crossing_dir; }
LineCoord2DCrossing flip() const {
return LineCoord2DCrossing({LineCoord2D<T>::end(), LineCoord2D<T>::dir().opposite()},
m_crossing_dir);
}
std::vector<LineCoord2DCrossing> next_points() const {
std::vector<LineCoord2DCrossing> result;
std::vector<Direction2D> dirs;
if (LineCoord2D<T>::dir().orthogonal_to(crossing_dir())) {
dirs.emplace_back(crossing_dir());
} else {
for (Direction2D dir :
{Direction2D::UP, Direction2D::DOWN, Direction2D::LEFT, Direction2D::RIGHT}) {
if (dir.orthogonal_to(LineCoord2D<T>::dir())) {
dirs.emplace_back(dir);
}
}
}
for (Direction2D dir : dirs) {
Assert(dir.orthogonal_to(LineCoord2D<T>::dir()),
"Crossing direction is not orthogonal to direction");
result.emplace_back(LineCoord2DCrossing<T>({LineCoord2D<T>::start() + dir, dir.opposite()},
LineCoord2D<T>::dir().opposite()));
result.emplace_back(LineCoord2DCrossing<T>({LineCoord2D<T>::end() + dir, dir.opposite()},
LineCoord2D<T>::dir()));
result.emplace_back(
LineCoord2DCrossing<T>({LineCoord2D<T>::start() + dir, LineCoord2D<T>::dir()}, dir));
}
for (size_t i = 0; i < result.size(); i++) {
if (result[i].dir() == Direction2D::UP || result[i].dir() == Direction2D::LEFT) {
result[i] = result[i].flip();
}
}
return result;
}
friend std::ostream& operator<<(std::ostream& os, const LineCoord2DCrossing& line_coord) {
os << "LineCoord2DCrossing(" << line_coord.start() << ", " << line_coord.dir() << ", "
<< line_coord.crossing_dir() << ")";
return os;
}
};
struct Extent2D {
double minx = std::numeric_limits<double>::infinity();
double maxx = -std::numeric_limits<double>::infinity();
double miny = std::numeric_limits<double>::infinity();
double maxy = -std::numeric_limits<double>::infinity();
bool contains(double x, double y) const {
return minx <= x && x <= maxx && miny <= y && y <= maxy;
}
void grow(const Extent2D& other) {
minx = std::min(minx, other.minx);
maxx = std::max(maxx, other.maxx);
miny = std::min(miny, other.miny);
maxy = std::max(maxy, other.maxy);
}
friend std::ostream& operator<<(std::ostream& os, const Extent2D& extent) {
return os << "[(" << extent.minx << ", " << extent.maxx << "), (" << extent.miny << ", "
<< extent.maxy << ")]";
}
};
struct Extent3D : Extent2D {
double minz = std::numeric_limits<double>::infinity();
double maxz = -std::numeric_limits<double>::infinity();
Extent3D() = default;
Extent3D(const Extent2D& extent, double minz, double maxz)
: Extent2D(extent), minz(minz), maxz(maxz) {}
Extent3D(double minx, double maxx, double miny, double maxy, double minz, double maxz)
: Extent2D{minx, maxx, miny, maxy}, minz(minz), maxz(maxz) {}
void grow(double x, double y, double z) {
minx = std::min(x, minx);
maxx = std::max(x, maxx);
miny = std::min(y, miny);
maxy = std::max(y, maxy);
minz = std::min(z, minz);
maxz = std::max(z, maxz);
}
void grow(double border) {
minx -= border;
maxx += border;
miny -= border;
maxy += border;
minz -= border;
maxz += border;
}
bool intersects(const Extent3D& other) const {
return !(other.minx > maxx || other.maxx < minx || other.miny > maxy || other.maxy < miny ||
other.minz > maxz || other.maxz < minz);
}
Coordinate3D<double> center() const {
return Coordinate3D<double>((minx + maxx) / 2, (miny + maxy) / 2, (minz + maxz) / 2);
}
double max_extent() const { return std::max(std::max(maxx - minx, maxy - miny), maxz - minz); }
void grow(const Extent3D& other) {
Extent2D::grow(other);
minz = std::min(minz, other.minz);
maxz = std::max(maxz, other.maxz);
}
Extent3D operator-(const Coordinate3D<double>& offset) const {
return {minx - offset.x(), maxx - offset.x(), miny - offset.y(),
maxy - offset.y(), minz - offset.z(), maxz - offset.z()};
}
Extent3D intersection(const Extent3D& other) const {
return {std::max(minx, other.minx), std::min(maxx, other.maxx), std::max(miny, other.miny),
std::min(maxy, other.maxy), std::max(minz, other.minz), std::min(maxz, other.maxz)};
}
bool operator!=(const Extent3D& other) const {
return minx != other.minx || maxx != other.maxx || miny != other.miny || maxy != other.maxy ||
minz != other.minz || maxz != other.maxz;
}
friend std::ostream& operator<<(std::ostream& os, const Extent3D& extent) {
return os << "[(" << extent.minx << ", " << extent.maxx << "), (" << extent.miny << ", "
<< extent.maxy << "), (" << extent.minz << ", " << extent.maxz << ")]";
}
};