Also fix bottom platforms disappearing and wrong coordinates being sent to the function that draws debug lines. Obviously debug lines drawing is still messed up... but at least you see the two lines!
404 lines
13 KiB
C++
404 lines
13 KiB
C++
/*
|
|
Copyright 2014 Michele "King_DuckZ" Santullo
|
|
|
|
This file is part of CloonelJump.
|
|
|
|
CloonelJump is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
CloonelJump is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with CloonelJump. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "texture.hpp"
|
|
#include "sdlerror.hpp"
|
|
#include "sdlmain.hpp"
|
|
#include "physicsfswrapper.hpp"
|
|
#include "compatibility.h"
|
|
#include "casts.hpp"
|
|
#include <SDL2/SDL.h>
|
|
#include <stdexcept>
|
|
#include <cassert>
|
|
#include <ciso646>
|
|
#include <png.h>
|
|
#include <endian.h>
|
|
#include <vector>
|
|
#if !defined(NDEBUG)
|
|
# include <cmath>
|
|
#endif
|
|
|
|
#define lengthof(a) (static_cast<int32_t>(sizeof(a) / sizeof(a[0])))
|
|
|
|
namespace cloonel {
|
|
namespace {
|
|
enum GraphicFormat {
|
|
GraphicFormat_Unknown,
|
|
GraphicFormat_Png
|
|
};
|
|
|
|
enum PixelFormat {
|
|
PixelFormat_RGB_24,
|
|
PixelFormat_RGBA_32
|
|
};
|
|
|
|
enum ColorChannelMask {
|
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
|
ColorChannelMask_Red = 0xff000000,
|
|
ColorChannelMask_Green = 0xff0000,
|
|
ColorChannelMask_Blue = 0xff00,
|
|
ColorChannelMask_Alpha = 0xff
|
|
#elif __BYTE_ORDER == __LITTLE_ENDIAN
|
|
ColorChannelMask_Red = 0xff,
|
|
ColorChannelMask_Green = 0xff00,
|
|
ColorChannelMask_Blue = 0xff0000,
|
|
ColorChannelMask_Alpha = 0xff000000
|
|
#else
|
|
# error "Unknonwn endianness"
|
|
#endif
|
|
};
|
|
|
|
struct GraphicFormatItem {
|
|
const char* extension;
|
|
size_t length;
|
|
GraphicFormat enumvalue;
|
|
};
|
|
|
|
struct RectFloat {
|
|
float2 from;
|
|
float2 to;
|
|
|
|
RectFloat ( void ) = default;
|
|
RectFloat ( const RectFloat& parOther ) :
|
|
from(parOther.from),
|
|
to(parOther.to)
|
|
{
|
|
assert(IsValid());
|
|
}
|
|
|
|
RectFloat ( const SDL_Rect& parRect ) :
|
|
from(static_cast<float>(parRect.x), static_cast<float>(parRect.y)),
|
|
to(static_cast<float>(parRect.x + parRect.w), static_cast<float>(parRect.y + parRect.h))
|
|
{
|
|
assert(IsValid());
|
|
}
|
|
|
|
RectFloat ( const float2& parFrom, const float2& parTo ) :
|
|
from(parFrom),
|
|
to(parTo)
|
|
{
|
|
assert(IsValid());
|
|
}
|
|
|
|
~RectFloat ( void ) noexcept = default;
|
|
|
|
operator SDL_Rect ( void ) const noexcept { return { static_cast<int>(from.x()), static_cast<int>(from.y()), static_cast<int>(Width()), static_cast<int>(Height()) }; }
|
|
|
|
float Width ( void ) const noexcept { return to.x() - from.x(); }
|
|
float Height ( void ) const noexcept { return to.y() - from.y(); }
|
|
float2 WidthHeight ( void ) const noexcept { return float2(Width(), Height()); }
|
|
bool IsValid ( void ) const noexcept { return from <= to; }
|
|
};
|
|
|
|
const GraphicFormatItem g_graphicFormatItems[] = {
|
|
{".png", 4, GraphicFormat_Png}
|
|
};
|
|
|
|
GraphicFormat GuessGraphicFormatFromName (const std::string& parPath) a_pure;
|
|
bool ClipRect ( RectFloat& parSrc, RectFloat& parDest, const RectFloat& parClip );
|
|
#if !defined(NDEBUG)
|
|
bool IsRectCompletelyInsideRect ( const RectFloat& parInner, const RectFloat& parOuter ) a_pure;
|
|
#endif
|
|
|
|
///----------------------------------------------------------------------
|
|
///----------------------------------------------------------------------
|
|
SDL_Surface* SurfaceFromPng (PixelFormat parPixelFormat, ushort2 parSize, const png_structp& parPngPtr, const png_infop& parInfoPtr, std::vector<uint8_t>& parBuff) {
|
|
const png_size_t stride = png_get_rowbytes(parPngPtr, parInfoPtr);
|
|
assert(stride > 0);
|
|
|
|
parBuff.resize(stride * parSize.y());
|
|
uint8_t* const imagePtr = parBuff.data();
|
|
|
|
for (uint16_t y = 0; y < parSize.y(); ++y) {
|
|
png_read_row(parPngPtr, imagePtr + stride * y, nullptr);
|
|
}
|
|
|
|
int bpp;
|
|
uint32_t redMask, greenMask, blueMask, alphaMask;
|
|
switch (parPixelFormat) {
|
|
case PixelFormat_RGB_24:
|
|
bpp = 24;
|
|
redMask = ColorChannelMask_Red;
|
|
greenMask = ColorChannelMask_Green;
|
|
blueMask = ColorChannelMask_Blue;
|
|
alphaMask = 0;
|
|
break;
|
|
case PixelFormat_RGBA_32:
|
|
bpp = 32;
|
|
redMask = ColorChannelMask_Red;
|
|
greenMask = ColorChannelMask_Green;
|
|
blueMask = ColorChannelMask_Blue;
|
|
alphaMask = ColorChannelMask_Alpha;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
SDL_Surface* const retSurf = SDL_CreateRGBSurfaceFrom(
|
|
imagePtr,
|
|
parSize.x(),
|
|
parSize.y(),
|
|
bpp,
|
|
checked_numcast<int>(stride),
|
|
redMask,
|
|
greenMask,
|
|
blueMask,
|
|
alphaMask
|
|
);
|
|
return retSurf;
|
|
}
|
|
|
|
///----------------------------------------------------------------------
|
|
///----------------------------------------------------------------------
|
|
GraphicFormat GuessGraphicFormatFromName (const std::string& parPath) {
|
|
const size_t dotPos = parPath.find_last_of('.');
|
|
if (parPath.npos == dotPos) {
|
|
return GraphicFormat_Unknown;
|
|
}
|
|
|
|
const size_t extLen = parPath.size() - dotPos;
|
|
for (size_t z = 0; z < sizeof(g_graphicFormatItems) / sizeof(g_graphicFormatItems[0]); ++z) {
|
|
const GraphicFormatItem& currItem = g_graphicFormatItems[z];
|
|
if (currItem.length == extLen and parPath.compare(dotPos, currItem.length, currItem.extension) == 0) {
|
|
return currItem.enumvalue;
|
|
}
|
|
}
|
|
return GraphicFormat_Unknown;
|
|
}
|
|
|
|
///----------------------------------------------------------------------
|
|
///----------------------------------------------------------------------
|
|
void ReadDataFromInputStream (png_structp parPngPtr, png_bytep parOutBytes, png_size_t parByteCountToRead) {
|
|
if (not png_get_io_ptr(parPngPtr))
|
|
return;
|
|
PhysicsFSFile& rawfile = *static_cast<PhysicsFSFile*>(png_get_io_ptr(parPngPtr));
|
|
|
|
const int64_t read = rawfile.Read(static_cast<void*>(parOutBytes), checked_numcast<uint32_t>(parByteCountToRead), 1);
|
|
if (read != static_cast<int64_t>(parByteCountToRead))
|
|
return;
|
|
|
|
return;
|
|
}
|
|
|
|
///----------------------------------------------------------------------
|
|
///http://blog.hammerian.net/2009/reading-png-images-from-memory/
|
|
///----------------------------------------------------------------------
|
|
SDL_Surface* LoadNewPngSurface (const std::string& parPath, std::vector<uint8_t>& parBuff) {
|
|
PhysicsFSFile rawfile(parPath.c_str(), PhysicsFSFile::OpenMode_Read, "graphics");
|
|
unsigned char header[8];
|
|
assert(rawfile.IsOpen());
|
|
|
|
//Read the signature from the input stream
|
|
rawfile.Read(header, lengthof(header), 1);
|
|
if (png_sig_cmp(header, 0, lengthof(header)))
|
|
return nullptr;
|
|
|
|
//Get the file info struct
|
|
png_structp pngptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
if (not pngptr)
|
|
return nullptr;
|
|
|
|
//Get the data info struct
|
|
png_infop infoptr = png_create_info_struct(pngptr);
|
|
if (not infoptr) {
|
|
png_destroy_read_struct(&pngptr, nullptr, nullptr);
|
|
return nullptr;
|
|
}
|
|
|
|
//Set the function that will be called to read data from the stream
|
|
png_set_read_fn(pngptr, &rawfile, ReadDataFromInputStream);
|
|
//Tell the lib we already verified the signature
|
|
png_set_sig_bytes(pngptr, lengthof(header));
|
|
|
|
//Read the header
|
|
png_uint_32 width, height;
|
|
int bitDepth, colorType;
|
|
png_read_info(pngptr, infoptr);
|
|
const png_uint_32 headerGetInfoRetVal = png_get_IHDR(pngptr, infoptr, &width, &height, &bitDepth, &colorType, nullptr, nullptr, nullptr);
|
|
if (static_cast<png_uint_32>(-1) == headerGetInfoRetVal) {
|
|
png_destroy_read_struct(&pngptr, &infoptr, nullptr);
|
|
return nullptr;
|
|
}
|
|
|
|
SDL_Surface* retSurf;
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_RGB:
|
|
assert(3 * bitDepth == 24);
|
|
retSurf = SurfaceFromPng(PixelFormat_RGB_24, ushort2(static_cast<uint8_t>(width), static_cast<uint8_t>(height)), pngptr, infoptr, parBuff);
|
|
break;
|
|
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
assert(bitDepth * 4 == 32);
|
|
retSurf = SurfaceFromPng(PixelFormat_RGBA_32, ushort2(static_cast<uint8_t>(width), static_cast<uint8_t>(height)), pngptr, infoptr, parBuff);
|
|
break;
|
|
|
|
default:
|
|
retSurf = nullptr;
|
|
}
|
|
|
|
png_destroy_read_struct(&pngptr, &infoptr, nullptr);
|
|
return retSurf;
|
|
}
|
|
|
|
///----------------------------------------------------------------------
|
|
///Adjustes parSrc and parDest so that parDest will fit into parClip and
|
|
///parSrc to be the relevant part of the source texture to be drawn.
|
|
///Returns true if such operation was possible and parSrc and parDest
|
|
///got set to a valid value, otherwise false.
|
|
///----------------------------------------------------------------------
|
|
bool ClipRect (RectFloat& parSrc, RectFloat& parDest, const RectFloat& parClip) {
|
|
assert(parSrc.IsValid());
|
|
assert(parDest.IsValid());
|
|
|
|
//If the dest rect is completely out of the clipping region, there
|
|
//is nothing to do at all.
|
|
if (parDest.to <= parClip.from or parDest.from >= parClip.to)
|
|
return false;
|
|
|
|
RectFloat dst;
|
|
dst.from.x() = std::max(parDest.from.x(), parClip.from.x());
|
|
dst.from.y() = std::max(parDest.from.y(), parClip.from.y());
|
|
dst.to.x() = std::min(parDest.to.x(), parClip.to.x());
|
|
dst.to.y() = std::min(parDest.to.y(), parClip.to.y());
|
|
|
|
if (not dst.IsValid())
|
|
return false;
|
|
|
|
assert(parDest.from <= dst.from);
|
|
assert(parDest.to >= dst.to);
|
|
|
|
RectFloat src;
|
|
{
|
|
const float2 srcWidthHeight(parSrc.WidthHeight());
|
|
const float2 scaledOffs((dst.from - parDest.from) / parDest.WidthHeight());
|
|
src.from = parSrc.from + scaledOffs * srcWidthHeight;
|
|
const float2 scaledCrop((parDest.to - dst.to) / parDest.WidthHeight());
|
|
src.to = parSrc.to - scaledCrop * srcWidthHeight;
|
|
assert(src.IsValid());
|
|
|
|
assert(dst.IsValid());
|
|
assert(dst.from >= parClip.from);
|
|
assert(dst.to <= parClip.to);
|
|
}
|
|
parDest = dst;
|
|
parSrc = src;
|
|
return true;
|
|
}
|
|
|
|
#if !defined(NDEBUG)
|
|
///----------------------------------------------------------------------
|
|
///----------------------------------------------------------------------
|
|
bool IsRectCompletelyInsideRect (const RectFloat& parInner, const RectFloat& parOuter) {
|
|
return parInner.from >= parOuter.from and parInner.to <= parOuter.to;
|
|
}
|
|
#endif
|
|
} //unnamed namespace
|
|
|
|
///--------------------------------------------------------------------------
|
|
///--------------------------------------------------------------------------
|
|
Texture::Texture (const std::string& parPath, SDLMain* parMain, bool parLoadNow) :
|
|
m_path(parPath),
|
|
m_texture(nullptr),
|
|
m_sdlmain(parMain)
|
|
{
|
|
if (parLoadNow)
|
|
Reload();
|
|
}
|
|
|
|
///--------------------------------------------------------------------------
|
|
///--------------------------------------------------------------------------
|
|
Texture::~Texture() noexcept {
|
|
Destroy();
|
|
}
|
|
|
|
///--------------------------------------------------------------------------
|
|
///--------------------------------------------------------------------------
|
|
void Texture::Destroy() noexcept {
|
|
if (m_texture) {
|
|
SDL_DestroyTexture(m_texture);
|
|
m_texture = nullptr;
|
|
}
|
|
}
|
|
|
|
///--------------------------------------------------------------------------
|
|
///--------------------------------------------------------------------------
|
|
void Texture::Reload() {
|
|
const GraphicFormat fmt = GuessGraphicFormatFromName(m_path);
|
|
Destroy();
|
|
|
|
SDL_Surface* surf = nullptr;
|
|
std::vector<uint8_t> memBuff;
|
|
switch (fmt) {
|
|
case GraphicFormat_Png:
|
|
surf = LoadNewPngSurface(m_path.c_str(), memBuff);
|
|
break;
|
|
|
|
default:
|
|
throw std::runtime_error(std::string("Unsupported file format for \"") + m_path + "\"");
|
|
}
|
|
|
|
if (nullptr == surf)
|
|
throw std::runtime_error(GetFullErrorMessage(__PRETTY_FUNCTION__, m_path));
|
|
|
|
m_texture = SDL_CreateTextureFromSurface(m_sdlmain->GetRenderer(), surf);
|
|
SDL_FreeSurface(surf);
|
|
if (m_texture) {
|
|
int2 wh;
|
|
SDL_QueryTexture(m_texture, nullptr, nullptr, &wh.x(), &wh.y());
|
|
m_size = vector_cast<float2>(wh);
|
|
}
|
|
}
|
|
|
|
///--------------------------------------------------------------------------
|
|
///--------------------------------------------------------------------------
|
|
void Texture::Render (const float2& parPos, const float2& parSize, const float2& parScaling, bool parClip) const {
|
|
assert(IsLoaded());
|
|
|
|
const float2 pos(parPos * parScaling);
|
|
const float2 siz(parSize * parScaling);
|
|
RectFloat dest(pos, pos + siz);
|
|
RectFloat src(float2(0.0f), m_size);
|
|
|
|
if (parClip) {
|
|
const RectFloat clip(float2(0.0f), vector_cast<float2>(m_sdlmain->WidthHeight()));
|
|
const bool visible = ClipRect(src, dest, clip);
|
|
if (not visible)
|
|
return;
|
|
}
|
|
#if !defined(NDEBUG)
|
|
else {
|
|
const RectFloat clip(float2(0.0f), vector_cast<float2>(m_sdlmain->WidthHeight()));
|
|
assert(IsRectCompletelyInsideRect(dest, clip));
|
|
}
|
|
#endif
|
|
|
|
SDL_Rect sdlsrc(src);
|
|
sdlsrc.y = static_cast<decltype(sdlsrc.y)>(m_size.y()) - sdlsrc.y - sdlsrc.h;
|
|
SDL_Rect sdldst(dest);
|
|
sdldst.y = m_sdlmain->WidthHeight().y() - sdldst.y - sdldst.h;
|
|
|
|
//By here everything is nice and clean
|
|
assert(sdlsrc.x >= 0 and sdlsrc.y >= 0);
|
|
assert(sdlsrc.w >= 0 and sdlsrc.w <= static_cast<int>(m_size.x()));
|
|
assert(sdlsrc.h >= 0 and sdlsrc.h <= static_cast<int>(m_size.y()));
|
|
assert(sdlsrc.x + sdlsrc.w <= m_sdlmain->WidthHeight().x());
|
|
assert(sdlsrc.y + sdlsrc.h <= m_sdlmain->WidthHeight().y());
|
|
SDL_RenderCopy(m_sdlmain->GetRenderer(), m_texture, &sdlsrc, &sdldst);
|
|
}
|
|
} //namespace cloonel
|