Add new Vec2, Mat2, RGB, RGBA classes and deprecate some old functions

This commit is contained in:
mniip 2023-04-03 08:37:37 +02:00 committed by mniip
parent a2c7242c7c
commit a0a9ad0abd
8 changed files with 873 additions and 158 deletions

View File

@ -59,30 +59,41 @@ void membwand(void * dest, void * src, size_t destsize, size_t srcsize);
// a b
// c d
struct matrix2d {
[[deprecated("Use Mat2")]]
float a,b,c,d;
};
[[deprecated("Use Mat2")]]
typedef struct matrix2d matrix2d;
// column vector
struct vector2d {
[[deprecated("Use Vec2")]]
float x,y;
};
[[deprecated("Use Vec2")]]
typedef struct vector2d vector2d;
matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2);
//matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2);
[[deprecated("Use Mat2::operator*(Vec2)")]]
vector2d m2d_multiply_v2d(matrix2d m, vector2d v);
matrix2d m2d_multiply_float(matrix2d m, float s);
//matrix2d m2d_multiply_float(matrix2d m, float s);
[[deprecated("Use Vec2<float>::operator*(float)")]]
vector2d v2d_multiply_float(vector2d v, float s);
[[deprecated("Use Vec2::operator+")]]
vector2d v2d_add(vector2d v1, vector2d v2);
[[deprecated("Use Vec2::operator-")]]
vector2d v2d_sub(vector2d v1, vector2d v2);
[[deprecated("Use Mat2")]]
matrix2d m2d_new(float me0, float me1, float me2, float me3);
[[deprecated("Use Vec2")]]
vector2d v2d_new(float x, float y);
[[deprecated("Use Vec2::Zero")]]
extern vector2d v2d_zero;
[[deprecated("Use Mat2::Identity")]]
extern matrix2d m2d_identity;
class ByteString;

View File

@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <common/Vec2.h>
constexpr int MENUSIZE = 40;
constexpr int BARSIZE = 17;
@ -7,19 +8,24 @@ constexpr int BARSIZE = 17;
constexpr float M_GRAV = 6.67300e-1f;
//CELL, the size of the pressure, gravity, and wall maps. Larger than 1 to prevent extreme lag
constexpr int CELL = 4;
constexpr int XCELLS = 153;
constexpr int YCELLS = 96;
constexpr int CELL = 4;
constexpr Vec2<int> CELLS = Vec2(153, 96);
constexpr Vec2<int> RES = CELLS * CELL;
constexpr int XCELLS = CELLS.X;
constexpr int YCELLS = CELLS.Y;
constexpr int NCELL = XCELLS * YCELLS;
constexpr int XRES = XCELLS * CELL;
constexpr int YRES = YCELLS * CELL;
constexpr int XRES = RES.X;
constexpr int YRES = RES.Y;
constexpr int NPART = XRES * YRES;
constexpr int XCNTR = XRES / 2;
constexpr int YCNTR = YRES / 2;
constexpr int WINDOWW = XRES + BARSIZE;
constexpr int WINDOWH = YRES + MENUSIZE;
constexpr Vec2<int> WINDOW = RES + Vec2(BARSIZE, MENUSIZE);
constexpr int WINDOWW = WINDOW.X;
constexpr int WINDOWH = WINDOW.Y;
constexpr int MAXSIGNS = 16;

View File

@ -1,4 +1,5 @@
#pragma once
#include "common/Plane.h"
#include "common/String.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
@ -51,26 +52,27 @@ public:
}
};
template<class Item>
struct Plane
template<typename Item>
struct [[deprecated("Use PlaneAdapter<std::vector>")]] Plane: PlaneAdapter<std::vector<Item>>
{
int width = 0;
int height = 0;
std::vector<Item> items;
// invariant: items.size() == width * height
[[deprecated("Use operator[](Vec2)")]]
Item *operator [](int y)
{
return &items[y * width];
return &*PlaneAdapter<std::vector<Item>>::RowIterator(Vec2(0, y));
}
[[deprecated("Use operator[](Vec2)")]]
const Item *operator [](int y) const
{
return &items[y * width];
return &*PlaneAdapter<std::vector<Item>>::RowIterator(Vec2(0, y));
}
[[deprecated("Use PlaneAdapter<std::vector>")]]
Plane() = default;
Plane(int newWidth, int newHeight, Item defaultVal) : width(newWidth), height(newHeight), items(width * height, defaultVal)
[[deprecated("Use PlaneAdapter<std::vector>")]]
Plane(int newWidth, int newHeight, Item defaultVal):
PlaneAdapter<std::vector<Item>>(Vec2(newWidth, newHeight), defaultVal)
{
}
};

