From 9e4f1f64dce248f23065bf40526ecd84a0637cfb Mon Sep 17 00:00:00 2001 From: elasota Date: Sun, 29 Dec 2019 02:38:41 -0500 Subject: [PATCH] Add ellipse plotter and oval drawing --- PortabilityLayer/EllipsePlotter.cpp | 411 ++++++++++++++++++ PortabilityLayer/EllipsePlotter.h | 62 +++ PortabilityLayer/LinePlotter.h | 7 +- PortabilityLayer/PLQDraw.cpp | 11 +- PortabilityLayer/PortabilityLayer.vcxproj | 2 + .../PortabilityLayer.vcxproj.filters | 6 + PortabilityLayer/Rect2i.h | 2 +- PortabilityLayer/ScanlineMaskConverter.cpp | 60 +++ PortabilityLayer/ScanlineMaskConverter.h | 2 + 9 files changed, 558 insertions(+), 5 deletions(-) create mode 100644 PortabilityLayer/EllipsePlotter.cpp create mode 100644 PortabilityLayer/EllipsePlotter.h diff --git a/PortabilityLayer/EllipsePlotter.cpp b/PortabilityLayer/EllipsePlotter.cpp new file mode 100644 index 0000000..01bb00e --- /dev/null +++ b/PortabilityLayer/EllipsePlotter.cpp @@ -0,0 +1,411 @@ +#include "EllipsePlotter.h" + +#include "Rect2i.h" + +#include + +namespace +{ + static int32_t SquareInt32(int32_t v) + { + return v * v; + } +} + +namespace PortabilityLayer +{ + EllipsePlotter::EllipsePlotter() + : m_2center(0, 0) + , m_point(0, 0) +#if PL_DEBUG_ELLIPSE_PLOTTER 1 + , m_2offsetFromCenter(0, 0) +#endif + , m_quadrant(Quadrant_PxPy) + , m_sqDistFromEdge(0) + { + } + + PlotDirection EllipsePlotter::PlotNext() + { + // distance = (m_2offsetFromCenter.x*m_diameters.y)^2 + (m_2offsetFromCenter.y*m_diameters.x)^2 <= (m_diameters.x*m_diameters.y)^2 + // We want to minimize distance while keeping it less than (m_diameters.x*m_diameters.y)^2 + + // Stepping X: + // ((m_2offsetFromCenter.x + n)*m_diameters.y)^2 = (m_2offsetFromCenter.x*m_diameters.y)^2 + t + // t = ((m_2offsetFromCenter.x + n)*m_diameters.y)^2 - (m_2offsetFromCenter.x*m_diameters.y)^2 + // t = (m_2offsetFromCenter.x*m_diameters.y + n*m_diameters.y)^2 - (m_2offsetFromCenter.x*m_diameters.y)^2 + // t = 2*(m_2offsetFromCenter.x*m_diameters.y*n*m_diameters.y) + (n*m_diameters.y)^2 + + // For n=2: + // t = 2 * (m_2offsetFromCenter.x*m_diameters.y*2*m_diameters.y) + (2*m_diameters.y) ^ 2 + // t = m_2offsetFromCenter.x * 4 * (m_diameters.y*)^2 + 4*(m_diameters.y) ^ 2 + + // For n=-2 + // t = 2 * (m_2offsetFromCenter.x*m_diameters.y*-2*m_diameters.y) + (-2*m_diameters.y) ^ 2 + // t = m_2offsetFromCenter.x * -4 * (m_diameters.y*m_diameters.y) + 4 * (m_diameters.y) ^ 2 + +#if PL_DEBUG_ELLIPSE_PLOTTER + { + const int32_t diameterSq = SquareInt32(m_diameters.m_x*m_diameters.m_y); + const int32_t sqDistX = SquareInt32(m_2offsetFromCenter.m_x*m_diameters.m_y); + const int32_t sqDistY = SquareInt32(m_2offsetFromCenter.m_y*m_diameters.m_x); + + assert(m_sqDistFromEdge >= 0); + assert(sqDistX + sqDistY >= diameterSq); + assert(sqDistX + sqDistY - diameterSq == m_sqDistFromEdge); + assert(m_xChangeCostDynamicFactor == m_2offsetFromCenter.m_x * 4 * SquareInt32(m_diameters.m_y)); + assert(m_yChangeCostDynamicFactor == m_2offsetFromCenter.m_y * 4 * SquareInt32(m_diameters.m_x)); + + Vec2i actualCoordinate = (m_2center + m_2offsetFromCenter); + actualCoordinate.m_x /= 2; + actualCoordinate.m_y /= 2; + + int n = 0; + } +#endif + + PlotDirection plotDir = PlotDirection_Exhausted; + + switch (m_quadrant) + { + case Quadrant_PxPy: + { + const int32_t xStepCost = -m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + const int32_t yStepCost = m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + const int32_t diagonalCostDelta = xStepCost + yStepCost; + + if (diagonalCostDelta < 0) + { + // Diagonal movement will move closer to the edge (first octant) + IncrementY(); + + const int32_t distWithDiagonalMovement = m_sqDistFromEdge + xStepCost; + if (distWithDiagonalMovement >= 0) + { + DecrementX(); + plotDir = PlotDirection_NegX_PosY; + } + else + plotDir = PlotDirection_0X_PosY; + } + else + { + // Diagonal movement will move farther from the center (second octant) + DecrementX(); + + if (m_sqDistFromEdge < 0) + { + IncrementY(); + plotDir = PlotDirection_NegX_PosY; + } + else + plotDir = PlotDirection_NegX_0Y; + } + + if (m_xChangeCostDynamicFactor <= 0) + m_quadrant = Quadrant_NxPy; + + return plotDir; + } + break; + + case Quadrant_NxPy: + { + const int32_t xStepCost = -m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + const int32_t yStepCost = -m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + const int32_t diagonalCostDelta = xStepCost + yStepCost; + + if (diagonalCostDelta < 0) + { + // Diagonal movement will move closer to the edge (first octant) + DecrementX(); + + const int32_t distWithDiagonalMovement = m_sqDistFromEdge + yStepCost; + if (distWithDiagonalMovement >= 0) + { + DecrementY(); + plotDir = PlotDirection_NegX_NegY; + } + else + plotDir = PlotDirection_NegX_0Y; + } + else + { + // Diagonal movement will move farther from the center (second octant) + DecrementY(); + + if (m_sqDistFromEdge < 0) + { + DecrementX(); + plotDir = PlotDirection_NegX_NegY; + } + else + plotDir = PlotDirection_0X_NegY; + } + + if (m_yChangeCostDynamicFactor <= 0) + m_quadrant = Quadrant_NxNy; + + return plotDir; + } + break; + + case Quadrant_NxNy: + { + const int32_t xStepCost = m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + const int32_t yStepCost = -m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + const int32_t diagonalCostDelta = xStepCost + yStepCost; + + if (diagonalCostDelta < 0) + { + // Diagonal movement will move closer to the edge (first octant) + DecrementY(); + + const int32_t distWithDiagonalMovement = m_sqDistFromEdge + xStepCost; + if (distWithDiagonalMovement >= 0) + { + IncrementX(); + plotDir = PlotDirection_PosX_NegY; + } + else + plotDir = PlotDirection_0X_NegY; + } + else + { + // Diagonal movement will move farther from the center (second octant) + IncrementX(); + + if (m_sqDistFromEdge < 0) + { + DecrementY(); + plotDir = PlotDirection_PosX_NegY; + } + else + plotDir = PlotDirection_PosX_0Y; + } + + if (m_xChangeCostDynamicFactor >= 0) + m_quadrant = Quadrant_PxNy; + + return plotDir; + } + break; + + case Quadrant_PxNy: + { + const int32_t xStepCost = m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + const int32_t yStepCost = m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + const int32_t diagonalCostDelta = xStepCost + yStepCost; + + if (diagonalCostDelta < 0) + { + // Diagonal movement will move closer to the edge (first octant) + IncrementX(); + + const int32_t distWithDiagonalMovement = m_sqDistFromEdge + yStepCost; + if (distWithDiagonalMovement >= 0) + { + IncrementY(); + plotDir = PlotDirection_PosX_PosY; + } + else + plotDir = PlotDirection_PosX_0Y; + } + else + { + // Diagonal movement will move farther from the center + IncrementY(); + + if (m_sqDistFromEdge < 0) + { + IncrementX(); + plotDir = PlotDirection_PosX_PosY; + } + else + plotDir = PlotDirection_0X_PosY; + } + + if (m_yChangeCostDynamicFactor >= 0) + m_quadrant = Quadrant_Finished; + + return plotDir; + } + break; + + case Quadrant_Finished: + return PlotDirection_Exhausted; + + default: + assert(false); + return PlotDirection_Exhausted; + } + } + + void EllipsePlotter::IncrementX() + { + const int32_t xStepCost = m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + + m_sqDistFromEdge += xStepCost; + m_xChangeCostDynamicFactor += m_xChangeCostDynamicFactorStep; + m_point.m_x++; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_x += 2; +#endif + } + + bool EllipsePlotter::ConditionalIncrementX() + { + const int32_t xStepCost = m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + + if (m_sqDistFromEdge + xStepCost >= 0) + { + m_sqDistFromEdge += xStepCost; + m_xChangeCostDynamicFactor += m_xChangeCostDynamicFactorStep; + m_point.m_x++; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_x += 2; +#endif + return true; + } + + return false; + } + + void EllipsePlotter::IncrementY() + { + const int32_t yStepCost = m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + m_sqDistFromEdge += yStepCost; + m_yChangeCostDynamicFactor += m_yChangeCostDynamicFactorStep; + m_point.m_y++; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_y += 2; +#endif + } + + bool EllipsePlotter::ConditionalIncrementY() + { + const int32_t yStepCost = m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + if (m_sqDistFromEdge + yStepCost >= 0) + { + m_sqDistFromEdge += yStepCost; + m_yChangeCostDynamicFactor += m_yChangeCostDynamicFactorStep; + m_point.m_y++; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_y += 2; +#endif + return true; + } + + return false; + } + + void EllipsePlotter::DecrementX() + { + const int32_t xStepCost = -m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + + m_sqDistFromEdge += xStepCost; + m_xChangeCostDynamicFactor -= m_xChangeCostDynamicFactorStep; + m_point.m_x--; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_x -= 2; +#endif + } + + bool EllipsePlotter::ConditionalDecrementX() + { + const int32_t xStepCost = -m_xChangeCostDynamicFactor + m_xChangeCostStaticFactor; + + if (m_sqDistFromEdge + xStepCost >= 0) + { + m_sqDistFromEdge += xStepCost; + m_xChangeCostDynamicFactor -= m_xChangeCostDynamicFactorStep; + m_point.m_x--; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_x -= 2; +#endif + return true; + } + + return false; + } + + void EllipsePlotter::DecrementY() + { + const int32_t yStepCost = -m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + m_sqDistFromEdge += yStepCost; + m_yChangeCostDynamicFactor -= m_yChangeCostDynamicFactorStep; + m_point.m_y--; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_y -= 2; +#endif + } + + bool EllipsePlotter::ConditionalDecrementY() + { + const int32_t yStepCost = -m_yChangeCostDynamicFactor + m_yChangeCostStaticFactor; + + if (m_sqDistFromEdge + yStepCost >= 0) + { + m_sqDistFromEdge += yStepCost; + m_yChangeCostDynamicFactor -= m_yChangeCostDynamicFactorStep; + m_point.m_y--; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter.m_y -= 2; +#endif + return true; + } + + return false; + } + + const Vec2i &EllipsePlotter::GetPoint() const + { + return m_point; + } + + void EllipsePlotter::Reset(const Rect2i &bounds) + { + assert(bounds.IsValid()); + m_quadrant = Quadrant_PxPy; + + m_2center = bounds.m_topLeft + bounds.m_bottomRight - Vec2i(1, 1); + m_diameters = bounds.m_bottomRight - bounds.m_topLeft - Vec2i(1, 1); + + m_point.m_x = bounds.m_bottomRight.m_x - 1; + m_point.m_y = (m_2center.m_y + 1) / 2; + + const Vec2i offsetFromCenterTimes2 = (m_point + m_point) - m_2center; + +#if PL_DEBUG_ELLIPSE_PLOTTER + m_2offsetFromCenter = offsetFromCenterTimes2; +#endif + + m_sqDistFromEdge = SquareInt32(offsetFromCenterTimes2.m_x * m_diameters.m_y) + SquareInt32(offsetFromCenterTimes2.m_y * m_diameters.m_x) - SquareInt32(m_diameters.m_x * m_diameters.m_y); + + const int32_t xCostMultiplier = 4 * SquareInt32(m_diameters.m_y); + const int32_t yCostMultiplier = 4 * SquareInt32(m_diameters.m_x); + + m_xChangeCostDynamicFactorStep = 2 * xCostMultiplier; + m_yChangeCostDynamicFactorStep = 2 * yCostMultiplier; + m_xChangeCostStaticFactor = 4 * SquareInt32(m_diameters.m_y); + m_yChangeCostStaticFactor = 4 * SquareInt32(m_diameters.m_x); + + m_xChangeCostDynamicFactor = offsetFromCenterTimes2.m_x * xCostMultiplier; + m_yChangeCostDynamicFactor = offsetFromCenterTimes2.m_y * yCostMultiplier; + } +} diff --git a/PortabilityLayer/EllipsePlotter.h b/PortabilityLayer/EllipsePlotter.h new file mode 100644 index 0000000..f52a880 --- /dev/null +++ b/PortabilityLayer/EllipsePlotter.h @@ -0,0 +1,62 @@ +#pragma once + +#include "IPlotter.h" +#include "PlotDirection.h" +#include "Vec2i.h" + +#define PL_DEBUG_ELLIPSE_PLOTTER 1 + +namespace PortabilityLayer +{ + struct Rect2i; + + class EllipsePlotter final : public IPlotter + { + public: + EllipsePlotter(); + PlotDirection PlotNext() override; + const Vec2i &GetPoint() const override; + + void Reset(const Rect2i &bounds); + + private: + enum Quadrant + { + Quadrant_NxNy, + Quadrant_NxPy, + Quadrant_PxNy, + Quadrant_PxPy, + + Quadrant_Finished + }; + + void IncrementX(); + bool ConditionalIncrementX(); + + void IncrementY(); + bool ConditionalIncrementY(); + + void DecrementX(); + bool ConditionalDecrementX(); + + void DecrementY(); + bool ConditionalDecrementY(); + + Vec2i m_2center; + Vec2i m_point; + Vec2i m_diameters; + int32_t m_sqDistFromEdge; + Quadrant m_quadrant; + + int32_t m_xChangeCostDynamicFactor; + int32_t m_yChangeCostDynamicFactor; + int32_t m_xChangeCostDynamicFactorStep; + int32_t m_yChangeCostDynamicFactorStep; + int32_t m_xChangeCostStaticFactor; + int32_t m_yChangeCostStaticFactor; + +#if PL_DEBUG_ELLIPSE_PLOTTER 1 + Vec2i m_2offsetFromCenter; +#endif + }; +} diff --git a/PortabilityLayer/LinePlotter.h b/PortabilityLayer/LinePlotter.h index c0a4236..8b7ac13 100644 --- a/PortabilityLayer/LinePlotter.h +++ b/PortabilityLayer/LinePlotter.h @@ -1,16 +1,17 @@ #pragma once +#include "IPlotter.h" #include "PlotDirection.h" #include "Vec2i.h" namespace PortabilityLayer { - class LinePlotter + class LinePlotter final : public IPlotter { public: LinePlotter(); - PlotDirection PlotNext(); - const Vec2i &GetPoint() const; + PlotDirection PlotNext() override; + const Vec2i &GetPoint() const override; void Reset(const Vec2i &pointA, const Vec2i &pointB); diff --git a/PortabilityLayer/PLQDraw.cpp b/PortabilityLayer/PLQDraw.cpp index 01b1ed1..939ac78 100644 --- a/PortabilityLayer/PLQDraw.cpp +++ b/PortabilityLayer/PLQDraw.cpp @@ -16,6 +16,7 @@ #include "ResTypeID.h" #include "RGBAColor.h" #include "ScanlineMask.h" +#include "ScanlineMaskConverter.h" #include "ScanlineMaskIterator.h" #include "QDStandardPalette.h" #include "WindowManager.h" @@ -571,7 +572,15 @@ void PaintRect(const Rect *rect) void PaintOval(const Rect *rect) { - PL_NotYetImplemented_TODO("Ovals"); + if (!rect->IsValid()) + return; + + PortabilityLayer::ScanlineMask *mask = PortabilityLayer::ScanlineMaskConverter::CompileEllipse(PortabilityLayer::Rect2i(rect->top, rect->left, rect->bottom, rect->right)); + if (mask) + { + FillScanlineMask(mask); + mask->Destroy(); + } } void FillScanlineSpan(uint8_t *rowStart, size_t startCol, size_t endCol) diff --git a/PortabilityLayer/PortabilityLayer.vcxproj b/PortabilityLayer/PortabilityLayer.vcxproj index 9125d81..d33775d 100644 --- a/PortabilityLayer/PortabilityLayer.vcxproj +++ b/PortabilityLayer/PortabilityLayer.vcxproj @@ -139,6 +139,7 @@ + @@ -261,6 +262,7 @@ + diff --git a/PortabilityLayer/PortabilityLayer.vcxproj.filters b/PortabilityLayer/PortabilityLayer.vcxproj.filters index 38a1a04..24cb6ea 100644 --- a/PortabilityLayer/PortabilityLayer.vcxproj.filters +++ b/PortabilityLayer/PortabilityLayer.vcxproj.filters @@ -393,6 +393,9 @@ Header Files + + Header Files + @@ -596,5 +599,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/PortabilityLayer/Rect2i.h b/PortabilityLayer/Rect2i.h index 6a271b5..cf46149 100644 --- a/PortabilityLayer/Rect2i.h +++ b/PortabilityLayer/Rect2i.h @@ -19,7 +19,7 @@ namespace PortabilityLayer Rect2i(const Rect2i &other); explicit Rect2i(const Rect &other); Rect2i(const Vec2i &topLeft, const Vec2i &bottomRight); - Rect2i(int32_t left, int32_t top, int32_t bottom, int32_t right); + Rect2i(int32_t top, int32_t left, int32_t bottom, int32_t right); Rect2i operator+(const Vec2i &other) const; Rect2i operator-(const Vec2i &other) const; diff --git a/PortabilityLayer/ScanlineMaskConverter.cpp b/PortabilityLayer/ScanlineMaskConverter.cpp index 07b3b9c..03bfdbe 100644 --- a/PortabilityLayer/ScanlineMaskConverter.cpp +++ b/PortabilityLayer/ScanlineMaskConverter.cpp @@ -1,4 +1,7 @@ #include "ScanlineMaskConverter.h" + +#include "EllipsePlotter.h" +#include "Rect2i.h" #include "ScanlineMask.h" #include "Vec2i.h" #include "LinePlotter.h" @@ -252,6 +255,33 @@ namespace PortabilityLayer return ScanlineMask::Create(Rect::Create(minPoint.m_y, minPoint.m_x, minPoint.m_y + static_cast(height), minPoint.m_x + static_cast(width)), maskBuilder); } + class SinglePointPlotter final : public IPlotter + { + public: + explicit SinglePointPlotter(const Vec2i &point); + + PlotDirection PlotNext() override; + const Vec2i &GetPoint() const override; + + private: + const Vec2i &m_point; + }; + + SinglePointPlotter::SinglePointPlotter(const Vec2i &point) + : m_point(point) + { + } + + PlotDirection SinglePointPlotter::PlotNext() + { + return PlotDirection_Exhausted; + } + + const Vec2i &SinglePointPlotter::GetPoint() const + { + return m_point; + } + class PolyPlotter final : public IPlotter { public: @@ -322,4 +352,34 @@ namespace PortabilityLayer PolyPlotter polyPlotter(points, numPoints); return ComputePlot(width, height, minPoint, polyPlotter); } + + ScanlineMask *ScanlineMaskConverter::CompileEllipse(const Rect2i &rect) + { + if (!rect.IsValid() || rect.m_topLeft.m_x == rect.m_bottomRight.m_x || rect.m_topLeft.m_y == rect.m_bottomRight.m_y) + return nullptr; + + const uint32_t width = rect.m_bottomRight.m_x - rect.m_topLeft.m_x; + const uint32_t height = rect.m_bottomRight.m_y - rect.m_topLeft.m_y; + + if (width == 1 || height == 1) + { + if (width == 1 && height == 1) + { + SinglePointPlotter plotter(rect.m_topLeft); + return ComputePlot(1, 1, rect.m_topLeft, plotter); + } + else + { + LinePlotter plotter; + plotter.Reset(rect.m_topLeft, rect.m_bottomRight - Vec2i(1, 1)); + return ComputePlot(width, height, rect.m_topLeft, plotter); + } + } + else + { + EllipsePlotter plotter; + plotter.Reset(rect); + return ComputePlot(width, height, rect.m_topLeft, plotter); + } + } } diff --git a/PortabilityLayer/ScanlineMaskConverter.h b/PortabilityLayer/ScanlineMaskConverter.h index d4bcc58..82ef623 100644 --- a/PortabilityLayer/ScanlineMaskConverter.h +++ b/PortabilityLayer/ScanlineMaskConverter.h @@ -6,10 +6,12 @@ namespace PortabilityLayer { class ScanlineMask; struct Vec2i; + struct Rect2i; class ScanlineMaskConverter { public: static ScanlineMask *CompilePoly(const Vec2i *points, size_t numPoints); + static ScanlineMask *CompileEllipse(const Rect2i &rect); }; }