From a0a9ad0abd78fced62d1eddfd744f551599f44d5 Mon Sep 17 00:00:00 2001 From: mniip Date: Mon, 3 Apr 2023 08:37:37 +0200 Subject: [PATCH] Add new Vec2, Mat2, RGB, RGBA classes and deprecate some old functions --- src/Misc.h | 17 +- src/SimulationConfig.h | 20 +- src/client/GameSave.h | 22 +- src/common/Plane.h | 126 +++++++++++ src/common/RasterGeometry.h | 164 ++++++++++++++ src/common/Vec2.h | 429 ++++++++++++++++++++++++++++++++++++ src/graphics/Pixel.h | 113 +++++++++- src/gui/interface/Point.h | 140 +----------- 8 files changed, 873 insertions(+), 158 deletions(-) create mode 100644 src/common/Plane.h create mode 100644 src/common/RasterGeometry.h create mode 100644 src/common/Vec2.h diff --git a/src/Misc.h b/src/Misc.h index f4798808b..5d097b859 100644 --- a/src/Misc.h +++ b/src/Misc.h @@ -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::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; diff --git a/src/SimulationConfig.h b/src/SimulationConfig.h index f59f84bff..bee3ff9c0 100644 --- a/src/SimulationConfig.h +++ b/src/SimulationConfig.h @@ -1,5 +1,6 @@ #pragma once #include +#include 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 CELLS = Vec2(153, 96); +constexpr Vec2 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 WINDOW = RES + Vec2(BARSIZE, MENUSIZE); + +constexpr int WINDOWW = WINDOW.X; +constexpr int WINDOWH = WINDOW.Y; constexpr int MAXSIGNS = 16; diff --git a/src/client/GameSave.h b/src/client/GameSave.h index e7d07066c..7f60154b5 100644 --- a/src/client/GameSave.h +++ b/src/client/GameSave.h @@ -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 -struct Plane +template +struct [[deprecated("Use PlaneAdapter")]] Plane: PlaneAdapter> { - int width = 0; - int height = 0; - std::vector items; - // invariant: items.size() == width * height - + [[deprecated("Use operator[](Vec2)")]] Item *operator [](int y) { - return &items[y * width]; + return &*PlaneAdapter>::RowIterator(Vec2(0, y)); } + [[deprecated("Use operator[](Vec2)")]] const Item *operator [](int y) const { - return &items[y * width]; + return &*PlaneAdapter>::RowIterator(Vec2(0, y)); } + [[deprecated("Use PlaneAdapter")]] Plane() = default; - Plane(int newWidth, int newHeight, Item defaultVal) : width(newWidth), height(newHeight), items(width * height, defaultVal) + + [[deprecated("Use PlaneAdapter")]] + Plane(int newWidth, int newHeight, Item defaultVal): + PlaneAdapter>(Vec2(newWidth, newHeight), defaultVal) { } }; diff --git a/src/common/Plane.h b/src/common/Plane.h new file mode 100644 index 000000000..8c8dcefa5 --- /dev/null +++ b/src/common/Plane.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +#include "common/Vec2.h" + +constexpr size_t DynamicExtent = std::numeric_limits::max(); + +template +struct extentStorage +{ + constexpr extentStorage(size_t) + {} + + constexpr size_t getExtent() const + { + return Extent; + } +}; + +template<> +struct extentStorage +{ + size_t extent; + + constexpr extentStorage(size_t extent): + extent(extent) + {} + + constexpr size_t getExtent() const + { + return extent; + } +}; + +template +struct xExtent: extentStorage +{ + using extentStorage::extentStorage; +}; + +template +struct yExtent: extentStorage +{ + using extentStorage::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 +class PlaneAdapter: xExtent, yExtent +{ + using value_type = std::remove_reference_t()[0])>; + using iterator = decltype(std::begin(std::declval())); + using const_iterator = decltype(std::begin(std::declval())); + + size_t getWidth() const + { + return xExtent::getExtent(); + } + + size_t getHeight() const + { + return yExtent::getExtent(); + } + +public: + T Base; + + PlaneAdapter(): + xExtent(0), + yExtent(0), + Base() + {} + + PlaneAdapter(PlaneAdapter const &) = default; + + PlaneAdapter(PlaneAdapter &&) = default; + + PlaneAdapter &operator=(PlaneAdapter const &) = default; + + PlaneAdapter &operator=(PlaneAdapter &&) = default; + + template + PlaneAdapter(Vec2 size, Args&&... args): + xExtent(size.X), + yExtent(size.Y), + Base(getWidth() * getHeight(), std::forward(args)...) + {} + + Vec2 Size() const + { + return Vec2(getWidth(), getHeight()); + } + + iterator RowIterator(Vec2 p) + { + return std::begin(Base) + (p.X + p.Y * getWidth()); + } + + const_iterator RowIterator(Vec2 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 p) + { + return Base[p.X + p.Y * getWidth()]; + } + + value_type const &operator[](Vec2 p) const + { + return Base[p.X + p.Y * getWidth()]; + } +}; diff --git a/src/common/RasterGeometry.h b/src/common/RasterGeometry.h new file mode 100644 index 000000000..b2cee21d1 --- /dev/null +++ b/src/common/RasterGeometry.h @@ -0,0 +1,164 @@ +#include "common/Vec2.h" + +// Draw a line from the origin on the ZW plane, assuming abs(dw) <= dz +template +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 +void RasterizeLine(Vec2 p1, Vec2 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(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(delta.Y, delta.X, [source, f](int z, int w) { f(source + Vec2(w, z)); }); + } +} + +template +void rasterizeEllipseQuadrant(Vec2 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 +void RasterizeEllipsePoints(Vec2 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 +void RasterizeEllipseRows(Vec2 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 +void RasterizeRect(Rect 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 +void RasterizeDottedRect(Rect 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)); +} diff --git a/src/common/Vec2.h b/src/common/Vec2.h new file mode 100644 index 000000000..6fd6c0198 --- /dev/null +++ b/src/common/Vec2.h @@ -0,0 +1,429 @@ +#pragma once + +#include +#include +#include +#include +#include + +template>> +struct Rect; + +template>> +struct Vec2 +{ + T X, Y; + + constexpr Vec2(T x, T y): + X(x), + Y(y) + { + } + + template>> + constexpr explicit Vec2(Vec2 other): + X(other.X), + Y(other.Y) + { + } + + constexpr bool operator==(Vec2 other) const + { + return X == other.X && Y == other.Y; + } + + constexpr bool operator!=(Vec2 other) const + { + return X != other.X || Y != other.Y; + } + + template + constexpr Vec2() + std::declval())> operator+(Vec2 other) const + { + return Vec2() + std::declval())>(X + other.X, Y + other.Y); + } + + template + [[deprecated("Use operator+(Vec2)")]] + constexpr Vec2() + std::declval())> operator+(S other) const + { + return Vec2() + std::declval())>(X + other, Y + other); + } + + constexpr Vec2 operator-() const + { + return Vec2(-X, -Y); + } + + template + constexpr Vec2() - std::declval())> operator-(Vec2 other) const + { + return Vec2() - std::declval())>(X - other.X, Y - other.Y); + } + + template + [[deprecated("Use operator-(Vec2)")]] + constexpr Vec2() - std::declval())> operator-(S other) const + { + return Vec2() - std::declval())>(X - other, Y - other); + } + + template>> + constexpr Vec2() * std::declval())> operator*(S other) const + { + return Vec2() * std::declval())>(X * other, Y * other); + } + + template>> + constexpr Vec2() / std::declval())> operator/(S other) const + { + return Vec2() / std::declval())>(X / other, Y / other); + } + + template + constexpr Vec2 &operator+=(Vec2 other) + { + return *this = *this + other; + } + + template + constexpr Vec2 &operator-=(Vec2 other) + { + return *this = *this - other; + } + + template + constexpr Vec2 &operator*=(Vec2 other) + { + return *this = *this * other; + } + + template + constexpr Vec2 &operator/=(Vec2 other) + { + return *this = *this / other; + } + + // Round towards -infinity + template>> + Vec2 Floor() const + { + return Vec2(std::floor(X), std::floor(Y)); + } + + // Round towards nearest integer, halfpoints towards -infinity + template>> + Vec2 Round() const + { + return (*this + Vec2(0.5, 0.5)).Floor(); + } + + // Return a rectangle starting at origin, whose dimensions match this vector + template>> + constexpr inline Rect OriginRect() const + { + return RectSized(Vec2(0, 0), *this); + } + + static Vec2 const Zero; +}; + +template +Vec2 const Vec2::Zero = Vec2(0, 0); + +template>> +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 other) const + { + return A == other.A && B == other.B && C == other.C && D == other.D; + } + + constexpr bool operator!=(Mat2 other) const + { + return A != other.A || B != other.B || C != other.C || D != other.D; + } + + template + constexpr Vec2() * std::declval())> operator*(Vec2 vec) const + { + return Vec2() * std::declval())>(A * vec.X + B * vec.Y, C * vec.X + D * vec.Y); + } + + static Mat2 const Identity, MirrorX, MirrorY, CCW; +}; + +template +Mat2 const Mat2::Identity = Mat2(1, 0, 0, 1); +template +Mat2 const Mat2::MirrorX = Mat2(-1, 0, 0, 1); +template +Mat2 const Mat2::MirrorY = Mat2(1, 0, 0, -1); +template +Mat2 const Mat2::CCW = Mat2(0, 1, -1, 0); // reminder: the Y axis points down + +template>> +constexpr static inline Rect RectBetween(Vec2, Vec2); + +enum IterationDirection +{ + TOP_TO_BOTTOM, + BOTTOM_TO_TOP, + LEFT_TO_RIGHT, + RIGHT_TO_LEFT, +}; + +template +struct Rect +{ + // Inclusive + Vec2 TopLeft, BottomRight; + +private: + constexpr Rect(Vec2 topLeft, Vec2 bottomRight): + TopLeft(topLeft), + BottomRight(bottomRight) + { + } + friend constexpr Rect RectBetween(Vec2, Vec2); + + struct end_sentinel + {}; + + template + 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 operator*() const + { + return Vec2(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; + 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 + 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 operator*() const + { + return Vec2(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; + 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 operator|(Rect other) const + { + return Rect( + Vec2(std::min(TopLeft.X, other.TopLeft.X), std::min(TopLeft.Y, other.TopLeft.Y)), + Vec2(std::max(BottomRight.X, other.BottomRight.X), std::max(BottomRight.Y, other.BottomRight.Y)) + ); + } + + // Return the intersection of two rectangles (possibly empty) + Rect operator&(Rect other) const + { + return Rect( + Vec2(std::max(TopLeft.X, other.TopLeft.X), std::max(TopLeft.Y, other.TopLeft.Y)), + Vec2(std::min(BottomRight.X, other.BottomRight.X), std::min(BottomRight.Y, other.BottomRight.Y)) + ); + } + + inline Rect &operator|=(Rect other) + { + return *this = *this | other; + } + + inline Rect &operator&=(Rect other) + { + return *this = *this & other; + } + + inline bool Contains(Vec2 point) const + { + return point.X >= TopLeft.X && point.X <= BottomRight.X && point.Y >= TopLeft.Y && point.Y <= BottomRight.Y; + } + + template>> + inline Vec2 Size() const + { + return BottomRight - TopLeft + Vec2(1, 1); + } + + template>> + 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{TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y}; + } + else + return range_column_major{TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y}; + } + + // Use when the order isn't important + constexpr typename range_row_major::iterator begin() const + { + return Range().begin(); + } + + constexpr end_sentinel end() const + { + return end_sentinel(); + } +}; + +template +constexpr inline Rect RectBetween(Vec2 topLeft, Vec2 bottomRight) +{ + return Rect(topLeft, bottomRight); +} + +template>> +constexpr inline Rect RectAt(Vec2 pos) +{ + return RectBetween(pos, pos); +} + +template>> +constexpr inline Rect RectSized(Vec2 topLeft, Vec2 dimen) +{ + return RectBetween(topLeft, topLeft + dimen - Vec2(1, 1)); +} diff --git a/src/graphics/Pixel.h b/src/graphics/Pixel.h index fb78bafef..1572968d5 100644 --- a/src/graphics/Pixel.h +++ b/src/graphics/Pixel.h @@ -1,13 +1,23 @@ #pragma once -typedef unsigned int pixel; +#include +#include +#include +#include +#include + +// 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>> +struct RGBA; + +template>> +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 // Avoid referring to the non-intuitive order of components + RGB(std::initializer_list) = delete; + + template>> + RGB Blend(RGBA other) const + { + if (other.Alpha == 0xFF) + return other.NoAlpha(); + return RGB( + // 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>> + RGB Add(RGBA other) const + { + return RGB( + 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>> + RGB Inverse() const + { + return RGB(0xFF - Red, 0xFF - Green, 0xFF - Blue); + } + + constexpr RGBA WithAlpha(T a) const + { + return RGBA(Red, Green, Blue, a); + } + + template>> + pixel Pack() const + { + return Red << 16 | Green << 8 | Blue; + } + + template>> + constexpr static RGB Unpack(pixel px) + { + return RGB(px >> 16, px >> 8, px); + } +}; + +constexpr inline RGB operator ""_rgb(unsigned long long value) +{ + return RGB::Unpack(value); +} + +template +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>> + RGBA(T r, T g, T b): + Blue(b), + Green(g), + Red(r), + Alpha(0xFF) + { + } + + template // Avoid referring to the non-intuitive order of components + RGBA(std::initializer_list) = delete; + + RGB NoAlpha() const + { + return RGB(Red, Green, Blue); + } +}; diff --git a/src/gui/interface/Point.h b/src/gui/interface/Point.h index 9d44c207d..7a64e5e69 100644 --- a/src/gui/interface/Point.h +++ b/src/gui/interface/Point.h @@ -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(v), Y * static_cast(v)); - } - - inline Point operator * (float v) const - { - return Point(X * static_cast(v), Y * static_cast(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(v), Y / static_cast(v)); - } - - inline Point operator / (float v) const - { - return Point(X / static_cast(v), Y / static_cast(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(v); - Y *= static_cast(v); - } - - inline void operator *= (float v) - { - X *= static_cast(v); - Y *= static_cast(v); - } - - inline void operator /= (const Point& v) - { - X /= v.X; - Y /= v.Y; - } - - inline void operator /= (int v) - { - X /= static_cast(v); - Y /= static_cast(v); - } - - inline void operator /= (float v) - { - X /= static_cast(v); - Y /= static_cast(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; }