126
src/common/Plane.h Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <cstdint>
#include <limits>
#include "common/Vec2.h"
constexpr size_t DynamicExtent = std::numeric_limits<size_t>::max();
template<size_t Extent>
struct extentStorage
{
constexpr extentStorage(size_t)
{}
constexpr size_t getExtent() const
{
return Extent;
}
};
template<>
struct extentStorage<DynamicExtent>
{
size_t extent;
constexpr extentStorage(size_t extent):
extent(extent)
{}
constexpr size_t getExtent() const
{
return extent;
}
};
template<size_t Extent>
struct xExtent: extentStorage<Extent>
{
using extentStorage<Extent>::extentStorage;
};
template<size_t Extent>
struct yExtent: extentStorage<Extent>
{
using extentStorage<Extent>::extentStorage;
};
// A class that contains some container T and lets you index into it as if it
// were a 2D array of size Width x Height, in row-major order.
template<typename T, size_t Width = DynamicExtent, size_t Height = DynamicExtent>
class PlaneAdapter: xExtent<Width>, yExtent<Height>
{
using value_type = std::remove_reference_t<decltype(std::declval<T>()[0])>;
using iterator = decltype(std::begin(std::declval<T &>()));
using const_iterator = decltype(std::begin(std::declval<T const &>()));
size_t getWidth() const
{
return xExtent<Width>::getExtent();
}
size_t getHeight() const
{
return yExtent<Height>::getExtent();
}
public:
T Base;
PlaneAdapter():
xExtent<Width>(0),
yExtent<Height>(0),
Base()
{}
PlaneAdapter(PlaneAdapter const &) = default;
PlaneAdapter(PlaneAdapter &&) = default;
PlaneAdapter &operator=(PlaneAdapter const &) = default;
PlaneAdapter &operator=(PlaneAdapter &&) = default;
template<typename... Args>
PlaneAdapter(Vec2<int> size, Args&&... args):
xExtent<Width>(size.X),
yExtent<Height>(size.Y),
Base(getWidth() * getHeight(), std::forward<Args>(args)...)
{}
Vec2<int> Size() const
{
return Vec2<int>(getWidth(), getHeight());
}
iterator RowIterator(Vec2<int> p)
{
return std::begin(Base) + (p.X + p.Y * getWidth());
}
const_iterator RowIterator(Vec2<int> p) const
{
return std::begin(Base) + (p.X + p.Y * getWidth());
}
value_type *data()
{
return std::data(Base);
}
value_type const *data() const
{
return std::data(Base);
}
value_type &operator[](Vec2<int> p)
{
return Base[p.X + p.Y * getWidth()];
}
value_type const &operator[](Vec2<int> p) const
{
return Base[p.X + p.Y * getWidth()];
}
};

164
src/common/RasterGeometry.h Normal file
View File

