From b99fc394441a13c2b4f4039634deffa86d3012ea Mon Sep 17 00:00:00 2001 From: tobid7 Date: Sat, 18 Apr 2026 14:33:31 +0200 Subject: [PATCH] Add support for rotated gradients --- include/pd/core/color.hpp | 14 ++++++++ include/pd/lithium/drawlist.hpp | 5 +++ source/lithium/drawlist.cpp | 61 +++++++++++++++++++++++++++++++-- source/lithium/math.cpp | 1 + tests/gfx/source/main.cpp | 57 ++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) diff --git a/include/pd/core/color.hpp b/include/pd/core/color.hpp index f9a569e..5a77895 100755 --- a/include/pd/core/color.hpp +++ b/include/pd/core/color.hpp @@ -121,6 +121,20 @@ class PD_API Color { return *this; } + /** + * Lerp + * @param v Target color + * @param t interpolation factor + * @return Class Reference + */ + constexpr Color& Lerp(const Color& v, float t) { + a = static_cast(a + (v.a - a) * t); + b = static_cast(b + (v.b - b) * t); + g = static_cast(g + (v.g - g) * t); + r = static_cast(r + (v.r - r) * t); + return *this; + } + /** * Get 32Bit Color Value * @return 32Bit Color Value (ABGR iirc) diff --git a/include/pd/lithium/drawlist.hpp b/include/pd/lithium/drawlist.hpp index 76872cc..807882d 100644 --- a/include/pd/lithium/drawlist.hpp +++ b/include/pd/lithium/drawlist.hpp @@ -26,6 +26,7 @@ using LiDrawFlags = PD::u32; enum LiDrawFlags_ : PD::u32 { LiDrawFlags_None = 0, LiDrawFlags_Close = 1 << 0, + LiDrawFlags_AA = 1 << 1, }; namespace PD { @@ -58,6 +59,8 @@ class PD_API Drawlist { void PathStroke(const PD::Color& color, int t = 1, LiDrawFlags flags = LiDrawFlags_None); void PathFill(const PD::Color& color); + void PathFillGradient(const PD::Color& a, const PD::Color& b, + float rad = 0.f); void PathArcToN(const fvec2& c, float r, float amin, float amax, int s); void PathFastArcToN(const fvec2& c, float r, float amin, float amax, int s); void PathRect(const fvec2& tl, const fvec2& br, float r = 0.f); @@ -97,6 +100,8 @@ class PD_API Drawlist { void DrawPolyLine(const Pool& points, const PD::Color& color, LiDrawFlags flags = LiDrawFlags_None, int t = 1); void DrawConvexPolyFilled(const Pool& points, const PD::Color& color); + void DrawConvexPolyFilled(const Pool& points, const PD::Color& a, + const PD::Color b, float rad = 0.f); void PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, const PD::Color& color); diff --git a/source/lithium/drawlist.cpp b/source/lithium/drawlist.cpp index 5c5175f..ef7c89a 100644 --- a/source/lithium/drawlist.cpp +++ b/source/lithium/drawlist.cpp @@ -45,6 +45,12 @@ PD_API void Drawlist::PathFill(const PD::Color& color) { PathClear(); } +PD_API void Drawlist::PathFillGradient(const PD::Color& a, const PD::Color& b, + float rad) { + DrawConvexPolyFilled(pPath, a, b, rad); + PathClear(); +} + PD_API void Drawlist::PathArcToN(const fvec2& c, float r, float amin, float amax, int s) { // Path.push_back(c); @@ -229,9 +235,9 @@ PD_API void Drawlist::DrawPolyLine(const Pool& points, } UnbindTexture(); auto& cmd = NewCommand(); - bool close = (flags & (1 << 0)); + bool close = (flags & LiDrawFlags_Close); int num_points = close ? (int)points.size() : (int)points.size() - 1; - if (flags & (1 << 1)) { + if (flags & LiDrawFlags_AA) { // TODO: Find a way to draw less garbage looking lines } else { // Non antialiased lines look awful when rendering with thickness != 1 @@ -282,6 +288,57 @@ PD_API void Drawlist::DrawConvexPolyFilled(const Pool& points, } } +PD_API void Drawlist::DrawConvexPolyFilled(const Pool& points, + const PD::Color& a, + const PD::Color b, float rad) { + if (points.size() < 3) { + return; // Need at least three points + } + + fvec2 dir = fvec2(std::cos(rad), std::sin(rad)); + // Support for Custom Textures (UV calculation) + float minX = points[0].x, minY = points[0].y; + float maxX = minX, maxY = minY; + // Check for the max and min Positions + for (const auto& it : points) { + if (it.x < minX) minX = it.x; + if (it.y < minY) minY = it.y; + if (it.x > maxX) maxX = it.x; + if (it.y > maxY) maxY = it.y; + } + // Get Short defines for UV + // (Bottom Right is not required) + auto uv_tl = pCurrentTexture.GetUV().TopLeft(); + auto uv_tr = pCurrentTexture.GetUV().TopRight(); + auto uv_bl = pCurrentTexture.GetUV().BotLeft(); + + // Gradient + float tmin = std::numeric_limits::max(); + float tmax = std::numeric_limits::lowest(); + for (const auto& p : points) { + float t = p.x * dir.x + p.y * dir.y; + if (t < tmin) tmin = t; + if (t > tmax) tmax = t; + } + // potential div0 + float irange = (tmax != tmin) ? (1.0f / (tmax - tmin)) : 0.0f; + // Command building (oder so) + auto& cmd = NewCommand(); + cmd.Reserve(points.size(), (points.size() - 2) * 3); + // Render + for (int i = 2; i < (int)points.size(); i++) { + cmd.Add(0, i, i - 1); + } + // Why was this for loop not used in normal Convex Poly filled??? + for (auto& it : points) { + // Calculate U and V coords + float u = uv_tl.x + ((it.x - minX) / (maxX - minX)) * (uv_tr.x - uv_tl.x); + float v = uv_tl.y + ((it.y - minY) / (maxY - minY)) * (uv_bl.y - uv_tl.y); + float t = it.x * dir.x + it.y * dir.y; + cmd.Add(Vertex(it, fvec2(u, v), PD::Color(a).Lerp(b, (t - tmin) * irange))); + } +} + PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, const PD::Color& color) { cmd.Reserve(4, 6); diff --git a/source/lithium/math.cpp b/source/lithium/math.cpp index eddfb6b..57b97f1 100644 --- a/source/lithium/math.cpp +++ b/source/lithium/math.cpp @@ -58,6 +58,7 @@ PD_API Rect PrimLine(const fvec2& a, const fvec2& b, int t) { // Using the vec maths api makes the code as short as it is vec2 dir = a - b; float len = dir.Len(); + if (len == 0.0f) return Rect(); vec2 unit_dir = dir / len; vec2 perpendicular(-unit_dir.y, unit_dir.x); vec2 off = perpendicular * ((float)t * 0.5f); diff --git a/tests/gfx/source/main.cpp b/tests/gfx/source/main.cpp index d98df53..60bcd44 100644 --- a/tests/gfx/source/main.cpp +++ b/tests/gfx/source/main.cpp @@ -166,6 +166,52 @@ struct Cursor { } }; +void DrawRectGradient135(PD::Li::Drawlist& l, const PD::fvec2& pos, + const PD::fvec2& size, const PD::Color& colA, + const PD::Color& colB) { + PD::fvec2 p0 = pos; + PD::fvec2 p1 = PD::fvec2(pos.x + size.x, pos.y); + PD::fvec2 p2 = PD::fvec2(pos.x + size.x, pos.y + size.y); + PD::fvec2 p3 = PD::fvec2(pos.x, pos.y + size.y); + + // 135° direction + PD::fvec2 dir = PD::fvec2(-0.70710678f, 0.70710678f); + + // Project all corners + float t0 = p0.x * dir.x + p0.y * dir.y; + float t1 = p1.x * dir.x + p1.y * dir.y; + float t2 = p2.x * dir.x + p2.y * dir.y; + float t3 = p3.x * dir.x + p3.y * dir.y; + + float tmin = std::min({t0, t1, t2, t3}); + float tmax = std::max({t0, t1, t2, t3}); + + auto normalize = [&](float t) { return (t - tmin) / (tmax - tmin); }; + + auto lerpColor = [&](float t) { + return PD::Color(colA.rf() + (colB.rf() - colA.rf()) * t, + colA.gf() + (colB.gf() - colA.gf()) * t, + colA.bf() + (colB.bf() - colA.bf()) * t, + colA.af() + (colB.af() - colA.af()) * t); + }; + + PD::Color c0 = lerpColor(normalize(t0)); + PD::Color c1 = lerpColor(normalize(t1)); + PD::Color c2 = lerpColor(normalize(t2)); + PD::Color c3 = lerpColor(normalize(t3)); + + auto& cmd = l.NewCommand(); + cmd.Reserve(4, 6); + + cmd.Add(2, 1, 0); + cmd.Add(3, 2, 0); + + cmd.Add(PD::Li::Vertex(p0, PD::fvec2(0, 0), c0)); + cmd.Add(PD::Li::Vertex(p1, PD::fvec2(1, 0), c1)); + cmd.Add(PD::Li::Vertex(p2, PD::fvec2(1, 1), c2)); + cmd.Add(PD::Li::Vertex(p3, PD::fvec2(0, 1), c3)); +} + int main(int argc, char** argv) { // PD::LogFilter(PD::LogLevel::Warning); Driver drv = Driver::OpenGL3; @@ -252,6 +298,17 @@ int main(int argc, char** argv) { "#ffffff"); LeftStick.Render(pList); RightStick.Render(pList); + pList.UnbindTexture(); + pList.PathRect(50, PD::fvec2(450, 240)); + pList.PathFillGradient("#ff0000", "#990000", PD::Radians(135)); + pList.PathAdd(PD::fvec2(100, 120)); + pList.PathAdd(PD::fvec2(250, 260)); + pList.PathAdd(PD::fvec2(420, 180)); + pList.PathAdd(PD::fvec2(600, 320)); + pList.PathAdd(PD::fvec2(820, 220)); + pList.PathAdd(PD::fvec2(1000, 360)); + + pList.PathStroke("#ff00ff", 10, LiDrawFlags_AA); PD::Gfx::Reset(); PD::Gfx::Draw(pList); pList.Clear();