From afef72f916ab55d2c5642699de4dda7cc747fd7d Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Mon, 3 May 2021 03:45:29 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20New=20map=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses integer math only: This speeds up the rendering and eliminates some zoom artifacts. Improves player indicator look -- it's now symmetric and more legible. --- CMakeLists.txt | 1 + Source/automap.cpp | 155 ++++++++++++++-------------- Source/automap_render.cpp | 210 ++++++++++++++++++++++++++++++++++++++ Source/automap_render.hpp | 77 ++++++++++++++ Source/engine.cpp | 48 +++++++-- Source/engine.h | 25 ++++- Source/qol/common.cpp | 8 +- 7 files changed, 424 insertions(+), 100 deletions(-) create mode 100644 Source/automap_render.cpp create mode 100644 Source/automap_render.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e163f74fa..7c7aeb915 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,7 @@ add_library(PKWare STATIC set(devilutionx_SRCS Source/appfat.cpp Source/automap.cpp + Source/automap_render.cpp Source/capture.cpp Source/codec.cpp Source/control.cpp diff --git a/Source/automap.cpp b/Source/automap.cpp index 6938a22ad..001b0c5e8 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -7,6 +7,7 @@ #include +#include "automap_render.hpp" #include "control.h" #include "inv.h" #include "miniwin/miniwin.h" @@ -53,35 +54,30 @@ enum MapFlags : uint8_t { // clang-format on }; -void DrawSquare(const CelOutputBuffer &out, Point center, uint8_t color) +void DrawDiamond(const CelOutputBuffer &out, Point center, uint8_t color) { const Point left { center.x - AmLine16, center.y }; const Point top { center.x, center.y - AmLine8 }; - const Point right { center.x + AmLine16, center.y }; const Point bottom { center.x, center.y + AmLine8 }; - DrawLineTo(out, top, left, color); - DrawLineTo(out, top, right, color); - DrawLineTo(out, bottom, left, color); - DrawLineTo(out, bottom, right, color); + DrawMapLineNE(out, left, AmLine8, color); + DrawMapLineSE(out, left, AmLine8, color); + DrawMapLineSE(out, top, AmLine8, color); + DrawMapLineNE(out, bottom, AmLine8, color); } void DrawMapVerticalDoor(const CelOutputBuffer &out, Point center) { - const Point offset { center.x - AmLine16, center.y - AmLine8 }; - - DrawLineTo(out, { center.x + AmLine16, offset.y }, { center.x + AmLine8, offset.y + AmLine4 }, MapColorsDim); - DrawLineTo(out, { offset.x, center.y + AmLine8 }, { offset.x + AmLine8, center.y + AmLine8 - AmLine4 }, MapColorsDim); - DrawSquare(out, center, MapColorsBright); + DrawMapLineNE(out, { center.x + AmLine8, center.y - AmLine4 }, AmLine4, MapColorsDim); + DrawMapLineNE(out, { center.x - AmLine16, center.y + AmLine8 }, AmLine4, MapColorsDim); + DrawDiamond(out, center, MapColorsBright); } void DrawMapHorizontalDoor(const CelOutputBuffer &out, Point center) { - const Point offset { center.x + AmLine16, center.y - AmLine8 }; - - DrawLineTo(out, { center.x - AmLine16, offset.y }, { center.x - AmLine16 + AmLine8, offset.y + AmLine4 }, MapColorsDim); - DrawLineTo(out, { offset.x, center.y + AmLine8 }, { offset.x - AmLine8, center.y + AmLine8 - AmLine4 }, MapColorsDim); - DrawSquare(out, center, MapColorsBright); + DrawMapLineSE(out, { center.x - AmLine16, center.y - AmLine8 }, AmLine4, MapColorsDim); + DrawMapLineSE(out, { center.x + AmLine8, center.y + AmLine4 }, AmLine4, MapColorsDim); + DrawDiamond(out, center, MapColorsBright); } /** @@ -111,10 +107,13 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT } if ((flags & MapFlagsStairs) != 0) { - DrawLineTo(out, { center.x - AmLine8, center.y - AmLine8 - AmLine4 }, { center.x + AmLine8 + AmLine16, center.y + AmLine4 }, MapColorsBright); - DrawLineTo(out, { center.x - AmLine16, center.y - AmLine8 }, { center.x + AmLine16, center.y + AmLine8 }, MapColorsBright); - DrawLineTo(out, { center.x - AmLine16 - AmLine8, center.y - AmLine4 }, { center.x + AmLine8, center.y + AmLine8 + AmLine4 }, MapColorsBright); - DrawLineTo(out, { center.x - AmLine32, center.y }, { center.x, center.y + AmLine16 }, MapColorsBright); + constexpr int NumStairSteps = 4; + const Point offset = { -AmLine8, AmLine4 }; + Point p = { center.x - AmLine8 , center.y - AmLine8 - AmLine4 }; + for (int i = 0; i < NumStairSteps; ++i) { + DrawMapLineSE(out, p, AmLine16, MapColorsBright); + p += offset; + } } bool drawVertical = false; @@ -123,7 +122,7 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT bool drawCaveVertical = false; switch (automapType & MapFlagsType) { case 1: // stand-alone column or other unpassable object - DrawSquare(out, { center.x, center.y - AmLine8 }, MapColorsDim); + DrawDiamond(out, { center.x, center.y - AmLine8 }, MapColorsDim); break; case 2: case 5: @@ -162,14 +161,14 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT DrawMapVerticalDoor(out, { center.x - AmLine16, center.y - AmLine8 }); } if ((flags & MapFlagsVerticalGrate) != 0) { // right-facing half-wall - DrawLineTo(out, { center.x - AmLine16, center.y - AmLine8 }, { center.x - AmLine32, center.y }, MapColorsDim); + DrawMapLineNE(out, { center.x - AmLine32, center.y }, AmLine8, MapColorsDim); flags |= MapFlagsVerticalArch; } if ((flags & MapFlagsVerticalArch) != 0) { // window or passable column - DrawSquare(out, { center.x, center.y - AmLine8 }, MapColorsDim); + DrawDiamond(out, { center.x, center.y - AmLine8 }, MapColorsDim); } if ((flags & (MapFlagsMapVerticalDoor | MapFlagsVerticalGrate | MapFlagsVerticalArch)) == 0) { - DrawLineTo(out, { center.x, center.y - AmLine16 }, { center.x - AmLine32, center.y }, MapColorsDim); + DrawMapLineNE(out, { center.x - AmLine32, center.y }, AmLine16, MapColorsDim); } } @@ -178,14 +177,14 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT DrawMapHorizontalDoor(out, { center.x + AmLine16, center.y - AmLine8 }); } if ((flags & MapFlagsHorizontalGrate) != 0) { - DrawLineTo(out, { center.x + AmLine16, center.y - AmLine8 }, { center.x + AmLine32, center.y }, MapColorsDim); + DrawMapLineSE(out, { center.x + AmLine16, center.y - AmLine8 }, AmLine8, MapColorsDim); flags |= MapFlagsHorizontalArch; } if ((flags & MapFlagsHorizontalArch) != 0) { - DrawSquare(out, { center.x, center.y - AmLine8 }, MapColorsDim); + DrawDiamond(out, { center.x, center.y - AmLine8 }, MapColorsDim); } if ((flags & (MapFlagsMapHorizontalDoor | MapFlagsHorizontalGrate | MapFlagsHorizontalArch)) == 0) { - DrawLineTo(out, { center.x, center.y - AmLine16 }, { center.x + AmLine32, center.y }, MapColorsDim); + DrawMapLineSE(out, { center.x, center.y - AmLine16 }, AmLine16, MapColorsDim); } } @@ -194,7 +193,7 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT if ((flags & MapFlagsMapVerticalDoor) != 0) { DrawMapHorizontalDoor(out, { center.x - AmLine16, center.y + AmLine8 }); } else { - DrawLineTo(out, { center.x, center.y + AmLine16 }, { center.x - AmLine32, center.y }, MapColorsDim); + DrawMapLineSE(out, { center.x - AmLine32, center.y }, AmLine16, MapColorsDim); } } @@ -202,7 +201,7 @@ void DrawAutomapTile(const CelOutputBuffer &out, Point center, uint16_t automapT if ((flags & MapFlagsMapHorizontalDoor) != 0) { DrawMapVerticalDoor(out, { center.x + AmLine16, center.y + AmLine8 }); } else { - DrawLineTo(out, { center.x, center.y + AmLine16 }, { center.x + AmLine32, center.y }, MapColorsDim); + DrawMapLineNE(out, { center.x, center.y + AmLine16 }, AmLine16, MapColorsDim); } } } @@ -244,7 +243,7 @@ void SearchAutomapItem(const CelOutputBuffer &out) screen.x += 160; } screen.y -= AmLine8; - DrawSquare(out, screen, MapColorsItem); + DrawDiamond(out, screen, MapColorsItem); } } } @@ -281,57 +280,57 @@ void DrawAutomapPlr(const CelOutputBuffer &out, int playerId) } base.y -= AmLine8; - Point point; - Point left; - Point right; - switch (plr[playerId]._pdir) { - case DIR_N: - point = { base.x, base.y - AmLine16 }; - left = { base.x - AmLine4, base.y - AmLine8 }; - right = { base.x + AmLine4, base.y - AmLine8 }; - break; - case DIR_NE: - point = { base.x + AmLine16, base.y - AmLine8 }; - left = { base.x + AmLine8, base.y - AmLine8 }; - right = { base.x + AmLine8 + AmLine4, base.y }; - break; - case DIR_E: - point = { base.x + AmLine16, base.y }; - left = { base.x + AmLine8, base.y - AmLine4 }; - right = { base.x + AmLine8, base.y + AmLine4 }; - break; - case DIR_SE: - point = { base.x + AmLine16, base.y + AmLine8 }; - left = { base.x + AmLine8 + AmLine4, base.y }; - right = { base.x + AmLine8, base.y + AmLine8 }; - break; + case DIR_N: { + const Point point { base.x, base.y - AmLine16 }; + DrawVerticalLine(out, point, AmLine16, playerColor); + DrawMapLineNE2(out, { point.x - AmLine4, point.y + 2 * AmLine4 }, AmLine4, playerColor); + DrawMapLineNW2(out, { point.x + AmLine4, point.y + 2 * AmLine4 }, AmLine4, playerColor); + } break; + case DIR_NE: { + const Point point { base.x + AmLine16, base.y - AmLine8 }; + DrawHorizontalLine(out, {point.x - AmLine8, point.y}, AmLine8, playerColor); + DrawMapLineNE(out, { point.x - 2 * AmLine8, point.y + AmLine8 }, AmLine8, playerColor); + DrawMapLineSW2(out, point, AmLine4, playerColor); + } break; + case DIR_E: { + const Point point { base.x + AmLine16, base.y }; + DrawMapLineNW(out, point, AmLine4, playerColor); + DrawHorizontalLine(out, { point.x - AmLine16, point.y }, AmLine16, playerColor); + DrawMapLineSW(out, point, AmLine4, playerColor); + } break; + case DIR_SE: { + const Point point { base.x + AmLine16, base.y + AmLine8 }; + DrawMapLineSE(out, { point.x - 2 * AmLine8, point.y - AmLine8 }, AmLine8, playerColor); + DrawHorizontalLine(out, { point.x - (AmLine8 + 1), point.y }, AmLine8 + 1, playerColor); + DrawMapLineNW2(out, point, AmLine4, playerColor); + } break; case DIR_S: - case DIR_OMNI: - point = { base.x, base.y + AmLine16 }; - left = { base.x + AmLine4, base.y + AmLine8 }; - right = { base.x - AmLine4, base.y + AmLine8 }; - break; - case DIR_SW: - point = { base.x - AmLine16, base.y + AmLine8 }; - left = { base.x - AmLine4 - AmLine8, base.y }; - right = { base.x - AmLine8, base.y + AmLine8 }; - break; - case DIR_W: - point = { base.x - AmLine16, base.y }; - left = { base.x - AmLine8, base.y - AmLine4 }; - right = { base.x - AmLine8, base.y + AmLine4 }; - break; - case DIR_NW: - point = { base.x - AmLine16, base.y - AmLine8 }; - left = { base.x - AmLine8, base.y - AmLine8 }; - right = { base.x - AmLine4 - AmLine8, base.y }; - break; + case DIR_OMNI: { + const Point point { base.x, base.y + AmLine16 }; + DrawVerticalLine(out, { point.x, point.y - AmLine16 }, AmLine16, playerColor); + DrawMapLineSW2(out, { point.x + AmLine4, point.y - 2 * AmLine4}, AmLine4, playerColor); + DrawMapLineSE2(out, { point.x - AmLine4, point.y - 2 * AmLine4}, AmLine4, playerColor); + } break; + case DIR_SW: { + const Point point { base.x - AmLine16, base.y + AmLine8 }; + DrawMapLineNE2(out, point, AmLine4, playerColor); + DrawMapLineSW(out, { point.x + 2 * AmLine8, point.y - AmLine8 }, AmLine8, playerColor); + DrawHorizontalLine(out, point, AmLine8 + 1, playerColor); + } break; + case DIR_W: { + const Point point { base.x - AmLine16, base.y }; + DrawMapLineNE(out, point, AmLine4, playerColor); + DrawHorizontalLine(out, point, AmLine16 + 1, playerColor); + DrawMapLineSE(out, point, AmLine4, playerColor); + } break; + case DIR_NW: { + const Point point { base.x - AmLine16, base.y - AmLine8 }; + DrawMapLineNW(out, { point.x + 2 * AmLine8, point.y + AmLine8 }, AmLine8, playerColor); + DrawHorizontalLine(out, point, AmLine8 + 1, playerColor); + DrawMapLineSE2(out, point, AmLine4, playerColor); + } break; } - - DrawLineTo(out, base, point, playerColor); - DrawLineTo(out, point, left, playerColor); - DrawLineTo(out, point, right, playerColor); } /** diff --git a/Source/automap_render.cpp b/Source/automap_render.cpp new file mode 100644 index 000000000..0a65b011f --- /dev/null +++ b/Source/automap_render.cpp @@ -0,0 +1,210 @@ +#include "automap_render.hpp" + +namespace devilution { +namespace { + +enum class DirectionX { + EAST = 1, + WEST = -1, +}; + +enum class DirectionY { + SOUTH = 1, + NORTH = -1, +}; + +template +void UnsafeDrawMapLine(const CelOutputBuffer &out, Point from, int height, bool drawTipPixel, std::uint8_t colorIndex) +{ + auto *dst = out.at(from.x, from.y); + const auto pitch = out.pitch(); + while (height-- > 0) { + *dst = colorIndex; + dst += static_cast(DirX); + *dst = colorIndex; + dst += static_cast(DirX) + static_cast(DirY) * pitch; + } + if (drawTipPixel) + *dst = colorIndex; +} + +int Width(int height) +{ + return 2 * height; +} + +int Height(int width) +{ + return width / 2; +} + +int HeightCeil(int width) +{ + return (width + 1) / 2; +} + +template +bool InDirectionBounds(const CelOutputBuffer &out, Point from) +{ + if (DirX == DirectionX::EAST) + if (from.x >= out.w()) + return false; + if (DirX == DirectionX::WEST) + if (from.x < 0) + return false; + if (DirY == DirectionY::SOUTH) + if (from.y >= out.h()) + return false; + if (DirY == DirectionY::NORTH) + if (from.y < 0) + return false; + return true; +} + +template +void DrawMapLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + if (!InDirectionBounds(out, from)) + return; + + bool drawFirstPixel = true; // skip the first pixel + + // First clip in the X direction. Allows for a 1-2 pixel overdraw, taken care of later. + if (DirX == DirectionX::EAST) { + if (from.x < 0) { + int skip = -from.x; + if (skip % 2 != 0) { + drawFirstPixel = false; + ++skip; + --height; + } + height -= Height(skip); + from.x = 0; + from.y += static_cast(DirY) * Height(skip); + } + if (from.x + Width(height) > out.w()) { + height = HeightCeil(out.w() - from.x); + } + } else { + if (from.x >= out.w()) { + int skip = from.x - out.w() + 1; + if (skip % 2 != 0) { + drawFirstPixel = false; + ++skip; + --height; + } + height -= Height(skip); + from.x = out.w() - 1; + from.y += static_cast(DirY) * Height(skip); + } + if (from.x < Width(height)) { + height = HeightCeil(from.x + 1); + } + } + + if (DirY == DirectionY::SOUTH) { + if (from.y < 0) { + const int skip = -from.y; + height -= skip; + from.y = 0; + from.x += static_cast(DirX) * Width(skip); + } + if (from.y + height > out.h()) { + height = out.h() - from.y; + } + } else { + if (from.y >= out.h()) { + const int skip = from.y - out.h() + 1; + from.y = out.h() - 1; + height -= skip; + from.x += static_cast(DirX) * Width(skip); + } + if (from.y < height) { + height = from.y + 1; + } + } + + if (!InDirectionBounds(out, from)) + return; + + const int overdrawX = DirX == DirectionX::EAST + ? from.x + Width(height) + 1 - out.w() + : Width(height) + 1 - from.x; + + const bool drawTipPixel = overdrawX != 1 && overdrawX != 2 + && !((DirY == DirectionY::SOUTH && from.y + height == out.h()) || (DirY == DirectionY::NORTH && from.y + 1 - height == 0)); + const bool drawLastNonTipPixel = overdrawX != 2; + + if (!drawFirstPixel) + SetPixel(out, { from.x + static_cast(DirX), from.y }, colorIndex); + if (!drawLastNonTipPixel) + --height; + if (height >= 0) + UnsafeDrawMapLine(out, from, height, drawTipPixel, colorIndex); + if (!drawLastNonTipPixel) { + SetPixel( + out, + { from.x + 2 * static_cast(DirX) * (height), + from.y + static_cast(DirY) * (height) }, + colorIndex); + } +} + +template +void DrawMapLine2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + // This is only used to draw small detail, so there is no unsafe version. + // We bounds-check each pixel individually instead. + while (width-- > 0) { + SetPixel(out, from, colorIndex); + from.y += static_cast(DirY); + SetPixel(out, from, colorIndex); + from.y += static_cast(DirY); + from.x += static_cast(DirX); + } + SetPixel(out, from, colorIndex); +} + +} // namespace + +void DrawMapLineNE(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + DrawMapLine(out, from, height, colorIndex); +} + +void DrawMapLineSE(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + DrawMapLine(out, from, height, colorIndex); +} + +void DrawMapLineNW(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + DrawMapLine(out, from, height, colorIndex); +} + +void DrawMapLineSW(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + DrawMapLine(out, from, height, colorIndex); +} + +void DrawMapLineNE2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + DrawMapLine2(out, from, width, colorIndex); +} + +void DrawMapLineSE2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + DrawMapLine2(out, from, width, colorIndex); +} + +void DrawMapLineNW2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + DrawMapLine2(out, from, width, colorIndex); +} + +void DrawMapLineSW2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + DrawMapLine2(out, from, width, colorIndex); +} + +} // namespace devilution diff --git a/Source/automap_render.hpp b/Source/automap_render.hpp new file mode 100644 index 000000000..5a4c60320 --- /dev/null +++ b/Source/automap_render.hpp @@ -0,0 +1,77 @@ +#include "engine.h" + +namespace devilution { + +/** + * @brief Draw a line in the target buffer from the given point towards north east at an `atan(1/2)` angle. + * + * Draws 2 horizontal pixels for each vertical step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x + 2 * height + 1, from.y - height }`. + */ +void DrawMapLineNE(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards south east at an `atan(1/2)` angle. + * + * Draws 2 horizontal pixels for each vertical step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x + 2 * height + 1, from.y + height }`. + */ +void DrawMapLineSE(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards north west at an `atan(1/2)` angle. + * + * Draws 2 horizontal pixels for each vertical step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x - 2 * height + 1, from.y - height }`. + */ +void DrawMapLineNW(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards south west at an `atan(1/2)` angle. + * + * Draws 2 horizontal pixels for each vertical step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x - 2 * height + 1, from.y + height }`. + */ +void DrawMapLineSW(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards north east at an `atan(1/2)` angle. + * + * Draws 2 vertical pixels for each horizontal step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x + width + 1, from.y - 2 * width }`. + */ +void DrawMapLineNE2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards south east at an `atan(2)` angle. + * + * Draws 2 vertical pixels for each horizontal step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x + width + 1, from.y + 2 * width }`. + */ +void DrawMapLineSE2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards north west at an `atan(1/2)` angle. + * + * Draws 2 vertical pixels for each horizontal step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x - (width + 1), from.y - 2 * width }`. + */ +void DrawMapLineNW2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex); + +/** + * @brief Draw a line in the target buffer from the given point towards south west at an `atan(1/2)` angle. + * + * Draws 2 vertical pixels for each horizontal step, then an additional one where it draws 1 pixel. + * + * The end point is at `{ from.x - (width + 1), from.y + 2 * width }`. + */ +void DrawMapLineSW2(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex); + +} // namespace devilution diff --git a/Source/engine.cpp b/Source/engine.cpp index 738051ad7..be77d79af 100644 --- a/Source/engine.cpp +++ b/Source/engine.cpp @@ -559,18 +559,44 @@ void SetPixel(const CelOutputBuffer &out, Point position, BYTE col) *out.at(position.x, position.y) = col; } -void DrawLineTo(const CelOutputBuffer &out, Point a, Point b, BYTE color_index) +void DrawHorizontalLine(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) { - int dx = b.x - a.x; - int dy = b.y - a.y; - int steps = abs(dx) > abs(dy) ? abs(dx) : abs(dy); - float ix = dx / (float)steps; - float iy = dy / (float)steps; - float sx = a.x; - float sy = a.y; - - for (int i = 0; i <= steps; i++, sx += ix, sy += iy) { - SetPixel(out, { static_cast(sx), static_cast(sy) }, color_index); + if (from.y < 0 || from.y >= out.h() || from.x >= out.w() || width <= 0 || from.x + width <= 0) + return; + if (from.x < 0) { + width += from.x; + from.x = 0; + } + if (from.x + width > out.w()) + width = (from.x + width) - out.w(); + return UnsafeDrawHorizontalLine(out, from, width, colorIndex); +} + +void UnsafeDrawHorizontalLine(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex) +{ + std::memset(out.at(from.x, from.y), colorIndex, width); +} + +void DrawVerticalLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + if (from.x < 0 || from.x >= out.w() || from.y >= out.h() || height <= 0 || from.y + height <= 0) + return; + if (from.y < 0) { + height += from.y; + from.y = 0; + } + if (from.y + height > out.h()) + height = (from.y + height) - out.h(); + return UnsafeDrawVerticalLine(out, from, height, colorIndex); +} + +void UnsafeDrawVerticalLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex) +{ + auto *dst = out.at(from.x, from.y); + const auto pitch = out.pitch(); + while (height-- > 0) { + *dst = colorIndex; + dst += pitch; } } diff --git a/Source/engine.h b/Source/engine.h index 8372d8f94..a1fd53d7b 100644 --- a/Source/engine.h +++ b/Source/engine.h @@ -565,13 +565,28 @@ void Cl2DrawLightTbl(const CelOutputBuffer &out, int sx, int sy, const CelSprite void Cl2DrawLight(const CelOutputBuffer &out, int sx, int sy, const CelSprite &cel, int frame); /** - * @brief Draw a line in the target buffer + * @brief Draw a horizontal line segment in the target buffer (left to right) * @param out Target buffer - * @param a Back buffer coordinate - * @param b Back buffer coordinate - * @param color_index Color index from current palette + * @param from Start of the line segment + * @param width + * @param colorIndex Color index from current palette */ -void DrawLineTo(const CelOutputBuffer &out, Point a, Point b, BYTE color_index); +void DrawHorizontalLine(const CelOutputBuffer &out, Point from, int width, std::uint8_t colorIndex); + +/** Same as DrawHorizontalLine but without bounds clipping. */ +void UnsafeDrawHorizontalLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** + * @brief Draw a vertical line segment in the target buffer (top to bottom) + * @param out Target buffer + * @param from Start of the line segment + * @param height + * @param colorIndex Color index from current palette + */ +void DrawVerticalLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); + +/** Same as DrawVerticalLine but without bounds clipping. */ +void UnsafeDrawVerticalLine(const CelOutputBuffer &out, Point from, int height, std::uint8_t colorIndex); /** * Draws a half-transparent rectangle by blacking out odd pixels on odd lines, diff --git a/Source/qol/common.cpp b/Source/qol/common.cpp index 50eba0360..cfa87579d 100644 --- a/Source/qol/common.cpp +++ b/Source/qol/common.cpp @@ -25,16 +25,12 @@ int GetTextWidth(const char *s) void FastDrawHorizLine(const CelOutputBuffer &out, int x, int y, int width, Uint8 col) { - memset(out.at(x, y), col, width); + UnsafeDrawHorizontalLine(out, {x, y}, width, col); } void FastDrawVertLine(const CelOutputBuffer &out, int x, int y, int height, Uint8 col) { - BYTE *p = out.at(x, y); - for (int j = 0; j < height; j++) { - *p = col; - p += out.pitch(); - } + UnsafeDrawVerticalLine(out, {x, y}, height, col); } char *PrintWithSeparator(char *out, long long n)