@ -0,0 +1,164 @@
#include "common/Vec2.h"
// Draw a line from the origin on the ZW plane, assuming abs(dw) <= dz
template<bool Ortho, typename F>
void rasterizeLineZW(int dz, int dw, F f)
{
const int incW = dw >= 0 ? 1 : -1;
int w = 0, err = 0;
for (int z = 0; z <= dz; z++)
{
f(z, w);
// err / (2 * dz) is the difference between the integer w and the
// (potentially non-integer) value z * dw / dz that would like w to be.
// When the difference becomes too large, we can increment w.
err += 2 * dw * incW;
if (err >= dz)
{
w += incW;
err -= 2 * dz;
if (Ortho && z < dz)
f(z, w);
}
}
}
// Call f for every point on the rasterization of a line between p1 and p2.
// Ortho makes the resulting line orthogonally connected.
template<bool Ortho, typename F>
void RasterizeLine(Vec2<int> p1, Vec2<int> p2, F f)
{
if(std::abs(p1.X - p2.X) >= std::abs(p1.Y - p2.Y))
{
// If it's more wide than tall, map Z to X and W to Y
auto source = p1.X < p2.X ? p1 : p2;
auto delta = p1.X < p2.X ? p2 - p1 : p1 - p2;
rasterizeLineZW<Ortho>(delta.X, delta.Y, [source, f](int z, int w) { f(source + Vec2(z, w)); });
}
else
{
// If it's more tall than wide, map Z to Y and W to X
auto source = p1.Y < p2.Y ? p1 : p2;
auto delta = p1.Y < p2.Y ? p2 - p1 : p1 - p2;
rasterizeLineZW<Ortho>(delta.Y, delta.X, [source, f](int z, int w) { f(source + Vec2(w, z)); });
}
}
template<typename F>
void rasterizeEllipseQuadrant(Vec2<float> radiusSquared, F f)
{
// An ellipse is a region of points (x, y) such that
// (x / rx)^2 + (y / ry)^2 <= 1, which can be rewritten as
// x^2 * ry^2 + y^2 * rx^2 <= ry^2 * rx^2,
// except, if rx == 0, then an additional constraint abs(y) <= ry must be
// added, and same for ry.
// The code below ensures 0 <= x <= rx and 0 <= y <= ry + 1.
// A false positive for y > ry can only happen if rx == 0 and does not
// affect the outcome
auto inEllipse = [=](int x, int y)
{
return y * y * radiusSquared.X + x * x * radiusSquared.Y <= radiusSquared.X * radiusSquared.Y;
};
// Focusing on the bottom right quadrant, in every row we find the range of
// points inside the ellipse, and within those, the range of points on the
// boundary.
int x = int(std::floor(std::sqrt(radiusSquared.X)));
int maxY = int(std::floor(std::sqrt(radiusSquared.Y)));
for (int y = 0; y <= maxY; y++)
{
// At the start of each iteration, (x, y) is on the boundary,
// i.e. the range of points inside the ellipse is [0, x]
if (inEllipse(x, y + 1))
{
// If the point below is inside, x is the only boundary point
f(x, x, y);
}
else
{
// Otherwise, all points whose below point is outside -- are on the boundary
int xStart = x;
do
{
x--;
} while (x >= 0 && !inEllipse(x, y + 1));
f(x + 1, xStart, y);
}
}
}
// Call f for every point on the rasterized boundary of the ellipse with the
// indicated radius.
// In some situations we may want the radius to be the square root of an
// integer, so passing the radius squared allows for exact calculation.
// In some situations we may want the radius to be a half-integer, and floating
// point arithmetic is still exact for half-integers (really, 1/16ths), which is
// why we pass this as a float.
template<typename F>
void RasterizeEllipsePoints(Vec2<float> radiusSquared, F f)
{
rasterizeEllipseQuadrant(radiusSquared, [f](int x1, int x2, int y)
{
for (int x = x1; x <= x2; x++)
{
f(Vec2(x, y));
if (x) f(Vec2(-x, y));
if (y) f(Vec2(x, -y));
if (x && y) f(Vec2(-x, -y));
}
});
}
// Call f for every point inside the ellipse with the indicated radius.
template<typename F>
void RasterizeEllipseRows(Vec2<float> radiusSquared, F f)
{
rasterizeEllipseQuadrant(radiusSquared, [f](int _, int xLim, int y)
{
f(xLim, y);
if (y) f(xLim, -y);
});
}
// Call f for every point on the boundary of the indicated rectangle (so that
// TopLeft and BottomRight are both corners).
template<typename F>
void RasterizeRect(Rect<int> rect, F f)
{
for (int x = rect.TopLeft.X; x <= rect.BottomRight.X; x++)
f(Vec2(x, rect.TopLeft.Y));
if (rect.TopLeft.Y != rect.BottomRight.Y)
for (int x = rect.TopLeft.X; x <= rect.BottomRight.X; x++)
f(Vec2(x, rect.BottomRight.Y));
// corners already drawn
for (int y = rect.TopLeft.Y + 1; y <= rect.BottomRight.Y - 1; y++)
f(Vec2(rect.TopLeft.X, y));
if (rect.TopLeft.X != rect.BottomRight.X)
for (int y = rect.TopLeft.Y + 1; y <= rect.BottomRight.Y - 1; y++)
f(Vec2(rect.BottomRight.X, y));
}
// Call f for every point on the dotted boundary of the indicated rectangle.
template<typename F>
void RasterizeDottedRect(Rect<int> rect, F f)
{
for (int x = rect.TopLeft.X; x <= rect.BottomRight.X; x += 2)
f(Vec2(x, rect.TopLeft.Y));
int bottomOff = (rect.BottomRight.Y - rect.TopLeft.Y) % 2;
if (rect.TopLeft.Y != rect.BottomRight.Y)
for (int x = rect.TopLeft.X + bottomOff; x <= rect.BottomRight.X; x += 2)
f(Vec2(x, rect.BottomRight.Y));
// corners already drawn
for (int y = rect.TopLeft.Y + 1 + 1; y <= rect.BottomRight.Y - 1; y += 2)
f(Vec2(rect.TopLeft.X, y));
int leftOff = (rect.BottomRight.X - rect.TopLeft.X + 1) % 2;
if (rect.TopLeft.X != rect.BottomRight.X)
for (int y = rect.TopLeft.Y + 1 + leftOff; y <= rect.BottomRight.Y - 1; y += 2)
f(Vec2(rect.BottomRight.X, y));
}

