You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1373 lines
42 KiB

/**
* @file dun_render.cpp
*
* Implementation of functionality for rendering the level tiles.
*/
#include "engine/render/dun_render.hpp"
#include <algorithm>
#include <climits>
#include <cstdint>
#include "lighting.h"
#include "options.h"
namespace devilution {
namespace {
/**
* Tile type.
*
* The tile type determines data encoding and the shape.
*
* Each tile type has its own encoding but they all encode data in the order
* of bottom-to-top (bottom row first).
*/
enum class TileType {
/**
* 🮆 A 32x32 square. Stored as an array of pixels.
*/
Square,
/**
* 🮆 A 32x32 square with transparency. RLE encoded.
*
* Each run starts with an int8_t value.
* If positive, it is followed by this many pixels.
* If negative, it indicates `-value` fully transparent pixels, which are omitted.
*
* Runs do not cross row boundaries.
*/
TransparentSquare,
/**
*🭮 Left-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes before every even row.
*
* The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row).
*
* Encoding:
* for i in [0, 30]:
* - 2 unused bytes if i is even
* - row (only the pixels within the triangle)
*/
LeftTriangle,
/**
* 🭬Right-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes after every even row.
*
* The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row).
*
* Encoding:
* for i in [0, 30]:
* - row (only the pixels within the triangle)
* - 2 unused bytes if i is even
*/
RightTriangle,
/**
* 🭓 Left-pointing 32x32 trapezoid: a 32x16 rectangle and the 16x16 bottom part of `LeftTriangle`.
*
* Begins with triangle part, which uses the `LeftTriangle` encoding,
* and is followed by a flat array of pixels for the top rectangle part.
*/
LeftTrapezoid,
/**
* 🭞 Right-pointing 32x32 trapezoid: 32x16 rectangle and the 16x16 bottom part of `RightTriangle`.
*
* Begins with the triangle part, which uses the `RightTriangle` encoding,
* and is followed by a flat array of pixels for the top rectangle part.
*/
RightTrapezoid,
};
/** Width of a tile rendering primitive. */
constexpr std::int_fast16_t Width = TILE_WIDTH / 2;
/** Height of a tile rendering primitive (except triangles). */
constexpr std::int_fast16_t Height = TILE_HEIGHT;
/** Height of the lower triangle of a triangular or a trapezoid tile. */
constexpr std::int_fast16_t LowerHeight = TILE_HEIGHT / 2;
/** Height of the upper triangle of a triangular tile. */
constexpr std::int_fast16_t TriangleUpperHeight = TILE_HEIGHT / 2 - 1;
/** Height of the upper rectangle of a trapezoid tile. */
constexpr std::int_fast16_t TrapezoidUpperHeight = TILE_HEIGHT / 2;
constexpr std::int_fast16_t TriangleHeight = LowerHeight + TriangleUpperHeight;
/** For triangles, for each pixel drawn vertically, this many pixels are drawn horizontally. */
constexpr std::int_fast16_t XStep = 2;
std::int_fast16_t GetTileHeight(TileType tile)
{
if (tile == TileType::LeftTriangle || tile == TileType::RightTriangle)
return TriangleHeight;
return Height;
}
// Debugging variables
// #define DEBUG_RENDER_COLOR
// #define DEBUG_RENDER_OFFSET_X 5
// #define DEBUG_RENDER_OFFSET_Y 5
#ifdef DEBUG_RENDER_COLOR
int DBGCOLOR = 0;
int GetTileDebugColor(TileType tile)
{
// clang-format off
switch (tile) {
case TileType::Square: return PAL16_YELLOW + 5;
case TileType::TransparentSquare: return PAL16_ORANGE + 5;
case TileType::LeftTriangle: return PAL16_GRAY + 5;
case TileType::RightTriangle: return PAL16_BEIGE;
case TileType::LeftTrapezoid: return PAL16_RED + 5;
case TileType::RightTrapezoid: return PAL16_BLUE + 5;
default: return 0;
}
// clang-format on
}
#endif // DEBUG_RENDER_COLOR
/** Fully transparent variant of WallMask. */
const std::uint32_t WallMask_FullyTrasparent[TILE_HEIGHT] = {
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000
};
/** Transparent variant of RightMask. */
const std::uint32_t RightMask_Transparent[TILE_HEIGHT] = {
0xC0000000,
0xF0000000,
0xFC000000,
0xFF000000,
0xFFC00000,
0xFFF00000,
0xFFFC0000,
0xFFFF0000,
0xFFFFC000,
0xFFFFF000,
0xFFFFFC00,
0xFFFFFF00,
0xFFFFFFC0,
0xFFFFFFF0,
0xFFFFFFFC,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF
};
/** Transparent variant of LeftMask. */
const std::uint32_t LeftMask_Transparent[TILE_HEIGHT] = {
0x00000003,
0x0000000F,
0x0000003F,
0x000000FF,
0x000003FF,
0x00000FFF,
0x00003FFF,
0x0000FFFF,
0x0003FFFF,
0x000FFFFF,
0x003FFFFF,
0x00FFFFFF,
0x03FFFFFF,
0x0FFFFFFF,
0x3FFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF
};
/** Specifies the draw masks used to render transparency of the right side of tiles. */
const std::uint32_t RightMask[TILE_HEIGHT] = {
0xEAAAAAAA,
0xF5555555,
0xFEAAAAAA,
0xFF555555,
0xFFEAAAAA,
0xFFF55555,
0xFFFEAAAA,
0xFFFF5555,
0xFFFFEAAA,
0xFFFFF555,
0xFFFFFEAA,
0xFFFFFF55,
0xFFFFFFEA,
0xFFFFFFF5,
0xFFFFFFFE,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF
};
/** Specifies the draw masks used to render transparency of the left side of tiles. */
const std::uint32_t LeftMask[TILE_HEIGHT] = {
0xAAAAAAAB,
0x5555555F,
0xAAAAAABF,
0x555555FF,
0xAAAAABFF,
0x55555FFF,
0xAAAABFFF,
0x5555FFFF,
0xAAABFFFF,
0x555FFFFF,
0xAABFFFFF,
0x55FFFFFF,
0xABFFFFFF,
0x5FFFFFFF,
0xBFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF
};
/** Specifies the draw masks used to render transparency of wall tiles. */
const std::uint32_t WallMask[TILE_HEIGHT] = {
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555,
0xAAAAAAAA,
0x55555555
};
/** Fully opaque mask */
const std::uint32_t SolidMask[TILE_HEIGHT] = {
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF,
0xFFFFFFFF
};
/** Used to mask out the left half of the tile diamond and only render additional content */
const std::uint32_t RightFoliageMask[TILE_HEIGHT] = {
0xFFFFFFFF,
0x3FFFFFFF,
0x0FFFFFFF,
0x03FFFFFF,
0x00FFFFFF,
0x003FFFFF,
0x000FFFFF,
0x0003FFFF,
0x0000FFFF,
0x00003FFF,
0x00000FFF,
0x000003FF,
0x000000FF,
0x0000003F,
0x0000000F,
0x00000003,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
};
/** Used to mask out the left half of the tile diamond and only render additional content */
const std::uint32_t LeftFoliageMask[TILE_HEIGHT] = {
0xFFFFFFFF,
0xFFFFFFFC,
0xFFFFFFF0,
0xFFFFFFC0,
0xFFFFFF00,
0xFFFFFC00,
0xFFFFF000,
0xFFFFC000,
0xFFFF0000,
0xFFFC0000,
0xFFF00000,
0xFFC00000,
0xFF000000,
0xFC000000,
0xF0000000,
0xC0000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
};
inline int CountLeadingZeros(std::uint32_t mask)
{
// Note: This function assumes that the argument is not zero,
// which means there is at least one bit set.
static_assert(
sizeof(std::uint32_t) == sizeof(uint32_t),
"CountLeadingZeros: std::uint32_t must be 32bits");
#if defined(__GNUC__) || defined(__clang__)
return __builtin_clz(mask);
#else
// Count the number of leading zeros using binary search.
int n = 0;
if ((mask & 0xFFFF0000) == 0)
n += 16, mask <<= 16;
if ((mask & 0xFF000000) == 0)
n += 8, mask <<= 8;
if ((mask & 0xF0000000) == 0)
n += 4, mask <<= 4;
if ((mask & 0xC0000000) == 0)
n += 2, mask <<= 2;
if ((mask & 0x80000000) == 0)
n += 1;
return n;
#endif
}
template <typename F>
void ForEachSetBit(std::uint32_t mask, const F &f)
{
int i = 0;
while (mask != 0) {
int z = CountLeadingZeros(mask);
i += z, mask <<= z;
for (; mask & 0x80000000; i++, mask <<= 1)
f(i);
}
}
inline void RenderLine(std::uint8_t *dst, const std::uint8_t *src, size_t n, const std::uint8_t *tbl, std::uint32_t mask)
{
// The number of iterations is limited by the size of the mask.
// So we can limit it by ANDing the mask with another mask that only keeps
// iterations that are lower than n. We can now avoid testing if i < n
// at every loop iteration.
assert(n != 0 && n <= sizeof(std::uint32_t) * CHAR_BIT);
const std::uint32_t firstNOnes = std::uint32_t(-1) << ((sizeof(std::uint32_t) * CHAR_BIT) - n);
mask &= firstNOnes;
if (mask == firstNOnes) { // Opaque line
if (light_table_index == lightmax) { // Complete darkness
memset(dst, 0, n);
} else if (light_table_index == 0) { // Fully lit
#ifndef DEBUG_RENDER_COLOR
memcpy(dst, src, n);
#else
memset(dst, DBGCOLOR, n);
#endif
} else { // Partially lit
#ifndef DEBUG_RENDER_COLOR
for (size_t i = 0; i < n; i++) {
dst[i] = tbl[src[i]];
}
#else
memset(dst, tbl[DBGCOLOR], n);
#endif
}
} else {
if (sgOptions.Graphics.bBlendedTransparancy) { // Blended transparancy
#ifndef DEBUG_RENDER_COLOR
if (light_table_index == lightmax) { // Complete darkness
for (size_t i = 0; i < n; i++, mask <<= 1) {
if ((mask & 0x80000000) != 0)
dst[i] = 0;
else
dst[i] = paletteTransparencyLookup[0][dst[i]];
}
} else if (light_table_index == 0) { // Fully lit
for (size_t i = 0; i < n; i++, mask <<= 1) {
if ((mask & 0x80000000) != 0)
dst[i] = src[i];
else
dst[i] = paletteTransparencyLookup[dst[i]][src[i]];
}
} else { // Partially lit
for (size_t i = 0; i < n; i++, mask <<= 1) {
if ((mask & 0x80000000) != 0)
dst[i] = tbl[src[i]];
else
dst[i] = paletteTransparencyLookup[dst[i]][tbl[src[i]]];
}
}
#else
for (size_t i = 0; i < n; i++, mask <<= 1) {
if ((mask & 0x80000000) != 0)
dst[i] = tbl[DBGCOLOR];
else
dst[i] = paletteTransparencyLookup[dst[i]][tbl[DBGCOLOR]];
}
#endif
} else { // Stippled transparancy
if (light_table_index == lightmax) { // Complete darkness
ForEachSetBit(mask, [=](int i) { dst[i] = 0; });
} else if (light_table_index == 0) { // Fully lit
#ifndef DEBUG_RENDER_COLOR
ForEachSetBit(mask, [=](int i) { dst[i] = src[i]; });
#else
ForEachSetBit(mask, [=](int i) { dst[i] = DBGCOLOR; });
#endif
} else { // Partially lit
ForEachSetBit(mask, [=](int i) { dst[i] = tbl[src[i]]; });
}
}
}
}
struct Clip {
std::int_fast16_t top;
std::int_fast16_t bottom;
std::int_fast16_t left;
std::int_fast16_t right;
std::int_fast16_t width;
std::int_fast16_t height;
};
Clip CalculateClip(std::int_fast16_t x, std::int_fast16_t y, std::int_fast16_t w, std::int_fast16_t h, const CelOutputBuffer &out)
{
Clip clip;
clip.top = y + 1 < h ? h - (y + 1) : 0;
clip.bottom = y + 1 > out.h() ? (y + 1) - out.h() : 0;
clip.left = x < 0 ? -x : 0;
clip.right = x + w > out.w() ? x + w - out.w() : 0;
clip.width = w - clip.left - clip.right;
clip.height = h - clip.top - clip.bottom;
return clip;
}
void RenderSquareFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
for (auto i = 0; i < Height; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, Width, tbl, *mask);
src += Width;
}
}
void RenderSquareClipped(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
src += clip.bottom * Height + clip.left;
for (auto i = 0; i < clip.height; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, clip.width, tbl, (*mask) << clip.left);
src += Width;
}
}
void RenderSquare(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width && clip.height == Height) {
RenderSquareFull(dst, dstPitch, src, mask, tbl);
} else {
RenderSquareClipped(dst, dstPitch, src, mask, tbl, clip);
}
}
void RenderTransparentSquareFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
for (auto i = 0; i < Height; ++i, dst -= dstPitch + Width, --mask) {
constexpr unsigned MaxMaskShift = 32;
std::uint_fast8_t drawWidth = Width;
std::uint32_t m = *mask;
while (drawWidth > 0) {
auto v = static_cast<std::int8_t>(*src++);
if (v > 0) {
RenderLine(dst, src, v, tbl, m);
src += v;
} else {
v = -v;
}
dst += v;
drawWidth -= v;
m = (v == MaxMaskShift) ? 0 : (m << v);
}
}
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity): Actually complex and has to be fast.
void RenderTransparentSquareClipped(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto skipRestOfTheLine = [&src](std::int_fast16_t remainingWidth) {
while (remainingWidth > 0) {
const auto v = static_cast<std::int8_t>(*src++);
if (v > 0) {
src += v;
remainingWidth -= v;
} else {
remainingWidth -= -v;
}
}
assert(remainingWidth == 0);
};
// Skip the bottom clipped lines.
for (auto i = 0; i < clip.bottom; ++i) {
skipRestOfTheLine(Width);
}
for (auto i = 0; i < clip.height; ++i, dst -= dstPitch + clip.width, --mask) {
constexpr unsigned MaxMaskShift = 32;
auto drawWidth = clip.width;
std::uint32_t m = *mask;
// Skip initial src if clipping on the left.
// Handles overshoot, i.e. when the RLE segment goes into the unclipped area.
auto remainingLeftClip = clip.left;
while (remainingLeftClip > 0) {
auto v = static_cast<std::int8_t>(*src++);
if (v > 0) {
if (v > remainingLeftClip) {
const auto overshoot = v - remainingLeftClip;
RenderLine(dst, src + remainingLeftClip, overshoot, tbl, m);
dst += overshoot;
drawWidth -= overshoot;
}
src += v;
} else {
v = -v;
if (v > remainingLeftClip) {
const auto overshoot = v - remainingLeftClip;
dst += overshoot;
drawWidth -= overshoot;
}
}
remainingLeftClip -= v;
m = (v == MaxMaskShift) ? 0 : (m << v);
}
// Draw the non-clipped segment
while (drawWidth > 0) {
auto v = static_cast<std::int8_t>(*src++);
if (v > 0) {
if (v > drawWidth) {
RenderLine(dst, src, drawWidth, tbl, m);
src += v;
dst += drawWidth;
drawWidth -= v;
break;
}
RenderLine(dst, src, v, tbl, m);
src += v;
} else {
v = -v;
if (v > drawWidth) {
dst += drawWidth;
drawWidth -= v;
break;
}
}
dst += v;
drawWidth -= v;
m = (v == MaxMaskShift) ? 0 : (m << v);
}
// Skip the rest of src line if clipping on the right
assert(drawWidth <= 0);
skipRestOfTheLine(clip.right + drawWidth);
}
}
void RenderTransparentSquare(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width && clip.height == Height) {
RenderTransparentSquareFull(dst, dstPitch, src, mask, tbl);
} else {
RenderTransparentSquareClipped(dst, dstPitch, src, mask, tbl, clip);
}
}
/** Vertical clip for the lower and upper triangles of a diamond tile (L/RTRIANGLE).*/
struct DiamondClipY {
std::int_fast16_t lowerBottom;
std::int_fast16_t lowerTop;
std::int_fast16_t upperBottom;
std::int_fast16_t upperTop;
};
template <std::int_fast16_t UpperHeight = TriangleUpperHeight>
DiamondClipY CalculateDiamondClipY(const Clip &clip)
{
DiamondClipY result;
if (clip.bottom > LowerHeight) {
result.lowerBottom = LowerHeight;
result.upperBottom = clip.bottom - LowerHeight;
result.lowerTop = result.upperTop = 0;
} else if (clip.top > UpperHeight) {
result.upperTop = UpperHeight;
result.lowerTop = clip.top - UpperHeight;
result.upperBottom = result.lowerBottom = 0;
} else {
result.upperTop = clip.top;
result.lowerBottom = clip.bottom;
result.lowerTop = result.upperBottom = 0;
}
return result;
}
std::size_t CalculateTriangleSourceSkipLowerBottom(std::int_fast16_t numLines)
{
return XStep * numLines * (numLines + 1) / 2 + 2 * ((numLines + 1) / 2);
}
std::size_t CalculateTriangleSourceSkipUpperBottom(std::int_fast16_t numLines)
{
return 2 * TriangleUpperHeight * numLines - numLines * (numLines - 1) + 2 * ((numLines + 1) / 2);
}
void RenderLeftTriangleFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
dst += XStep * (LowerHeight - 1);
for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
dst += 2 * XStep;
for (auto i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch - XStep, --mask) {
src += 2 * (i % 2);
const auto width = Width - XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
}
void RenderLeftTriangleClipVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
dst += 2 * XStep + XStep * clipY.upperBottom;
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep, --mask) {
src += 2 * (i % 2);
const auto width = Width - XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
}
void RenderLeftTriangleClipLeftAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
const auto clipLeft = clip.left;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1) - clipLeft;
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
const auto startX = Width - XStep * i;
const auto skip = startX < clipLeft ? clipLeft - startX : 0;
if (width > skip)
RenderLine(dst + skip, src + skip, width - skip, tbl, (*mask) << skip);
src += width;
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
dst += 2 * XStep + XStep * clipY.upperBottom;
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep, --mask) {
src += 2 * (i % 2);
const auto width = Width - XStep * i;
const auto startX = XStep * i;
const auto skip = startX < clipLeft ? clipLeft - startX : 0;
if (width > skip)
RenderLine(dst + skip, src + skip, width - skip, tbl, (*mask) << skip);
src += width;
}
}
void RenderLeftTriangleClipRightAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
const auto clipRight = clip.right;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
if (width > clipRight)
RenderLine(dst, src, width - clipRight, tbl, *mask);
src += width;
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
dst += 2 * XStep + XStep * clipY.upperBottom;
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep, --mask) {
src += 2 * (i % 2);
const auto width = Width - XStep * i;
if (width <= clipRight)
break;
RenderLine(dst, src, width - clipRight, tbl, *mask);
src += width;
}
}
void RenderLeftTriangle(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width) {
if (clip.height == TriangleHeight) {
RenderLeftTriangleFull(dst, dstPitch, src, mask, tbl);
} else {
RenderLeftTriangleClipVertical(dst, dstPitch, src, mask, tbl, clip);
}
} else if (clip.right == 0) {
RenderLeftTriangleClipLeftAndVertical(dst, dstPitch, src, mask, tbl, clip);
} else {
RenderLeftTriangleClipRightAndVertical(dst, dstPitch, src, mask, tbl, clip);
}
}
void RenderRightTriangleFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
for (auto i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch, --mask) {
const auto width = Width - XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
}
void RenderRightTriangleClipVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
const auto width = Width - XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
}
void RenderRightTriangleClipLeftAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
const auto clipLeft = clip.left;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
if (width > clipLeft)
RenderLine(dst, src + clipLeft, width - clipLeft, tbl, (*mask) << clipLeft);
src += width + 2 * (i % 2);
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
const auto width = Width - XStep * i;
if (width <= clipLeft)
break;
RenderLine(dst, src + clipLeft, width - clipLeft, tbl, (*mask) << clipLeft);
src += width + 2 * (i % 2);
}
}
void RenderRightTriangleClipRightAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY(clip);
const auto clipRight = clip.right;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0;
if (width > skip)
RenderLine(dst, src, width - skip, tbl, *mask);
src += width + 2 * (i % 2);
}
src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom);
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
const auto width = Width - XStep * i;
const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0;
if (width > skip)
RenderLine(dst, src, width - skip, tbl, *mask);
src += width + 2 * (i % 2);
}
}
void RenderRightTriangle(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width) {
if (clip.height == TriangleHeight) {
RenderRightTriangleFull(dst, dstPitch, src, mask, tbl);
} else {
RenderRightTriangleClipVertical(dst, dstPitch, src, mask, tbl, clip);
}
} else if (clip.right == 0) {
RenderRightTriangleClipLeftAndVertical(dst, dstPitch, src, mask, tbl, clip);
} else {
RenderRightTriangleClipRightAndVertical(dst, dstPitch, src, mask, tbl, clip);
}
}
void RenderLeftTrapezoidFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
dst += XStep * (LowerHeight - 1);
for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
dst += XStep;
for (auto i = 1; i <= TrapezoidUpperHeight; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, Width, tbl, *mask);
src += Width;
}
}
void RenderLeftTrapezoidClipVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width;
}
src += clipY.upperBottom * Width;
dst += XStep;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, Width, tbl, *mask);
src += Width;
}
}
void RenderLeftTrapezoidClipLeftAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
const auto clipLeft = clip.left;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1) - clipLeft;
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
const auto startX = Width - XStep * i;
const auto skip = startX < clipLeft ? clipLeft - startX : 0;
if (width > skip)
RenderLine(dst + skip, src + skip, width - skip, tbl, (*mask) << skip);
src += width;
}
src += clipY.upperBottom * Width + clipLeft;
dst += XStep + clipLeft;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, clip.width, tbl, (*mask) << clipLeft);
src += Width;
}
}
void RenderLeftTrapezoidClipRightAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
const auto clipRight = clip.right;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep, --mask) {
src += 2 * (i % 2);
const auto width = XStep * i;
if (width > clipRight)
RenderLine(dst, src, width - clipRight, tbl, *mask);
src += width;
}
src += clipY.upperBottom * Width;
dst += XStep;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, clip.width, tbl, *mask);
src += Width;
}
}
void RenderLeftTrapezoid(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width) {
if (clip.height == Height) {
RenderLeftTrapezoidFull(dst, dstPitch, src, mask, tbl);
} else {
RenderLeftTrapezoidClipVertical(dst, dstPitch, src, mask, tbl, clip);
}
} else if (clip.right == 0) {
RenderLeftTrapezoidClipLeftAndVertical(dst, dstPitch, src, mask, tbl, clip);
} else {
RenderLeftTrapezoidClipRightAndVertical(dst, dstPitch, src, mask, tbl, clip);
}
}
void RenderRightTrapezoidFull(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl)
{
for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
for (auto i = 1; i <= TrapezoidUpperHeight; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, Width, tbl, *mask);
src += Width;
}
}
void RenderRightTrapezoidClipVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
const auto lowerMax = LowerHeight - clipY.lowerTop;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
RenderLine(dst, src, width, tbl, *mask);
src += width + 2 * (i % 2);
}
src += clipY.upperBottom * Width;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, Width, tbl, *mask);
src += Width;
}
}
void RenderRightTrapezoidClipLeftAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
const auto clipLeft = clip.left;
const auto lowerMax = LowerHeight - clipY.lowerTop;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
if (width > clipLeft)
RenderLine(dst, src + clipLeft, width - clipLeft, tbl, (*mask) << clipLeft);
src += width + 2 * (i % 2);
}
src += clipY.upperBottom * Width + clipLeft;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, clip.width, tbl, (*mask) << clipLeft);
src += Width;
}
}
void RenderRightTrapezoidClipRightAndVertical(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
const auto clipY = CalculateDiamondClipY<TrapezoidUpperHeight>(clip);
const auto clipRight = clip.right;
const auto lowerMax = LowerHeight - clipY.lowerTop;
src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom);
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch, --mask) {
const auto width = XStep * i;
const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0;
if (width > skip)
RenderLine(dst, src, width - skip, tbl, *mask);
src += width + 2 * (i % 2);
}
src += clipY.upperBottom * Width;
const auto upperMax = TrapezoidUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch, --mask) {
RenderLine(dst, src, clip.width, tbl, *mask);
src += Width;
}
}
void RenderRightTrapezoid(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, const std::uint32_t *mask, const std::uint8_t *tbl, Clip clip)
{
if (clip.width == Width) {
if (clip.height == Height) {
RenderRightTrapezoidFull(dst, dstPitch, src, mask, tbl);
} else {
RenderRightTrapezoidClipVertical(dst, dstPitch, src, mask, tbl, clip);
}
} else if (clip.right == 0) {
RenderRightTrapezoidClipLeftAndVertical(dst, dstPitch, src, mask, tbl, clip);
} else {
RenderRightTrapezoidClipRightAndVertical(dst, dstPitch, src, mask, tbl, clip);
}
}
/** Returns the mask that defines what parts of the tile are opaque. */
const std::uint32_t *GetMask(TileType tile)
{
#ifdef _DEBUG
if ((GetAsyncKeyState(DVL_VK_MENU) & 0x8000) != 0) {
return &SolidMask[TILE_HEIGHT - 1];
}
#endif
if (cel_transparency_active) {
if (arch_draw_type == 0) {
if (sgOptions.Graphics.bBlendedTransparancy) // Use a fully transparent mask
return &WallMask_FullyTrasparent[TILE_HEIGHT - 1];
return &WallMask[TILE_HEIGHT - 1];
}
if (arch_draw_type == 1 && tile != TileType::LeftTriangle) {
const auto c = block_lvid[level_piece_id];
if (c == 1 || c == 3) {
if (sgOptions.Graphics.bBlendedTransparancy) // Use a fully transparent mask
return &LeftMask_Transparent[TILE_HEIGHT - 1];
return &LeftMask[TILE_HEIGHT - 1];
}
}
if (arch_draw_type == 2 && tile != TileType::RightTriangle) {
const auto c = block_lvid[level_piece_id];
if (c == 2 || c == 3) {
if (sgOptions.Graphics.bBlendedTransparancy) // Use a fully transparent mask
return &RightMask_Transparent[TILE_HEIGHT - 1];
return &RightMask[TILE_HEIGHT - 1];
}
}
} else if (arch_draw_type != 0 && cel_foliage_active) {
if (tile != TileType::TransparentSquare)
return nullptr;
if (arch_draw_type == 1)
return &LeftFoliageMask[TILE_HEIGHT - 1];
if (arch_draw_type == 2)
return &RightFoliageMask[TILE_HEIGHT - 1];
}
return &SolidMask[TILE_HEIGHT - 1];
}
// Blit with left and vertical clipping.
void RenderBlackTileClipLeftAndVertical(std::uint8_t *dst, int dstPitch, int sx, DiamondClipY clipY)
{
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
// Lower triangle (drawn bottom to top):
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = clipY.lowerBottom + 1; i <= lowerMax; ++i, dst -= dstPitch + XStep) {
const auto w = 2 * XStep * i;
const auto curX = sx + TILE_WIDTH / 2 - XStep * i;
if (curX >= 0) {
memset(dst, 0, w);
} else if (-curX <= w) {
memset(dst - curX, 0, w + curX);
}
}
dst += 2 * XStep + XStep * clipY.upperBottom;
// Upper triangle (drawn bottom to top):
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = clipY.upperBottom; i < upperMax; ++i, dst -= dstPitch - XStep) {
const auto w = 2 * XStep * (TriangleUpperHeight - i);
const auto curX = sx + TILE_WIDTH / 2 - XStep * (TriangleUpperHeight - i);
if (curX >= 0) {
memset(dst, 0, w);
} else if (-curX <= w) {
memset(dst - curX, 0, w + curX);
} else {
break;
}
}
}
// Blit with right and vertical clipping.
void RenderBlackTileClipRightAndVertical(std::uint8_t *dst, int dstPitch, std::int_fast16_t maxWidth, DiamondClipY clipY)
{
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
// Lower triangle (drawn bottom to top):
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = clipY.lowerBottom + 1; i <= lowerMax; ++i, dst -= dstPitch + XStep) {
const auto width = 2 * XStep * i;
const auto endX = TILE_WIDTH / 2 + XStep * i;
const auto skip = endX > maxWidth ? endX - maxWidth : 0;
if (width > skip)
memset(dst, 0, width - skip);
}
dst += 2 * XStep + XStep * clipY.upperBottom;
// Upper triangle (drawn bottom to top):
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) {
const auto width = TILE_WIDTH - 2 * XStep * i;
const auto endX = TILE_WIDTH / 2 + XStep * (TriangleUpperHeight - i + 1);
const auto skip = endX > maxWidth ? endX - maxWidth : 0;
if (width <= skip)
break;
memset(dst, 0, width - skip);
}
}
// Blit with vertical clipping only.
void RenderBlackTileClipY(std::uint8_t *dst, int dstPitch, DiamondClipY clipY)
{
dst += XStep * (LowerHeight - clipY.lowerBottom - 1);
// Lower triangle (drawn bottom to top):
const auto lowerMax = LowerHeight - clipY.lowerTop;
for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) {
memset(dst, 0, 2 * XStep * i);
}
dst += 2 * XStep + XStep * clipY.upperBottom;
// Upper triangle (drawn bottom to top):
const auto upperMax = TriangleUpperHeight - clipY.upperTop;
for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) {
memset(dst, 0, TILE_WIDTH - 2 * XStep * i);
}
}
// Blit a black tile without clipping (must be fully in bounds).
void RenderBlackTileFull(std::uint8_t *dst, int dstPitch)
{
dst += XStep * (LowerHeight - 1);
// Tile is fully in bounds, can use constant loop boundaries.
// Lower triangle (drawn bottom to top):
for (unsigned i = 1; i <= LowerHeight; ++i, dst -= dstPitch + XStep) {
memset(dst, 0, 2 * XStep * i);
}
dst += 2 * XStep;
// Upper triangle (drawn bottom to to top):
for (unsigned i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch - XStep) {
memset(dst, 0, TILE_WIDTH - 2 * XStep * i);
}
}
} // namespace
void RenderTile(const CelOutputBuffer &out, int x, int y)
{
const auto tile = static_cast<TileType>((level_cel_block & 0x7000) >> 12);
const auto *mask = GetMask(tile);
if (mask == nullptr)
return;
#ifdef DEBUG_RENDER_OFFSET_X
x += DEBUG_RENDER_OFFSET_X;
#endif
#ifdef DEBUG_RENDER_OFFSET_Y
y += DEBUG_RENDER_OFFSET_Y;
#endif
#ifdef DEBUG_RENDER_COLOR
DBGCOLOR = GetTileDebugColor(tile);
#endif
Clip clip = CalculateClip(x, y, Width, GetTileHeight(tile), out);
if (clip.width <= 0 || clip.height <= 0)
return;
const std::uint8_t *tbl = &pLightTbl[256 * light_table_index];
const auto *pFrameTable = reinterpret_cast<const std::uint32_t *>(pDungeonCels.get());
const auto *src = reinterpret_cast<const std::uint8_t *>(&pDungeonCels[SDL_SwapLE32(pFrameTable[level_cel_block & 0xFFF])]);
std::uint8_t *dst = out.at(static_cast<int>(x + clip.left), static_cast<int>(y - clip.bottom));
const auto dstPitch = out.pitch();
mask -= clip.bottom;
switch (tile) {
case TileType::Square:
RenderSquare(dst, dstPitch, src, mask, tbl, clip);
break;
case TileType::TransparentSquare:
RenderTransparentSquare(dst, dstPitch, src, mask, tbl, clip);
break;
case TileType::LeftTriangle:
RenderLeftTriangle(dst, dstPitch, src, mask, tbl, clip);
break;
case TileType::RightTriangle:
RenderRightTriangle(dst, dstPitch, src, mask, tbl, clip);
break;
case TileType::LeftTrapezoid:
RenderLeftTrapezoid(dst, dstPitch, src, mask, tbl, clip);
break;
case TileType::RightTrapezoid:
RenderRightTrapezoid(dst, dstPitch, src, mask, tbl, clip);
break;
}
}
void world_draw_black_tile(const CelOutputBuffer &out, int sx, int sy)
{
#ifdef DEBUG_RENDER_OFFSET_X
sx += DEBUG_RENDER_OFFSET_X;
#endif
#ifdef DEBUG_RENDER_OFFSET_Y
sy += DEBUG_RENDER_OFFSET_Y;
#endif
auto clip = CalculateClip(sx, sy, TILE_WIDTH, TriangleHeight, out);
if (clip.width <= 0 || clip.height <= 0)
return;
auto clipY = CalculateDiamondClipY(clip);
std::uint8_t *dst = out.at(sx, static_cast<int>(sy - clip.bottom));
if (clip.width == TILE_WIDTH) {
if (clip.height == TriangleHeight) {
RenderBlackTileFull(dst, out.pitch());
} else {
RenderBlackTileClipY(dst, out.pitch(), clipY);
}
} else {
if (clip.right == 0) {
RenderBlackTileClipLeftAndVertical(dst, out.pitch(), sx, clipY);
} else {
RenderBlackTileClipRightAndVertical(dst, out.pitch(), clip.width, clipY);
}
}
}
} // namespace devilution