429
src/common/Vec2.h Normal file
View File

@ -0,0 +1,429 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <iterator>
#include <type_traits>
#include <utility>
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct Rect;
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct Vec2
{
T X, Y;
constexpr Vec2(T x, T y):
X(x),
Y(y)
{
}
template<typename S, typename = std::enable_if_t<std::is_constructible_v<T, S>>>
constexpr explicit Vec2(Vec2<S> other):
X(other.X),
Y(other.Y)
{
}
constexpr bool operator==(Vec2<T> other) const
{
return X == other.X && Y == other.Y;
}
constexpr bool operator!=(Vec2<T> other) const
{
return X != other.X || Y != other.Y;
}
template<typename S>
constexpr Vec2<decltype(std::declval<T>() + std::declval<S>())> operator+(Vec2<S> other) const
{
return Vec2<decltype(std::declval<T>() + std::declval<S>())>(X + other.X, Y + other.Y);
}
template<typename S>
[[deprecated("Use operator+(Vec2)")]]
constexpr Vec2<decltype(std::declval<T>() + std::declval<S>())> operator+(S other) const
{
return Vec2<decltype(std::declval<T>() + std::declval<S>())>(X + other, Y + other);
}
constexpr Vec2<T> operator-() const
{
return Vec2<T>(-X, -Y);
}
template<typename S>
constexpr Vec2<decltype(std::declval<T>() - std::declval<S>())> operator-(Vec2<S> other) const
{
return Vec2<decltype(std::declval<T>() - std::declval<S>())>(X - other.X, Y - other.Y);
}
template<typename S>
[[deprecated("Use operator-(Vec2)")]]
constexpr Vec2<decltype(std::declval<T>() - std::declval<S>())> operator-(S other) const
{
return Vec2<decltype(std::declval<T>() - std::declval<S>())>(X - other, Y - other);
}
template<typename S, typename = std::enable_if_t<std::is_arithmetic_v<S>>>
constexpr Vec2<decltype(std::declval<T>() * std::declval<S>())> operator*(S other) const
{
return Vec2<decltype(std::declval<T>() * std::declval<S>())>(X * other, Y * other);
}
template<typename S, typename = std::enable_if_t<std::is_arithmetic_v<S>>>
constexpr Vec2<decltype(std::declval<T>() / std::declval<S>())> operator/(S other) const
{
return Vec2<decltype(std::declval<T>() / std::declval<S>())>(X / other, Y / other);
}
template<typename S>
constexpr Vec2<T> &operator+=(Vec2<S> other)
{
return *this = *this + other;
}
template<typename S>
constexpr Vec2<T> &operator-=(Vec2<S> other)
{
return *this = *this - other;
}
template<typename S>
constexpr Vec2<T> &operator*=(Vec2<S> other)
{
return *this = *this * other;
}
template<typename S>
constexpr Vec2<T> &operator/=(Vec2<S> other)
{
return *this = *this / other;
}
// Round towards -infinity
template<typename S = T, typename = std::enable_if_t<std::is_floating_point_v<S>>>
Vec2<T> Floor() const
{
return Vec2<T>(std::floor(X), std::floor(Y));
}
// Round towards nearest integer, halfpoints towards -infinity
template<typename S = T, typename = std::enable_if_t<std::is_floating_point_v<S>>>
Vec2<T> Round() const
{
return (*this + Vec2<T>(0.5, 0.5)).Floor();
}
// Return a rectangle starting at origin, whose dimensions match this vector
template<typename S = T, typename = std::enable_if_t<std::is_integral_v<S>>>
constexpr inline Rect<T> OriginRect() const
{
return RectSized(Vec2<T>(0, 0), *this);
}
static Vec2<T> const Zero;
};
template<typename T, typename V>
Vec2<T> const Vec2<T, V>::Zero = Vec2<T>(0, 0);
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct Mat2
{
// ⎛A B⎞
// ⎝C D⎠, acting on column vectors
T A, B, C, D;
constexpr Mat2(T a, T b, T c, T d):
A(a),
B(b),
C(c),
D(d)
{
}
constexpr bool operator==(Mat2<T> other) const
{
return A == other.A && B == other.B && C == other.C && D == other.D;
}
constexpr bool operator!=(Mat2<T> other) const
{
return A != other.A || B != other.B || C != other.C || D != other.D;
}
template<typename S>
constexpr Vec2<decltype(std::declval<T>() * std::declval<S>())> operator*(Vec2<S> vec) const
{
return Vec2<decltype(std::declval<T>() * std::declval<S>())>(A * vec.X + B * vec.Y, C * vec.X + D * vec.Y);
}
static Mat2<T> const Identity, MirrorX, MirrorY, CCW;
};
template<typename T, typename V>
Mat2<T> const Mat2<T, V>::Identity = Mat2<T>(1, 0, 0, 1);
template<typename T, typename V>
Mat2<T> const Mat2<T, V>::MirrorX = Mat2<T>(-1, 0, 0, 1);
template<typename T, typename V>
Mat2<T> const Mat2<T, V>::MirrorY = Mat2<T>(1, 0, 0, -1);
template<typename T, typename V>
Mat2<T> const Mat2<T, V>::CCW = Mat2<T>(0, 1, -1, 0); // reminder: the Y axis points down
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr static inline Rect<T> RectBetween(Vec2<T>, Vec2<T>);
enum IterationDirection
{
TOP_TO_BOTTOM,
BOTTOM_TO_TOP,
LEFT_TO_RIGHT,
RIGHT_TO_LEFT,
};
template<typename T, typename>
struct Rect
{
// Inclusive
Vec2<T> TopLeft, BottomRight;
private:
constexpr Rect(Vec2<T> topLeft, Vec2<T> bottomRight):
TopLeft(topLeft),
BottomRight(bottomRight)
{
}
friend constexpr Rect<T> RectBetween<T>(Vec2<T>, Vec2<T>);
struct end_sentinel
{};
template<IterationDirection D1, IterationDirection D2>
struct range_row_major
{
static_assert(D1 == TOP_TO_BOTTOM || D1 == BOTTOM_TO_TOP);
static_assert(D2 == LEFT_TO_RIGHT || D2 == RIGHT_TO_LEFT);
T left, top, right, bottom;
struct iterator
{
T x, y;
T const first_x, last_x, end_y;
iterator &operator++()
{
if (x == last_x)
{
x = first_x;
if constexpr (D1 == TOP_TO_BOTTOM)
y++;
else
y--;
}
else
{
if constexpr (D2 == LEFT_TO_RIGHT)
x++;
else
x--;
}
return *this;
}
Vec2<T> operator*() const
{
return Vec2<T>(x, y);
}
bool operator!=(end_sentinel) const
{
if constexpr (D1 == TOP_TO_BOTTOM)
return y < end_y;
else
return y > end_y;
}
using difference_type = void;
using value_type = Vec2<T>;
using pointer = void;
using reference = void;
using iterator_category = std::forward_iterator_tag;
};
iterator begin() const
{
T first_x = D2 == LEFT_TO_RIGHT ? left : right;
T last_x = D2 == LEFT_TO_RIGHT ? right : left;
T first_y = D1 == TOP_TO_BOTTOM ? top : bottom;
T end_y = D1 == TOP_TO_BOTTOM ? bottom + 1 : top - 1;
return iterator{first_x, right >= left ? first_y : end_y, first_x, last_x, end_y};
}
end_sentinel end() const
{
return end_sentinel();
}
};
template<IterationDirection D1, IterationDirection D2>
struct range_column_major
{
static_assert(D1 == LEFT_TO_RIGHT || D1 == RIGHT_TO_LEFT);
static_assert(D2 == TOP_TO_BOTTOM || D2 == BOTTOM_TO_TOP);
T left, top, right, bottom;
struct iterator
{
T x, y;
T const first_y, last_y, end_x;
iterator &operator++()
{
if (y == last_y)
{
y = first_y;
if constexpr (D1 == LEFT_TO_RIGHT)
x++;
else
x--;
}
else
{
if constexpr (D2 == TOP_TO_BOTTOM)
y++;
else
y--;
}
return *this;
}
Vec2<T> operator*() const
{
return Vec2<T>(x, y);
}
bool operator!=(end_sentinel) const
{
if constexpr (D1 == LEFT_TO_RIGHT)
return x < end_x;
else
return x > end_x;
}
using difference_type = void;
using value_type = Vec2<T>;
using pointer = void;
using reference = void;
using iterator_category = std::forward_iterator_tag;
};
iterator begin() const
{
T first_y = D2 == TOP_TO_BOTTOM ? top : bottom;
T last_y = D2 == TOP_TO_BOTTOM ? bottom : top;
T first_x = D1 == LEFT_TO_RIGHT ? left : right;
T end_x = D1 == LEFT_TO_RIGHT ? right + 1 : left - 1;
return iterator{bottom >= top ? first_x : end_x, first_y, first_y, last_y, end_x};
}
end_sentinel end() const
{
return end_sentinel();
}
};
public:
constexpr operator bool() const
{
return BottomRight.X >= TopLeft.X || BottomRight.Y >= TopLeft.Y;
}
// Return the smallest rectangle that contains both input rectangles,
// **assuming neither are empty**
Rect<T> operator|(Rect<T> other) const
{
return Rect<T>(
Vec2<T>(std::min(TopLeft.X, other.TopLeft.X), std::min(TopLeft.Y, other.TopLeft.Y)),
Vec2<T>(std::max(BottomRight.X, other.BottomRight.X), std::max(BottomRight.Y, other.BottomRight.Y))
);
}
// Return the intersection of two rectangles (possibly empty)
Rect<T> operator&(Rect<T> other) const
{
return Rect<T>(
Vec2<T>(std::max(TopLeft.X, other.TopLeft.X), std::max(TopLeft.Y, other.TopLeft.Y)),
Vec2<T>(std::min(BottomRight.X, other.BottomRight.X), std::min(BottomRight.Y, other.BottomRight.Y))
);
}
inline Rect<T> &operator|=(Rect<T> other)
{
return *this = *this | other;
}
inline Rect<T> &operator&=(Rect<T> other)
{
return *this = *this & other;
}
inline bool Contains(Vec2<T> point) const
{
return point.X >= TopLeft.X && point.X <= BottomRight.X && point.Y >= TopLeft.Y && point.Y <= BottomRight.Y;
}
template<typename S = T, typename = std::enable_if_t<std::is_integral_v<S>>>
inline Vec2<T> Size() const
{
return BottomRight - TopLeft + Vec2<T>(1, 1);
}
template<IterationDirection D1, IterationDirection D2, typename S = T, typename = std::enable_if_t<std::is_integral_v<T>>>
constexpr auto Range() const
{
static_assert(
((D1 == TOP_TO_BOTTOM || D1 == BOTTOM_TO_TOP) && (D2 == LEFT_TO_RIGHT || D2 == RIGHT_TO_LEFT)) ||
((D1 == LEFT_TO_RIGHT || D1 == RIGHT_TO_LEFT) && (D2 == TOP_TO_BOTTOM || D2 == BOTTOM_TO_TOP)),
"Must include exactly 1 of TOP_TO_BOTTOM/BOTTOM_TO_TOP and exactly 1 of LEFT_TO_RIGHT/RIGHT_TO_LEFT"
);
if constexpr (D1 == TOP_TO_BOTTOM || D1 == BOTTOM_TO_TOP)
{
return range_row_major<D1, D2>{TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y};
}
else
return range_column_major<D1, D2>{TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y};
}
// Use when the order isn't important
constexpr typename range_row_major<TOP_TO_BOTTOM, LEFT_TO_RIGHT>::iterator begin() const
{
return Range<TOP_TO_BOTTOM, LEFT_TO_RIGHT>().begin();
}
constexpr end_sentinel end() const
{
return end_sentinel();
}
};
template<typename T, typename>
constexpr inline Rect<T> RectBetween(Vec2<T> topLeft, Vec2<T> bottomRight)
{
return Rect<T>(topLeft, bottomRight);
}
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Rect<T> RectAt(Vec2<T> pos)
{
return RectBetween<T>(pos, pos);
}
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
constexpr inline Rect<T> RectSized(Vec2<T> topLeft, Vec2<T> dimen)
{
return RectBetween<T>(topLeft, topLeft + dimen - Vec2<T>(1, 1));
}

View File

@ -1,13 +1,23 @@
#pragma once
typedef unsigned int pixel;
#include <algorithm>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
// This is always packed with the least significant byte being blue,
// then green, then red, then 0.
typedef uint32_t pixel;
constexpr int PIXELCHANNELS = 3;
constexpr int PIXELSIZE = 4;
[[deprecated("Use 0x######_rgb .Pack()")]]
constexpr pixel PIXPACK(int x)
{
return x;
}
[[deprecated("Use RGB(...).Pack()")]]
constexpr pixel PIXRGB(int r, int g, int b)
{
return (r << 16) | (g << 8) | b;
@ -24,3 +34,104 @@ constexpr int PIXB(pixel x)
{
return x & 0xFF;
}
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct RGBA;
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T)) RGB
{
T Blue, Green, Red;
constexpr RGB(T r, T g, T b):
Blue(b),
Green(g),
Red(r)
{
}
template<typename S> // Avoid referring to the non-intuitive order of components
RGB(std::initializer_list<S>) = delete;
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
RGB<T> Blend(RGBA<T> other) const
{
if (other.Alpha == 0xFF)
return other.NoAlpha();
return RGB<T>(
// Technically should divide by 0xFF, but >> 8 is close enough
(other.Alpha * other.Red + (0xFF - other.Alpha) * Red ) >> 8,
(other.Alpha * other.Green + (0xFF - other.Alpha) * Green) >> 8,
(other.Alpha * other.Blue + (0xFF - other.Alpha) * Blue ) >> 8
);
}
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
RGB<T> Add(RGBA<T> other) const
{
return RGB<T>(
std::min(0xFF, (other.Alpha * other.Red + 0xFF * Red ) >> 8),
std::min(0xFF, (other.Alpha * other.Green + 0xFF * Green) >> 8),
std::min(0xFF, (other.Alpha * other.Blue + 0xFF * Blue ) >> 8)
);
}
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
RGB<T> Inverse() const
{
return RGB<T>(0xFF - Red, 0xFF - Green, 0xFF - Blue);
}
constexpr RGBA<T> WithAlpha(T a) const
{
return RGBA<T>(Red, Green, Blue, a);
}
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
pixel Pack() const
{
return Red << 16 | Green << 8 | Blue;
}
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
constexpr static RGB<T> Unpack(pixel px)
{
return RGB<T>(px >> 16, px >> 8, px);
}
};
constexpr inline RGB<uint8_t> operator ""_rgb(unsigned long long value)
{
return RGB<uint8_t>::Unpack(value);
}
template<typename T, typename>
struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T)) RGBA
{
T Blue, Green, Red, Alpha;
constexpr RGBA(T r, T g, T b, T a):
Blue(b),
Green(g),
Red(r),
Alpha(a)
{
}
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
RGBA(T r, T g, T b):
Blue(b),
Green(g),
Red(r),
Alpha(0xFF)
{
}
template<typename S> // Avoid referring to the non-intuitive order of components
RGBA(std::initializer_list<S>) = delete;
RGB<T> NoAlpha() const
{
return RGB<T>(Red, Green, Blue);
}
};

View File

@ -1,142 +1,8 @@
#pragma once
#include "common/Vec2.h"
namespace ui
{
//Lightweight 2D Int32/Float32 Point struct for UI
struct Point
{
using POINT_T = int;
POINT_T X;
POINT_T Y;
Point(POINT_T x, POINT_T y)
: X(x)
, Y(y)
{
}
inline Point operator - () const
{
return Point(-X, -Y);
}
inline Point operator + (const Point& v) const
{
return Point(X + v.X, Y + v.Y);
}
inline Point operator + (const int v) const
{
return Point(X + v, Y + v);
}
inline Point operator - (const Point& v) const
{
return Point(X - v.X, Y - v.Y);
}
inline Point operator - (const int v) const
{
return Point(X - v, Y - v);
}
inline Point operator * (const Point& v) const
{
return Point(X * v.X, Y * v.Y);
}
inline Point operator * (int v) const
{
return Point(X * static_cast<POINT_T>(v), Y * static_cast<POINT_T>(v));
}
inline Point operator * (float v) const
{
return Point(X * static_cast<POINT_T>(v), Y * static_cast<POINT_T>(v));
}
inline Point operator / (const Point& v) const
{
return Point(X / v.X, Y / v.Y);
}
inline Point operator / (int v) const
{
return Point(X / static_cast<POINT_T>(v), Y / static_cast<POINT_T>(v));
}
inline Point operator / (float v) const
{
return Point(X / static_cast<POINT_T>(v), Y / static_cast<POINT_T>(v));
}
inline void operator += (const Point& v)
{
X += v.X;
Y += v.Y;
}
inline void operator -= (const Point& v)
{
X -= v.X;
Y -= v.Y;
}
inline void operator *= (const Point& v)
{
X *= v.X;
Y *= v.Y;
}
inline void operator *= (int v)
{
X *= static_cast<POINT_T>(v);
Y *= static_cast<POINT_T>(v);
}
inline void operator *= (float v)
{
X *= static_cast<POINT_T>(v);
Y *= static_cast<POINT_T>(v);
}
inline void operator /= (const Point& v)
{
X /= v.X;
Y /= v.Y;
}
inline void operator /= (int v)
{
X /= static_cast<POINT_T>(v);
Y /= static_cast<POINT_T>(v);
}
inline void operator /= (float v)
{
X /= static_cast<POINT_T>(v);
Y /= static_cast<POINT_T>(v);
}
inline bool operator == (const Point& v) const
{
return (X == v.X && Y == v.Y);
}
inline bool operator != (const Point& v) const
{
return (X != v.X || Y != v.Y);
}
inline Point operator = (const Point& v)
{
X = v.X;
Y = v.Y;
return Point(X, Y);
}
};
using Point = Vec2<int>;
}