Implement a new tileset atlas layout to allow for bigger tilesets
The atlas packing algorithm has been reworked to pack autotiles and tileset very efficiently into a texture, splitting the tileset in multiple ways and eliminating the previous duplication of image data in the atlas across "frames". Animation, which these frames were designed for, is now done via duplicated buffer frames, ie. each animation frame has its own VBO and IBO data. This was not done to save on VRAM (hardly less memory is used), but to make place for the new atlas layout. Thanks to this new layout, even with a max texture size of 2048, one can use tilesets with up to 15000 height. Of course, such a tileset couldn't be stored in a regular Bitmap to begin with, which is why I also introduced a hack called "mega surfaces": software surfaces stored in RAM and wrapped inside a Bitmap, whose sole purpose is to be passed to a Tilemap as tilesets. Various other minor changes and fixes are included.
This commit is contained in:
parent
6b2c892280
commit
10ec55e39b
6
mkxp.pro
6
mkxp.pro
|
@ -59,7 +59,8 @@ HEADERS += \
|
||||||
src/binding.h \
|
src/binding.h \
|
||||||
src/gl-util.h \
|
src/gl-util.h \
|
||||||
src/util.h \
|
src/util.h \
|
||||||
src/config.h
|
src/config.h \
|
||||||
|
src/tileatlas.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/main.cpp \
|
src/main.cpp \
|
||||||
|
@ -85,7 +86,8 @@ SOURCES += \
|
||||||
src/graphics.cpp \
|
src/graphics.cpp \
|
||||||
src/debuglogger.cpp \
|
src/debuglogger.cpp \
|
||||||
src/etc.cpp \
|
src/etc.cpp \
|
||||||
src/config.cpp
|
src/config.cpp \
|
||||||
|
src/tileatlas.cpp
|
||||||
|
|
||||||
EMBED = shader/transSimple.frag \
|
EMBED = shader/transSimple.frag \
|
||||||
shader/trans.frag \
|
shader/trans.frag \
|
||||||
|
|
|
@ -40,6 +40,13 @@
|
||||||
|
|
||||||
#define DISP_CLASS_NAME "bitmap"
|
#define DISP_CLASS_NAME "bitmap"
|
||||||
|
|
||||||
|
#define GUARD_MEGA \
|
||||||
|
{ \
|
||||||
|
if (p->megaSurface) \
|
||||||
|
throw Exception(Exception::MKXPError, \
|
||||||
|
"Operation not supported for mega surfaces"); \
|
||||||
|
}
|
||||||
|
|
||||||
struct BitmapPrivate
|
struct BitmapPrivate
|
||||||
{
|
{
|
||||||
TEXFBO tex;
|
TEXFBO tex;
|
||||||
|
@ -50,7 +57,14 @@ struct BitmapPrivate
|
||||||
|
|
||||||
Font *font;
|
Font *font;
|
||||||
|
|
||||||
|
/* "Mega surfaces" are a hack to allow Tilesets to be used
|
||||||
|
* whose Bitmaps don't fit into a regular texture. They're
|
||||||
|
* kept in RAM and will throw an error if they're used in
|
||||||
|
* any context other than as Tilesets */
|
||||||
|
SDL_Surface *megaSurface;
|
||||||
|
|
||||||
BitmapPrivate()
|
BitmapPrivate()
|
||||||
|
: megaSurface(0)
|
||||||
{
|
{
|
||||||
font = &gState->defaultFont();
|
font = &gState->defaultFont();
|
||||||
}
|
}
|
||||||
|
@ -143,10 +157,19 @@ Bitmap::Bitmap(const char *filename)
|
||||||
if (!imgSurf)
|
if (!imgSurf)
|
||||||
throw Exception(Exception::SDLError, "SDL: %s", SDL_GetError());
|
throw Exception(Exception::SDLError, "SDL: %s", SDL_GetError());
|
||||||
|
|
||||||
TEXFBO tex;
|
|
||||||
|
|
||||||
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
|
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
|
||||||
|
|
||||||
|
if (imgSurf->w > glState.caps.maxTexSize || imgSurf->h > glState.caps.maxTexSize)
|
||||||
|
{
|
||||||
|
/* Mega surface */
|
||||||
|
p = new BitmapPrivate;
|
||||||
|
p->megaSurface = imgSurf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Regular surface */
|
||||||
|
|
||||||
|
TEXFBO tex;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
tex = gState->texPool().request(imgSurf->w, imgSurf->h);
|
tex = gState->texPool().request(imgSurf->w, imgSurf->h);
|
||||||
|
@ -164,6 +187,7 @@ Bitmap::Bitmap(const char *filename)
|
||||||
TEX::uploadImage(p->tex.width, p->tex.height, imgSurf->pixels, GL_RGBA);
|
TEX::uploadImage(p->tex.width, p->tex.height, imgSurf->pixels, GL_RGBA);
|
||||||
|
|
||||||
SDL_FreeSurface(imgSurf);
|
SDL_FreeSurface(imgSurf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap::Bitmap(int width, int height)
|
Bitmap::Bitmap(int width, int height)
|
||||||
|
@ -196,14 +220,20 @@ Bitmap::~Bitmap()
|
||||||
|
|
||||||
int Bitmap::width() const
|
int Bitmap::width() const
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
if (p->megaSurface)
|
||||||
|
return p->megaSurface->w;
|
||||||
|
|
||||||
return p->tex.width;
|
return p->tex.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Bitmap::height() const
|
int Bitmap::height() const
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
if (p->megaSurface)
|
||||||
|
return p->megaSurface->h;
|
||||||
|
|
||||||
return p->tex.height;
|
return p->tex.height;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +257,8 @@ void Bitmap::stretchBlt(const IntRect &destRect,
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED;
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
opacity = clamp(opacity, 0, 255);
|
opacity = clamp(opacity, 0, 255);
|
||||||
|
|
||||||
if (opacity == 0)
|
if (opacity == 0)
|
||||||
|
@ -291,7 +323,9 @@ void Bitmap::fillRect(int x, int y,
|
||||||
|
|
||||||
void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
|
void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
p->fillRect(rect, color);
|
p->fillRect(rect, color);
|
||||||
|
|
||||||
|
@ -310,7 +344,9 @@ void Bitmap::gradientFillRect(const IntRect &rect,
|
||||||
const Vec4 &color1, const Vec4 &color2,
|
const Vec4 &color1, const Vec4 &color2,
|
||||||
bool vertical)
|
bool vertical)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
|
@ -354,7 +390,9 @@ void Bitmap::clearRect(int x, int y, int width, int height)
|
||||||
|
|
||||||
void Bitmap::clearRect(const IntRect &rect)
|
void Bitmap::clearRect(const IntRect &rect)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
p->fillRect(rect, Vec4());
|
p->fillRect(rect, Vec4());
|
||||||
|
|
||||||
|
@ -363,7 +401,9 @@ void Bitmap::clearRect(const IntRect &rect)
|
||||||
|
|
||||||
void Bitmap::clear()
|
void Bitmap::clear()
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
/* Any queued points won't be visible after this anyway */
|
/* Any queued points won't be visible after this anyway */
|
||||||
p->pointArray.reset();
|
p->pointArray.reset();
|
||||||
|
@ -383,6 +423,8 @@ Vec4 Bitmap::getPixel(int x, int y) const
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED;
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
if (x < 0 || y < 0 || x >= width() || y >= height())
|
if (x < 0 || y < 0 || x >= width() || y >= height())
|
||||||
return Vec4();
|
return Vec4();
|
||||||
|
|
||||||
|
@ -399,7 +441,9 @@ Vec4 Bitmap::getPixel(int x, int y) const
|
||||||
|
|
||||||
void Bitmap::setPixel(int x, int y, const Vec4 &color)
|
void Bitmap::setPixel(int x, int y, const Vec4 &color)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
p->pointArray.append(Vec2(x+.5, y+.5), color);
|
p->pointArray.append(Vec2(x+.5, y+.5), color);
|
||||||
|
|
||||||
|
@ -408,7 +452,9 @@ void Bitmap::setPixel(int x, int y, const Vec4 &color)
|
||||||
|
|
||||||
void Bitmap::hueChange(int hue)
|
void Bitmap::hueChange(int hue)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
if ((hue % 360) == 0)
|
if ((hue % 360) == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -458,7 +504,9 @@ void Bitmap::drawText(int x, int y,
|
||||||
|
|
||||||
void Bitmap::drawText(const IntRect &rect, const char *str, int align)
|
void Bitmap::drawText(const IntRect &rect, const char *str, int align)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
if (*str == '\0')
|
if (*str == '\0')
|
||||||
return;
|
return;
|
||||||
|
@ -557,7 +605,9 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
|
||||||
|
|
||||||
IntRect Bitmap::textSize(const char *str)
|
IntRect Bitmap::textSize(const char *str)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
GUARD_MEGA;
|
||||||
|
|
||||||
TTF_Font *font = p->font->getSdlFont();
|
TTF_Font *font = p->font->getSdlFont();
|
||||||
|
|
||||||
|
@ -577,6 +627,9 @@ void Bitmap::flush() const
|
||||||
if (isDisposed())
|
if (isDisposed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (p->megaSurface)
|
||||||
|
return;
|
||||||
|
|
||||||
p->flushPoints();
|
p->flushPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,6 +638,16 @@ TEXFBO &Bitmap::getGLTypes()
|
||||||
return p->tex;
|
return p->tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_Surface *Bitmap::megaSurface()
|
||||||
|
{
|
||||||
|
return p->megaSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bitmap::ensureNonMega()
|
||||||
|
{
|
||||||
|
GUARD_MEGA;
|
||||||
|
}
|
||||||
|
|
||||||
void Bitmap::bindTex(ShaderBase &shader)
|
void Bitmap::bindTex(ShaderBase &shader)
|
||||||
{
|
{
|
||||||
p->bindTexture(shader);
|
p->bindTexture(shader);
|
||||||
|
@ -592,6 +655,10 @@ void Bitmap::bindTex(ShaderBase &shader)
|
||||||
|
|
||||||
void Bitmap::releaseResources()
|
void Bitmap::releaseResources()
|
||||||
{
|
{
|
||||||
|
if (p->megaSurface)
|
||||||
|
SDL_FreeSurface(p->megaSurface);
|
||||||
|
else
|
||||||
gState->texPool().release(p->tex);
|
gState->texPool().release(p->tex);
|
||||||
|
|
||||||
delete p;
|
delete p;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
class Font;
|
class Font;
|
||||||
class ShaderBase;
|
class ShaderBase;
|
||||||
struct TEXFBO;
|
struct TEXFBO;
|
||||||
|
struct SDL_Surface;
|
||||||
|
|
||||||
struct BitmapPrivate;
|
struct BitmapPrivate;
|
||||||
// FIXME make this class use proper RGSS classes again
|
// FIXME make this class use proper RGSS classes again
|
||||||
|
@ -104,6 +105,8 @@ public:
|
||||||
/* <internal> */
|
/* <internal> */
|
||||||
void flush() const;
|
void flush() const;
|
||||||
TEXFBO &getGLTypes();
|
TEXFBO &getGLTypes();
|
||||||
|
SDL_Surface *megaSurface();
|
||||||
|
void ensureNonMega();
|
||||||
|
|
||||||
/* Binds the backing texture and sets the correct
|
/* Binds the backing texture and sets the correct
|
||||||
* texture size uniform in shader */
|
* texture size uniform in shader */
|
||||||
|
|
|
@ -85,6 +85,7 @@ Plane::Plane(Viewport *viewport)
|
||||||
|
|
||||||
#define DISP_CLASS_NAME "plane"
|
#define DISP_CLASS_NAME "plane"
|
||||||
|
|
||||||
|
DEF_ATTR_RD_SIMPLE(Plane, Bitmap, Bitmap*, p->bitmap)
|
||||||
DEF_ATTR_RD_SIMPLE(Plane, OX, int, p->ox)
|
DEF_ATTR_RD_SIMPLE(Plane, OX, int, p->ox)
|
||||||
DEF_ATTR_RD_SIMPLE(Plane, OY, int, p->oy)
|
DEF_ATTR_RD_SIMPLE(Plane, OY, int, p->oy)
|
||||||
DEF_ATTR_RD_SIMPLE(Plane, ZoomX, float, p->zoomX)
|
DEF_ATTR_RD_SIMPLE(Plane, ZoomX, float, p->zoomX)
|
||||||
|
@ -92,7 +93,6 @@ DEF_ATTR_RD_SIMPLE(Plane, ZoomY, float, p->zoomY)
|
||||||
DEF_ATTR_RD_SIMPLE(Plane, BlendType, int, p->blendType)
|
DEF_ATTR_RD_SIMPLE(Plane, BlendType, int, p->blendType)
|
||||||
|
|
||||||
DEF_ATTR_SIMPLE(Plane, Opacity, int, p->opacity)
|
DEF_ATTR_SIMPLE(Plane, Opacity, int, p->opacity)
|
||||||
DEF_ATTR_SIMPLE(Plane, Bitmap, Bitmap*, p->bitmap)
|
|
||||||
DEF_ATTR_SIMPLE(Plane, Color, Color*, p->color)
|
DEF_ATTR_SIMPLE(Plane, Color, Color*, p->color)
|
||||||
DEF_ATTR_SIMPLE(Plane, Tone, Tone*, p->tone)
|
DEF_ATTR_SIMPLE(Plane, Tone, Tone*, p->tone)
|
||||||
|
|
||||||
|
@ -101,6 +101,15 @@ Plane::~Plane()
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plane::setBitmap(Bitmap *value)
|
||||||
|
{
|
||||||
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
value->ensureNonMega();
|
||||||
|
|
||||||
|
p->bitmap = value;
|
||||||
|
}
|
||||||
|
|
||||||
void Plane::setOX(int value)
|
void Plane::setOX(int value)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED
|
||||||
|
@ -205,6 +214,8 @@ void Plane::draw()
|
||||||
p->quad.draw();
|
p->quad.draw();
|
||||||
|
|
||||||
TEX::setRepeat(false);
|
TEX::setRepeat(false);
|
||||||
|
|
||||||
|
glState.blendMode.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plane::onGeometryChange(const Scene::Geometry &geo)
|
void Plane::onGeometryChange(const Scene::Geometry &geo)
|
||||||
|
|
|
@ -152,6 +152,8 @@ void Sprite::setBitmap(Bitmap *bitmap)
|
||||||
if (p->bitmap == bitmap)
|
if (p->bitmap == bitmap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bitmap->ensureNonMega();
|
||||||
|
|
||||||
p->bitmap = bitmap;
|
p->bitmap = bitmap;
|
||||||
*p->srcRect = bitmap->rect();
|
*p->srcRect = bitmap->rect();
|
||||||
p->onSrcRectChange();
|
p->onSrcRectChange();
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
#include "tileatlas.h"
|
||||||
|
|
||||||
|
namespace TileAtlas
|
||||||
|
{
|
||||||
|
|
||||||
|
/* A Row represents a Rect
|
||||||
|
* with undefined width */
|
||||||
|
struct Row
|
||||||
|
{
|
||||||
|
int x, y, h;
|
||||||
|
|
||||||
|
Row(int x, int y, int h)
|
||||||
|
: x(x), y(y), h(h)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QList<Row> RowList;
|
||||||
|
|
||||||
|
/* Autotile area width */
|
||||||
|
static const int atAreaW = 96*4;
|
||||||
|
/* Autotile area height */
|
||||||
|
static const int atAreaH = 128*7;
|
||||||
|
/* Autotile area */
|
||||||
|
static const int atArea = atAreaW * atAreaH;
|
||||||
|
|
||||||
|
static const int tilesetW = 256;
|
||||||
|
static const int tsLaneW = tilesetW / 2;
|
||||||
|
|
||||||
|
static int freeArea(int width, int height)
|
||||||
|
{
|
||||||
|
return width * height - atArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i minSize(int tilesetH, int maxAtlasSize)
|
||||||
|
{
|
||||||
|
int width = atAreaW;
|
||||||
|
int height = atAreaH;
|
||||||
|
|
||||||
|
const int tsArea = tilesetW * tilesetH;
|
||||||
|
|
||||||
|
/* Expand vertically */
|
||||||
|
while (freeArea(width, height) < tsArea && height < maxAtlasSize)
|
||||||
|
height += 32;
|
||||||
|
|
||||||
|
if (freeArea(width, height) >= tsArea && height <= maxAtlasSize)
|
||||||
|
return Vec2i(width, height);
|
||||||
|
|
||||||
|
/* Expand horizontally */
|
||||||
|
while (freeArea(width, height) < tsArea && width < maxAtlasSize)
|
||||||
|
width += tsLaneW;
|
||||||
|
|
||||||
|
if (freeArea(width, height) >= tsArea && width <= maxAtlasSize)
|
||||||
|
return Vec2i(width, height);
|
||||||
|
|
||||||
|
return Vec2i(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static RowList calcSrcRows(int tilesetH)
|
||||||
|
{
|
||||||
|
RowList rows;
|
||||||
|
|
||||||
|
rows << Row(0, 0, tilesetH);
|
||||||
|
rows << Row(tsLaneW, 0, tilesetH);
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RowList calcDstRows(int atlasW, int atlasH)
|
||||||
|
{
|
||||||
|
RowList rows;
|
||||||
|
|
||||||
|
/* Rows below the autotile area */
|
||||||
|
const int underAt = atlasH - atAreaH;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
rows << Row(i*tsLaneW, atAreaH, underAt);
|
||||||
|
|
||||||
|
if (atlasW <= atAreaW)
|
||||||
|
return rows;
|
||||||
|
|
||||||
|
const int remRows = (atlasW - atAreaW) / tsLaneW;
|
||||||
|
|
||||||
|
for (int i = 0; i < remRows; ++i)
|
||||||
|
rows << Row(i*tsLaneW + atAreaW, 0, atlasH);
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlitList calcBlitsInt(RowList &srcRows, RowList &dstRows)
|
||||||
|
{
|
||||||
|
BlitList blits;
|
||||||
|
|
||||||
|
while (!srcRows.empty())
|
||||||
|
{
|
||||||
|
Row srcRow = srcRows.takeFirst();
|
||||||
|
Q_ASSERT(srcRow.h > 0);
|
||||||
|
|
||||||
|
while (!dstRows.empty() && srcRow.h > 0)
|
||||||
|
{
|
||||||
|
Row dstRow = dstRows.takeFirst();
|
||||||
|
|
||||||
|
if (srcRow.h > dstRow.h)
|
||||||
|
{
|
||||||
|
/* srcRow doesn't fully fit into dstRow */
|
||||||
|
blits << Blit(srcRow.x, srcRow.y,
|
||||||
|
dstRow.x, dstRow.y, dstRow.h);
|
||||||
|
|
||||||
|
srcRow.y += dstRow.h;
|
||||||
|
srcRow.h -= dstRow.h;
|
||||||
|
}
|
||||||
|
else if (srcRow.h < dstRow.h)
|
||||||
|
{
|
||||||
|
/* srcRow fits into dstRow with space remaining */
|
||||||
|
blits << Blit(srcRow.x, srcRow.y,
|
||||||
|
dstRow.x, dstRow.y, srcRow.h);
|
||||||
|
|
||||||
|
dstRow.y += srcRow.h;
|
||||||
|
dstRow.h -= srcRow.h;
|
||||||
|
dstRows.prepend(dstRow);
|
||||||
|
srcRow.h = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* srcRow fits perfectly into dstRow */
|
||||||
|
blits << Blit(srcRow.x, srcRow.y,
|
||||||
|
dstRow.x, dstRow.y, dstRow.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blits;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlitList calcBlits(int tilesetH, const Vec2i &atlasSize)
|
||||||
|
{
|
||||||
|
RowList srcRows = calcSrcRows(tilesetH);
|
||||||
|
RowList dstRows = calcDstRows(atlasSize.x, atlasSize.y);
|
||||||
|
|
||||||
|
return calcBlitsInt(srcRows, dstRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i tileToAtlasCoor(int tileX, int tileY, int tilesetH, int atlasH)
|
||||||
|
{
|
||||||
|
int laneX = tileX*32;
|
||||||
|
int laneY = tileY*32;
|
||||||
|
|
||||||
|
int longlaneH = atlasH;
|
||||||
|
int shortlaneH = longlaneH - atAreaH;
|
||||||
|
|
||||||
|
int longlaneOffset = shortlaneH * 3;
|
||||||
|
|
||||||
|
int laneIdx = 0;
|
||||||
|
int atlasY = 0;
|
||||||
|
|
||||||
|
/* Check if we're inside the 2nd lane */
|
||||||
|
if (laneX >= tsLaneW)
|
||||||
|
{
|
||||||
|
laneY += tilesetH;
|
||||||
|
laneX -= tsLaneW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (laneY < longlaneOffset)
|
||||||
|
{
|
||||||
|
/* Below autotile area */
|
||||||
|
laneIdx = laneY / shortlaneH;
|
||||||
|
atlasY = laneY % shortlaneH + atAreaH;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Right of autotile area */
|
||||||
|
int _y = laneY - longlaneOffset;
|
||||||
|
laneIdx = 3 + _y / longlaneH;
|
||||||
|
atlasY = _y % longlaneH;
|
||||||
|
}
|
||||||
|
|
||||||
|
int atlasX = laneIdx * tsLaneW + laneX;
|
||||||
|
|
||||||
|
return Vec2i(atlasX, atlasY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef TILEATLAS_H
|
||||||
|
#define TILEATLAS_H
|
||||||
|
|
||||||
|
#include "etc-internal.h"
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
namespace TileAtlas
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Abstract definition of a blit
|
||||||
|
* operation with undefined rect width */
|
||||||
|
struct Blit
|
||||||
|
{
|
||||||
|
Vec2i src;
|
||||||
|
Vec2i dst;
|
||||||
|
int h;
|
||||||
|
|
||||||
|
Blit(int sx, int sy,
|
||||||
|
int dx, int dy,
|
||||||
|
int h)
|
||||||
|
: src(sx, sy),
|
||||||
|
dst(dx, dy),
|
||||||
|
h(h)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QList<Blit> BlitList;
|
||||||
|
|
||||||
|
/* Calculates the minimum atlas size required to hold
|
||||||
|
* a tileset of height 'tilesetH'. If the required dimensions
|
||||||
|
* exceed 'maxAtlasSize', Vec2i(-1, -1) is returned. */
|
||||||
|
Vec2i minSize(int tilesetH, int maxAtlasSize);
|
||||||
|
|
||||||
|
/* Calculates a series of blits necessary to fill dstRows
|
||||||
|
* with srcRows without wasting any space */
|
||||||
|
BlitList calcBlits(int tilesetH, const Vec2i &atlasSize);
|
||||||
|
|
||||||
|
/* Translates a tile coordinate (not pixel!) to a physical
|
||||||
|
* pixel coordinate in the atlas */
|
||||||
|
Vec2i tileToAtlasCoor(int tileX, int tileY, int tilesetH, int atlasH);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TILEATLAS_H
|
412
src/tilemap.cpp
412
src/tilemap.cpp
|
@ -32,6 +32,7 @@
|
||||||
#include "quadarray.h"
|
#include "quadarray.h"
|
||||||
#include "texpool.h"
|
#include "texpool.h"
|
||||||
#include "quad.h"
|
#include "quad.h"
|
||||||
|
#include "tileatlas.h"
|
||||||
|
|
||||||
#include "sigc++/connection.h"
|
#include "sigc++/connection.h"
|
||||||
|
|
||||||
|
@ -40,14 +41,23 @@
|
||||||
|
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <SDL2/SDL_surface.h>
|
||||||
|
|
||||||
extern const StaticRect autotileRects[];
|
extern const StaticRect autotileRects[];
|
||||||
|
|
||||||
typedef QVector<SVertex> SVVector;
|
typedef QVector<SVertex> SVVector;
|
||||||
|
typedef struct { SVVector v[4]; } TileVBuffer;
|
||||||
|
|
||||||
static const int tilesetW = 8 * 32;
|
static const int tilesetW = 8 * 32;
|
||||||
static const int autotileW = 3 * 32;
|
static const int autotileW = 3 * 32;
|
||||||
static const int autotileH = 4 * 32;
|
static const int autotileH = 4 * 32;
|
||||||
static const int atlasFrameW = tilesetW + autotileW;
|
|
||||||
|
static const int autotileCount = 7;
|
||||||
|
|
||||||
|
static const int atAreaW = autotileW * 4;
|
||||||
|
static const int atAreaH = autotileH * autotileCount;
|
||||||
|
|
||||||
|
static const int tsLaneW = tilesetW / 2;
|
||||||
|
|
||||||
/* Vocabulary:
|
/* Vocabulary:
|
||||||
*
|
*
|
||||||
|
@ -57,32 +67,57 @@ static const int atlasFrameW = tilesetW + autotileW;
|
||||||
* This means that we have to watch the 'modified' signals
|
* This means that we have to watch the 'modified' signals
|
||||||
* of all Bitmaps that make up the atlas, and update it
|
* of all Bitmaps that make up the atlas, and update it
|
||||||
* as required during runtime.
|
* as required during runtime.
|
||||||
* The atlas is made up of one or four 'atlas frames'.
|
* The atlas is tightly packed, with the autotiles located
|
||||||
* They look like this: ('AT' = Autotile)
|
* in the top left corener and the tileset image filing the
|
||||||
|
* remaining open space (below the autotiles as well as
|
||||||
|
* besides it). The tileset is vertically cut in half, where
|
||||||
|
* the first half fills available texture space, and then the
|
||||||
|
* other half (as if the right half was cut and pasted below
|
||||||
|
* the left half before fitting it all into the atlas).
|
||||||
|
* Internally these halves are called "tileset lanes".
|
||||||
*
|
*
|
||||||
* Atlas frame
|
* Tile atlas
|
||||||
* ----------------*--------
|
* *-----------------------*--------------*
|
||||||
* | | |
|
* | | | | | ¦ |
|
||||||
* | | AT1 |
|
* | AT1 | AT1 | AT1 | AT1 | ¦ |
|
||||||
* | | |
|
* | FR0 | FR1 | FR2 | FR3 | | ¦ | |
|
||||||
* | | |
|
* |-----|-----|-----|-----| v ¦ v |
|
||||||
* | |-------|
|
* | | | | | ¦ |
|
||||||
* | Tileset | |
|
* | AT1 | | | | ¦ |
|
||||||
* | | AT2 |
|
* | | | | | ¦ |
|
||||||
* | | |
|
* |-----|-----|-----|-----| ¦ |
|
||||||
* | | |
|
* |[...]| | | | ¦ |
|
||||||
* | |-------|
|
* |-----|-----|-----|-----| ¦ |
|
||||||
* | | |
|
* | | | | | | ¦ | |
|
||||||
* ...
|
* | AT7 | | | | v ¦ v |
|
||||||
|
* | | | | | ¦ |
|
||||||
|
* |-----|-----|-----|-----| ¦ |
|
||||||
|
* | ¦ ¦ ¦ ¦ |
|
||||||
|
* | Tile- ¦ | ¦ | ¦ ¦ |
|
||||||
|
* | set ¦ v ¦ v ¦ ¦ |
|
||||||
|
* | ¦ ¦ ¦ | ¦ | |
|
||||||
|
* | | ¦ ¦ ¦ v ¦ v |
|
||||||
|
* | v ¦ | ¦ | ¦ ¦ |
|
||||||
|
* | ¦ v ¦ v ¦ ¦ |
|
||||||
|
* | ¦ ¦ ¦ ¦ |
|
||||||
|
* *---------------------------------------*
|
||||||
*
|
*
|
||||||
* If none of the attached autotiles is animated, the
|
* When allocating the atlas size, we first expand vertically
|
||||||
* atlas consists of only one such frame. In case of
|
* until all the space immediately below the autotile area
|
||||||
* animated autotiles, it consists of four such frames,
|
* is used up, and then, when the max texture size
|
||||||
* where each autotile area contains the respective
|
* is reached, horizontally.
|
||||||
* animation frame of the autotile. Static autotiles
|
*
|
||||||
* and the tileset area are identical for all frames.
|
* To animate the autotiles, we keep 4 buffers (packed into
|
||||||
* This allows us to keep vertex data static and only
|
* one big VBO and accessed using offsets) with vertex data
|
||||||
* change the x-offset in the atlas texture during animation.
|
* corresponding to the respective animation frame. Likewise,
|
||||||
|
* the IBO is expanded to 4 times its usual size. In practice
|
||||||
|
* this means that all vertex data which does not stem from an
|
||||||
|
* animated autotile is duplicated across all 4 buffers.
|
||||||
|
* The range of one such buffer inside the VBO is called
|
||||||
|
* buffer frame, and tiles.bufferFrameSize * bufferIndex gives
|
||||||
|
* us the base offset into the IBO to access it.
|
||||||
|
* If there are no animated autotiles attached, we only use
|
||||||
|
* the first buffer.
|
||||||
*
|
*
|
||||||
* Elements:
|
* Elements:
|
||||||
* Even though the Tilemap carries similarities with other
|
* Even though the Tilemap carries similarities with other
|
||||||
|
@ -190,7 +225,7 @@ struct TilemapPrivate
|
||||||
Viewport *viewport;
|
Viewport *viewport;
|
||||||
|
|
||||||
Tilemap::Autotiles autotilesProxy;
|
Tilemap::Autotiles autotilesProxy;
|
||||||
Bitmap *autotiles[7];
|
Bitmap *autotiles[autotileCount];
|
||||||
|
|
||||||
Bitmap *tileset;
|
Bitmap *tileset;
|
||||||
Table *mapData;
|
Table *mapData;
|
||||||
|
@ -207,14 +242,15 @@ struct TilemapPrivate
|
||||||
/* Tile atlas */
|
/* Tile atlas */
|
||||||
struct {
|
struct {
|
||||||
TEXFBO gl;
|
TEXFBO gl;
|
||||||
bool animated;
|
|
||||||
int frameH;
|
Vec2i size;
|
||||||
int width;
|
|
||||||
|
/* Indices of usable
|
||||||
|
* (not null, not disposed) autotiles */
|
||||||
|
QVector<uint8_t> usableATs;
|
||||||
|
|
||||||
/* Indices of animated autotiles */
|
/* Indices of animated autotiles */
|
||||||
QVector<uint8_t> animatedATs;
|
QVector<uint8_t> animatedATs;
|
||||||
/* Animation state */
|
|
||||||
uint8_t frameIdx;
|
|
||||||
uint8_t aniIdx;
|
|
||||||
} atlas;
|
} atlas;
|
||||||
|
|
||||||
/* Map size in tiles */
|
/* Map size in tiles */
|
||||||
|
@ -222,10 +258,10 @@ struct TilemapPrivate
|
||||||
int mapHeight;
|
int mapHeight;
|
||||||
|
|
||||||
/* Ground layer vertices */
|
/* Ground layer vertices */
|
||||||
SVVector groundVert;
|
TileVBuffer groundVert;
|
||||||
|
|
||||||
/* Scanrow vertices */
|
/* Scanrow vertices */
|
||||||
QVector<SVVector> scanrowVert;
|
QVector<TileVBuffer> scanrowVert;
|
||||||
|
|
||||||
/* Base quad indices of each scanrow
|
/* Base quad indices of each scanrow
|
||||||
* in the shared buffer */
|
* in the shared buffer */
|
||||||
|
@ -237,6 +273,17 @@ struct TilemapPrivate
|
||||||
{
|
{
|
||||||
VAO::ID vao;
|
VAO::ID vao;
|
||||||
VBO::ID vbo;
|
VBO::ID vbo;
|
||||||
|
bool animated;
|
||||||
|
|
||||||
|
/* Buffer count is either 1 or 4 */
|
||||||
|
uint8_t bufferCount;
|
||||||
|
|
||||||
|
/* Size of an IBO buffer frame, in bytes */
|
||||||
|
GLintptr bufferFrameSize;
|
||||||
|
|
||||||
|
/* Animation state */
|
||||||
|
uint8_t frameIdx;
|
||||||
|
uint8_t aniIdx;
|
||||||
} tiles;
|
} tiles;
|
||||||
|
|
||||||
/* Flash buffers */
|
/* Flash buffers */
|
||||||
|
@ -276,13 +323,13 @@ struct TilemapPrivate
|
||||||
|
|
||||||
/* Change watches */
|
/* Change watches */
|
||||||
sigc::connection tilesetCon;
|
sigc::connection tilesetCon;
|
||||||
sigc::connection autotilesCon[7];
|
sigc::connection autotilesCon[autotileCount];
|
||||||
sigc::connection mapDataCon;
|
sigc::connection mapDataCon;
|
||||||
sigc::connection prioritiesCon;
|
sigc::connection prioritiesCon;
|
||||||
sigc::connection flashDataCon;
|
sigc::connection flashDataCon;
|
||||||
|
|
||||||
/* Dispose watches */
|
/* Dispose watches */
|
||||||
sigc::connection autotilesDispCon[7];
|
sigc::connection autotilesDispCon[autotileCount];
|
||||||
|
|
||||||
/* Draw prepare call */
|
/* Draw prepare call */
|
||||||
sigc::connection prepareCon;
|
sigc::connection prepareCon;
|
||||||
|
@ -295,6 +342,8 @@ struct TilemapPrivate
|
||||||
priorities(0),
|
priorities(0),
|
||||||
visible(true),
|
visible(true),
|
||||||
tileYOffset(0),
|
tileYOffset(0),
|
||||||
|
mapWidth(0),
|
||||||
|
mapHeight(0),
|
||||||
replicas(Normal),
|
replicas(Normal),
|
||||||
atlasSizeDirty(false),
|
atlasSizeDirty(false),
|
||||||
atlasDirty(false),
|
atlasDirty(false),
|
||||||
|
@ -305,9 +354,11 @@ struct TilemapPrivate
|
||||||
{
|
{
|
||||||
memset(autotiles, 0, sizeof(autotiles));
|
memset(autotiles, 0, sizeof(autotiles));
|
||||||
|
|
||||||
atlas.animatedATs.reserve(7);
|
atlas.animatedATs.reserve(autotileCount);
|
||||||
atlas.frameIdx = 0;
|
tiles.animated = false;
|
||||||
atlas.aniIdx = 0;
|
tiles.bufferCount = 1;
|
||||||
|
tiles.frameIdx = 0;
|
||||||
|
tiles.aniIdx = 0;
|
||||||
|
|
||||||
/* Init tile buffers */
|
/* Init tile buffers */
|
||||||
tiles.vbo = VBO::gen();
|
tiles.vbo = VBO::gen();
|
||||||
|
@ -367,7 +418,7 @@ struct TilemapPrivate
|
||||||
VBO::del(flash.vbo);
|
VBO::del(flash.vbo);
|
||||||
|
|
||||||
tilesetCon.disconnect();
|
tilesetCon.disconnect();
|
||||||
for (int i = 0; i < 7; ++i)
|
for (int i = 0; i < autotileCount; ++i)
|
||||||
{
|
{
|
||||||
autotilesCon[i].disconnect();
|
autotilesCon[i].disconnect();
|
||||||
autotilesDispCon[i].disconnect();
|
autotilesDispCon[i].disconnect();
|
||||||
|
@ -379,6 +430,62 @@ struct TilemapPrivate
|
||||||
prepareCon.disconnect();
|
prepareCon.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateAtlasInfo()
|
||||||
|
{
|
||||||
|
if (!tileset || tileset->isDisposed())
|
||||||
|
{
|
||||||
|
atlas.size = Vec2i();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
atlas.size = TileAtlas::minSize(tileset->height(), glState.caps.maxTexSize);
|
||||||
|
|
||||||
|
if (atlas.size.x < 0)
|
||||||
|
throw Exception(Exception::MKXPError,
|
||||||
|
"Cannot allocate big enough texture for tileset atlas");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateAutotileInfo()
|
||||||
|
{
|
||||||
|
/* Check if and which autotiles are animated */
|
||||||
|
QVector<uint8_t> &usableATs = atlas.usableATs;
|
||||||
|
QVector<uint8_t> &animatedATs = atlas.animatedATs;
|
||||||
|
|
||||||
|
usableATs.clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < autotileCount; ++i)
|
||||||
|
{
|
||||||
|
if (!autotiles[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (autotiles[i]->isDisposed())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
usableATs.append(i);
|
||||||
|
|
||||||
|
autotiles[i]->flush();
|
||||||
|
|
||||||
|
if (autotiles[i]->width() > autotileW)
|
||||||
|
animatedATs.append(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.animated = !animatedATs.empty();
|
||||||
|
tiles.bufferCount = animatedATs.empty() ? 1 : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateMapDataInfo()
|
||||||
|
{
|
||||||
|
if (!mapData)
|
||||||
|
{
|
||||||
|
mapWidth = 0;
|
||||||
|
mapHeight = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapWidth = mapData->xSize();
|
||||||
|
mapHeight = mapData->ySize();
|
||||||
|
}
|
||||||
|
|
||||||
void updateSceneGeometry(const Scene::Geometry &geo)
|
void updateSceneGeometry(const Scene::Geometry &geo)
|
||||||
{
|
{
|
||||||
elem.sceneOffset.x = geo.rect.x - geo.xOrigin;
|
elem.sceneOffset.x = geo.rect.x - geo.xOrigin;
|
||||||
|
@ -388,12 +495,12 @@ struct TilemapPrivate
|
||||||
|
|
||||||
void updatePosition()
|
void updatePosition()
|
||||||
{
|
{
|
||||||
dispPos.x = -offset.x + elem.sceneOffset.x;
|
|
||||||
dispPos.y = -offset.y + elem.sceneOffset.y;
|
|
||||||
|
|
||||||
if (mapWidth == 0 || mapHeight == 0)
|
if (mapWidth == 0 || mapHeight == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
dispPos.x = -offset.x + elem.sceneOffset.x;
|
||||||
|
dispPos.y = -offset.y + elem.sceneOffset.y;
|
||||||
|
|
||||||
dispPos.x %= mapWidth * 32;
|
dispPos.x %= mapWidth * 32;
|
||||||
dispPos.y %= mapHeight * 32;
|
dispPos.y %= mapHeight * 32;
|
||||||
}
|
}
|
||||||
|
@ -403,6 +510,10 @@ struct TilemapPrivate
|
||||||
void updateReplicas()
|
void updateReplicas()
|
||||||
{
|
{
|
||||||
replicas = Normal;
|
replicas = Normal;
|
||||||
|
|
||||||
|
if (mapWidth == 0 || mapHeight == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
const IntRect &sRect = elem.sceneGeo.rect;
|
const IntRect &sRect = elem.sceneGeo.rect;
|
||||||
|
|
||||||
if (dispPos.x > sRect.x)
|
if (dispPos.x > sRect.x)
|
||||||
|
@ -453,34 +564,11 @@ struct TilemapPrivate
|
||||||
/* Allocates correctly sized TexFBO for atlas */
|
/* Allocates correctly sized TexFBO for atlas */
|
||||||
void allocateAtlas()
|
void allocateAtlas()
|
||||||
{
|
{
|
||||||
/* Check if and which autotiles are animated */
|
updateAtlasInfo();
|
||||||
QVector<uint8_t> &animatedATs = atlas.animatedATs;
|
|
||||||
|
|
||||||
for (int i = 0; i < 7; ++i)
|
|
||||||
{
|
|
||||||
Bitmap *autotile = autotiles[i];
|
|
||||||
|
|
||||||
if (!autotile)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (autotile->isDisposed())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
autotile->flush();
|
|
||||||
|
|
||||||
if (autotile->width() > autotileW)
|
|
||||||
animatedATs.append(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
atlas.animated = !animatedATs.empty();
|
|
||||||
|
|
||||||
atlas.frameH = max(tileset->height(), autotileH*7);
|
|
||||||
|
|
||||||
/* Aquire atlas tex */
|
/* Aquire atlas tex */
|
||||||
atlas.width = animatedATs.empty() ? atlasFrameW : atlasFrameW * 4;
|
|
||||||
|
|
||||||
gState->texPool().release(atlas.gl);
|
gState->texPool().release(atlas.gl);
|
||||||
atlas.gl = gState->texPool().request(atlas.width, atlas.frameH);
|
atlas.gl = gState->texPool().request(atlas.size.x, atlas.size.y);
|
||||||
|
|
||||||
atlasDirty = true;
|
atlasDirty = true;
|
||||||
}
|
}
|
||||||
|
@ -490,7 +578,12 @@ struct TilemapPrivate
|
||||||
{
|
{
|
||||||
tileset->flush();
|
tileset->flush();
|
||||||
|
|
||||||
QVector<uint8_t> &animatedATs = atlas.animatedATs;
|
updateAutotileInfo();
|
||||||
|
|
||||||
|
Q_FOREACH (uint8_t i, atlas.usableATs)
|
||||||
|
autotiles[i]->flush();
|
||||||
|
|
||||||
|
TileAtlas::BlitList blits = TileAtlas::calcBlits(tileset->height(), atlas.size);
|
||||||
|
|
||||||
/* Clear atlas */
|
/* Clear atlas */
|
||||||
FBO::bind(atlas.gl.fbo, FBO::Draw);
|
FBO::bind(atlas.gl.fbo, FBO::Draw);
|
||||||
|
@ -502,52 +595,64 @@ struct TilemapPrivate
|
||||||
glState.scissorTest.pop();
|
glState.scissorTest.pop();
|
||||||
glState.clearColor.pop();
|
glState.clearColor.pop();
|
||||||
|
|
||||||
int tsW = tilesetW;
|
/* Blit autotiles */
|
||||||
int tsH = tileset->height();
|
Q_FOREACH (uint8_t i, atlas.usableATs)
|
||||||
|
|
||||||
/* Assemble first frame with static content */
|
|
||||||
FBO::bind(tileset->getGLTypes().fbo, FBO::Read);
|
|
||||||
FBO::blit(0, 0, 0, 0, tsW, tsH);
|
|
||||||
|
|
||||||
for (int i = 0; i < 7; ++i)
|
|
||||||
{
|
{
|
||||||
if (!autotiles[i])
|
int blitW = min(autotiles[i]->width(), atAreaW);
|
||||||
continue;
|
int blitH = min(autotiles[i]->height(), atAreaH);
|
||||||
|
|
||||||
if (autotiles[i]->isDisposed())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (animatedATs.contains(i))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
FBO::bind(autotiles[i]->getGLTypes().fbo, FBO::Read);
|
FBO::bind(autotiles[i]->getGLTypes().fbo, FBO::Read);
|
||||||
FBO::blit(0, 0, tsW, i*autotileH, autotileW, autotileH);
|
FBO::blit(0, 0, 0, i*autotileH, blitW, blitH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If there aren't any animated autotiles, we're done */
|
/* Blit tileset */
|
||||||
if (!atlas.animated)
|
if (tileset->megaSurface())
|
||||||
return;
|
|
||||||
|
|
||||||
/* Copy the first frame to the remaining 3 */
|
|
||||||
FBO::bind(atlas.gl.fbo, FBO::Read);
|
|
||||||
|
|
||||||
for (int i = 1; i < 4; ++i)
|
|
||||||
FBO::blit(0, 0, i*atlasFrameW, 0, atlasFrameW, atlas.frameH);
|
|
||||||
|
|
||||||
/* Finally, patch in the animated autotiles */
|
|
||||||
for (int i = 0; i < animatedATs.count(); ++i)
|
|
||||||
{
|
{
|
||||||
int atInd = animatedATs[i];
|
/* Mega surface tileset */
|
||||||
Bitmap *at = autotiles[atInd];
|
FBO::unbind(FBO::Draw);
|
||||||
|
TEX::bind(atlas.gl.tex);
|
||||||
|
|
||||||
if (at->isDisposed())
|
SDL_Surface *tsSurf = tileset->megaSurface();
|
||||||
continue;
|
|
||||||
|
|
||||||
FBO::bind(at->getGLTypes().fbo, FBO::Read);
|
int bpp;
|
||||||
|
Uint32 rMask, gMask, bMask, aMask;
|
||||||
|
SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
|
||||||
|
&bpp, &rMask, &gMask, &bMask, &aMask);
|
||||||
|
|
||||||
for (int j = 0; j < 4; ++j)
|
for (int i = 0; i < blits.count(); ++i)
|
||||||
FBO::blit(j*autotileW, 0, (j*atlasFrameW)+tsW, atInd*autotileH,
|
{
|
||||||
autotileW, autotileH);
|
TileAtlas::Blit &blitOp = blits[i];
|
||||||
|
|
||||||
|
SDL_Surface *blitTemp =
|
||||||
|
SDL_CreateRGBSurface(0, tsLaneW, blitOp.h, bpp, rMask, gMask, bMask, aMask);
|
||||||
|
|
||||||
|
SDL_Rect tsRect;
|
||||||
|
tsRect.x = blitOp.src.x;
|
||||||
|
tsRect.y = blitOp.src.y;
|
||||||
|
tsRect.w = tsLaneW;
|
||||||
|
tsRect.h = blitOp.h;
|
||||||
|
|
||||||
|
SDL_Rect tmpRect = tsRect;
|
||||||
|
tmpRect.x = tmpRect.y = 0;
|
||||||
|
|
||||||
|
SDL_UpperBlit(tsSurf, &tsRect, blitTemp, &tmpRect);
|
||||||
|
|
||||||
|
TEX::uploadSubImage(blitOp.dst.x, blitOp.dst.y, tsLaneW, blitOp.h, blitTemp->pixels, GL_RGBA);
|
||||||
|
|
||||||
|
SDL_FreeSurface(blitTemp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Regular tileset */
|
||||||
|
FBO::bind(tileset->getGLTypes().fbo, FBO::Read);
|
||||||
|
|
||||||
|
for (int i = 0; i < blits.count(); ++i)
|
||||||
|
{
|
||||||
|
TileAtlas::Blit &blitOp = blits[i];
|
||||||
|
|
||||||
|
FBO::blit(blitOp.src.x, blitOp.src.y, blitOp.dst.x, blitOp.dst.y, tsLaneW, blitOp.h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +687,7 @@ struct TilemapPrivate
|
||||||
return FloatRect(x, y, 16, 16);
|
return FloatRect(x, y, 16, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAutotile(int x, int y, int tileInd, SVVector &array)
|
void handleAutotile(int x, int y, int tileInd, TileVBuffer *array)
|
||||||
{
|
{
|
||||||
/* Which autotile [0-7] */
|
/* Which autotile [0-7] */
|
||||||
int atInd = tileInd / 48 - 1;
|
int atInd = tileInd / 48 - 1;
|
||||||
|
@ -591,20 +696,29 @@ struct TilemapPrivate
|
||||||
|
|
||||||
const StaticRect *pieceRect = &autotileRects[subInd*4];
|
const StaticRect *pieceRect = &autotileRects[subInd*4];
|
||||||
|
|
||||||
|
/* Iterate over the 4 tile pieces */
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
{
|
{
|
||||||
FloatRect posRect = getAutotilePieceRect(x*32, y*32, i);
|
FloatRect posRect = getAutotilePieceRect(x*32, y*32, i);
|
||||||
FloatRect texRect = pieceRect[i];
|
FloatRect texRect = pieceRect[i];
|
||||||
|
|
||||||
/* Adjust to atlas coordinates */
|
/* Adjust to atlas coordinates */
|
||||||
texRect.x += tilesetW;
|
|
||||||
texRect.y += atInd * autotileH;
|
texRect.y += atInd * autotileH;
|
||||||
|
|
||||||
SVertex v[4];
|
for (int k = 0; k < tiles.bufferCount; ++k)
|
||||||
Quad::setTexPosRect(v, texRect, posRect);
|
{
|
||||||
|
FloatRect _texRect = texRect;
|
||||||
|
|
||||||
|
if (atlas.animatedATs.contains(atInd))
|
||||||
|
_texRect.x += autotileW*k;
|
||||||
|
|
||||||
|
SVertex v[4];
|
||||||
|
Quad::setTexPosRect(v, _texRect, posRect);
|
||||||
|
|
||||||
|
/* Iterate over 4 vertices */
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
array.append(v[i]);
|
array->v[k].append(v[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,7 +736,7 @@ struct TilemapPrivate
|
||||||
if (prio == -1)
|
if (prio == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SVVector *targetArray;
|
TileVBuffer *targetArray;
|
||||||
|
|
||||||
/* Prio 0 tiles are all part of the same ground layer */
|
/* Prio 0 tiles are all part of the same ground layer */
|
||||||
if (prio == 0)
|
if (prio == 0)
|
||||||
|
@ -638,7 +752,7 @@ struct TilemapPrivate
|
||||||
/* Check for autotile */
|
/* Check for autotile */
|
||||||
if (tileInd < 48*8)
|
if (tileInd < 48*8)
|
||||||
{
|
{
|
||||||
handleAutotile(x, y, tileInd, *targetArray);
|
handleAutotile(x, y, tileInd, targetArray);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,19 +760,22 @@ struct TilemapPrivate
|
||||||
int tileX = tsInd % 8;
|
int tileX = tsInd % 8;
|
||||||
int tileY = tsInd / 8;
|
int tileY = tsInd / 8;
|
||||||
|
|
||||||
FloatRect texRect(tileX*32+.5, tileY*32+.5, 31, 31);
|
Vec2i texPos = TileAtlas::tileToAtlasCoor(tileX, tileY, tileset->height(), atlas.size.y);
|
||||||
|
FloatRect texRect((float) texPos.x+.5, (float) texPos.y+.5, 31, 31);
|
||||||
FloatRect posRect(x*32, y*32, 32, 32);
|
FloatRect posRect(x*32, y*32, 32, 32);
|
||||||
|
|
||||||
SVertex v[4];
|
SVertex v[4];
|
||||||
Quad::setTexPosRect(v, texRect, posRect);
|
Quad::setTexPosRect(v, texRect, posRect);
|
||||||
|
|
||||||
|
for (int k = 0; k < tiles.bufferCount; ++k)
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
targetArray->append(v[i]);
|
targetArray->v[k].append(v[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearQuadArrays()
|
void clearQuadArrays()
|
||||||
{
|
{
|
||||||
groundVert.clear();
|
for (int i = 0; i < 4; ++i)
|
||||||
|
groundVert.v[i].clear();
|
||||||
scanrowVert.clear();
|
scanrowVert.clear();
|
||||||
scanrowBases.clear();
|
scanrowBases.clear();
|
||||||
}
|
}
|
||||||
|
@ -667,8 +784,6 @@ struct TilemapPrivate
|
||||||
{
|
{
|
||||||
clearQuadArrays();
|
clearQuadArrays();
|
||||||
|
|
||||||
mapWidth = mapData->xSize();
|
|
||||||
mapHeight = mapData->ySize();
|
|
||||||
int mapDepth = mapData->zSize();
|
int mapDepth = mapData->zSize();
|
||||||
|
|
||||||
scanrowVert.resize(mapHeight + 5);
|
scanrowVert.resize(mapHeight + 5);
|
||||||
|
@ -695,40 +810,51 @@ struct TilemapPrivate
|
||||||
scanrowBases.resize(scanrowCount + 1);
|
scanrowBases.resize(scanrowCount + 1);
|
||||||
|
|
||||||
/* Calculate total quad count */
|
/* Calculate total quad count */
|
||||||
int groundQuadCount = groundVert.count() / 4;
|
int groundQuadCount = groundVert.v[0].count() / 4;
|
||||||
int quadCount = groundQuadCount;
|
int quadCount = groundQuadCount;
|
||||||
|
|
||||||
for (int i = 0; i < scanrowCount; ++i)
|
for (int i = 0; i < scanrowCount; ++i)
|
||||||
{
|
{
|
||||||
scanrowBases[i] = quadCount;
|
scanrowBases[i] = quadCount;
|
||||||
quadCount += scanrowVert[i].count() / 4;
|
quadCount += scanrowVert[i].v[0].count() / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
scanrowBases[scanrowCount] = quadCount;
|
scanrowBases[scanrowCount] = quadCount;
|
||||||
|
|
||||||
|
int bufferFrameQuadCount = quadCount;
|
||||||
|
tiles.bufferFrameSize = quadCount * 6 * sizeof(uint32_t);
|
||||||
|
|
||||||
|
quadCount *= tiles.bufferCount;
|
||||||
|
|
||||||
VBO::bind(tiles.vbo);
|
VBO::bind(tiles.vbo);
|
||||||
VBO::allocEmpty(quadDataSize(quadCount));
|
VBO::allocEmpty(quadDataSize(quadCount));
|
||||||
|
|
||||||
VBO::uploadSubData(0, quadDataSize(groundQuadCount), groundVert.constData());
|
for (int k = 0; k < tiles.bufferCount; ++k)
|
||||||
|
{
|
||||||
|
VBO::uploadSubData(k*quadDataSize(bufferFrameQuadCount),
|
||||||
|
quadDataSize(groundQuadCount), groundVert.v[k].constData());
|
||||||
|
|
||||||
for (int i = 0; i < scanrowCount; ++i)
|
for (int i = 0; i < scanrowCount; ++i)
|
||||||
{
|
{
|
||||||
VBO::uploadSubData(quadDataSize(scanrowBases[i]),
|
if (scanrowVert[i].v[0].empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
VBO::uploadSubData(k*quadDataSize(bufferFrameQuadCount) + quadDataSize(scanrowBases[i]),
|
||||||
quadDataSize(scanrowSize(i)),
|
quadDataSize(scanrowSize(i)),
|
||||||
scanrowVert[i].constData());
|
scanrowVert[i].v[k].constData());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VBO::unbind();
|
VBO::unbind();
|
||||||
|
|
||||||
/* Ensure global IBO size */
|
/* Ensure global IBO size */
|
||||||
gState->ensureQuadIBO(quadCount);
|
gState->ensureQuadIBO(quadCount*tiles.bufferCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindAtlas(SimpleShader &shader)
|
void bindAtlas(SimpleShader &shader)
|
||||||
{
|
{
|
||||||
TEX::bind(atlas.gl.tex);
|
TEX::bind(atlas.gl.tex);
|
||||||
shader.setTexSize(Vec2i(atlas.animated ? atlasFrameW * 4 : atlasFrameW, atlas.frameH));
|
shader.setTexSize(atlas.size);
|
||||||
shader.setTexOffsetX(atlas.frameIdx * atlasFrameW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2i getReplicaOffset(Position pos)
|
Vec2i getReplicaOffset(Position pos)
|
||||||
|
@ -839,7 +965,7 @@ struct TilemapPrivate
|
||||||
/* Only generate elements for non-emtpy scanrows */
|
/* Only generate elements for non-emtpy scanrows */
|
||||||
QVector<int> scanrowInd;
|
QVector<int> scanrowInd;
|
||||||
for (int i = 0; i < scanrowCount; ++i)
|
for (int i = 0; i < scanrowCount; ++i)
|
||||||
if (scanrowVert[i].count() > 0)
|
if (scanrowVert[i].v[0].count() > 0)
|
||||||
scanrowInd.append(i);
|
scanrowInd.append(i);
|
||||||
|
|
||||||
generateElements(scanrowInd);
|
generateElements(scanrowInd);
|
||||||
|
@ -963,7 +1089,7 @@ void GroundLayer::draw()
|
||||||
void GroundLayer::drawInt()
|
void GroundLayer::drawInt()
|
||||||
{
|
{
|
||||||
glDrawElements(GL_TRIANGLES, vboCount,
|
glDrawElements(GL_TRIANGLES, vboCount,
|
||||||
GL_UNSIGNED_INT, 0);
|
GL_UNSIGNED_INT, (GLvoid*) (p->tiles.frameIdx * p->tiles.bufferFrameSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroundLayer::drawFlashInt()
|
void GroundLayer::drawFlashInt()
|
||||||
|
@ -983,7 +1109,7 @@ ScanRow::ScanRow(TilemapPrivate *p, Viewport *viewport, int index)
|
||||||
index(index),
|
index(index),
|
||||||
p(p)
|
p(p)
|
||||||
{
|
{
|
||||||
vboOffset = p->scanrowBases[index] * sizeof(uint) * 6;
|
vboOffset = p->scanrowBases[index] * sizeof(uint32_t) * 6;
|
||||||
vboCount = p->scanrowSize(index) * 6;
|
vboCount = p->scanrowSize(index) * 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,7 +1143,7 @@ void ScanRow::draw()
|
||||||
void ScanRow::drawInt()
|
void ScanRow::drawInt()
|
||||||
{
|
{
|
||||||
glDrawElements(GL_TRIANGLES, vboCount,
|
glDrawElements(GL_TRIANGLES, vboCount,
|
||||||
GL_UNSIGNED_INT, (const GLvoid*) vboOffset);
|
GL_UNSIGNED_INT, (GLvoid*) (vboOffset + p->tiles.frameIdx * p->tiles.bufferFrameSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScanRow::updateZ()
|
void ScanRow::updateZ()
|
||||||
|
@ -1028,7 +1154,7 @@ void ScanRow::updateZ()
|
||||||
|
|
||||||
void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
|
void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
|
||||||
{
|
{
|
||||||
if (i < 0 || i > 6)
|
if (i < 0 || i > autotileCount-1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (p->autotiles[i] == bitmap)
|
if (p->autotiles[i] == bitmap)
|
||||||
|
@ -1045,11 +1171,13 @@ void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
|
||||||
p->autotilesDispCon[i].disconnect();
|
p->autotilesDispCon[i].disconnect();
|
||||||
p->autotilesDispCon[i] = bitmap->wasDisposed.connect
|
p->autotilesDispCon[i] = bitmap->wasDisposed.connect
|
||||||
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasContents));
|
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasContents));
|
||||||
|
|
||||||
|
p->updateAutotileInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap *Tilemap::Autotiles::get(int i) const
|
Bitmap *Tilemap::Autotiles::get(int i) const
|
||||||
{
|
{
|
||||||
if (i < 0 || i > 6)
|
if (i < 0 || i > autotileCount-1)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return p->autotiles[i];
|
return p->autotiles[i];
|
||||||
|
@ -1074,20 +1202,25 @@ static const uchar atAnimation[16*4] =
|
||||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static elementsN(atAnimation);
|
||||||
|
|
||||||
void Tilemap::update()
|
void Tilemap::update()
|
||||||
{
|
{
|
||||||
|
if (!p->tilemapReady)
|
||||||
|
return;
|
||||||
|
|
||||||
/* Animate flash */
|
/* Animate flash */
|
||||||
if (++p->flash.alphaIdx > flashAlphaN-1)
|
if (++p->flash.alphaIdx >= flashAlphaN)
|
||||||
p->flash.alphaIdx = 0;
|
p->flash.alphaIdx = 0;
|
||||||
|
|
||||||
/* Animate autotiles */
|
/* Animate autotiles */
|
||||||
if (!p->atlas.animated)
|
if (!p->tiles.animated)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
p->atlas.frameIdx = atAnimation[p->atlas.aniIdx];
|
p->tiles.frameIdx = atAnimation[p->tiles.aniIdx];
|
||||||
|
|
||||||
if (++p->atlas.aniIdx >= 16*4)
|
if (++p->tiles.aniIdx >= atAnimationN)
|
||||||
p->atlas.aniIdx = 0;
|
p->tiles.aniIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tilemap::Autotiles &Tilemap::getAutotiles() const
|
Tilemap::Autotiles &Tilemap::getAutotiles() const
|
||||||
|
@ -1137,6 +1270,8 @@ void Tilemap::setTileset(Bitmap *value)
|
||||||
p->tilesetCon.disconnect();
|
p->tilesetCon.disconnect();
|
||||||
p->tilesetCon = value->modified.connect
|
p->tilesetCon = value->modified.connect
|
||||||
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasSize));
|
(sigc::mem_fun(p, &TilemapPrivate::invalidateAtlasSize));
|
||||||
|
|
||||||
|
p->updateAtlasInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tilemap::setMapData(Table *value)
|
void Tilemap::setMapData(Table *value)
|
||||||
|
@ -1152,6 +1287,9 @@ void Tilemap::setMapData(Table *value)
|
||||||
p->mapDataCon.disconnect();
|
p->mapDataCon.disconnect();
|
||||||
p->mapDataCon = value->modified.connect
|
p->mapDataCon = value->modified.connect
|
||||||
(sigc::mem_fun(p, &TilemapPrivate::invalidateBuffers));
|
(sigc::mem_fun(p, &TilemapPrivate::invalidateBuffers));
|
||||||
|
|
||||||
|
|
||||||
|
p->updateMapDataInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tilemap::setFlashData(Table *value)
|
void Tilemap::setFlashData(Table *value)
|
||||||
|
|
|
@ -694,10 +694,10 @@ void Window::update()
|
||||||
|
|
||||||
#define DISP_CLASS_NAME "window"
|
#define DISP_CLASS_NAME "window"
|
||||||
|
|
||||||
DEF_ATTR_SIMPLE(Window, Windowskin, Bitmap*, p->windowskin)
|
|
||||||
DEF_ATTR_SIMPLE(Window, X, int, p->position.x)
|
DEF_ATTR_SIMPLE(Window, X, int, p->position.x)
|
||||||
DEF_ATTR_SIMPLE(Window, Y, int, p->position.y)
|
DEF_ATTR_SIMPLE(Window, Y, int, p->position.y)
|
||||||
|
|
||||||
|
DEF_ATTR_RD_SIMPLE(Window, Windowskin, Bitmap*, p->windowskin)
|
||||||
DEF_ATTR_RD_SIMPLE(Window, Contents, Bitmap*, p->contents)
|
DEF_ATTR_RD_SIMPLE(Window, Contents, Bitmap*, p->contents)
|
||||||
DEF_ATTR_RD_SIMPLE(Window, Stretch, bool, p->bgStretch)
|
DEF_ATTR_RD_SIMPLE(Window, Stretch, bool, p->bgStretch)
|
||||||
DEF_ATTR_RD_SIMPLE(Window, CursorRect, Rect*, p->cursorRect)
|
DEF_ATTR_RD_SIMPLE(Window, CursorRect, Rect*, p->cursorRect)
|
||||||
|
@ -711,9 +711,20 @@ DEF_ATTR_RD_SIMPLE(Window, Opacity, int, p->opacity)
|
||||||
DEF_ATTR_RD_SIMPLE(Window, BackOpacity, int, p->backOpacity)
|
DEF_ATTR_RD_SIMPLE(Window, BackOpacity, int, p->backOpacity)
|
||||||
DEF_ATTR_RD_SIMPLE(Window, ContentsOpacity, int, p->contentsOpacity)
|
DEF_ATTR_RD_SIMPLE(Window, ContentsOpacity, int, p->contentsOpacity)
|
||||||
|
|
||||||
|
void Window::setWindowskin(Bitmap *value)
|
||||||
|
{
|
||||||
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
value->ensureNonMega();
|
||||||
|
|
||||||
|
p->windowskin = value;
|
||||||
|
}
|
||||||
|
|
||||||
void Window::setContents(Bitmap *value)
|
void Window::setContents(Bitmap *value)
|
||||||
{
|
{
|
||||||
GUARD_DISPOSED
|
GUARD_DISPOSED;
|
||||||
|
|
||||||
|
value->ensureNonMega();
|
||||||
|
|
||||||
p->contents = value;
|
p->contents = value;
|
||||||
p->controlsVertDirty = true;
|
p->controlsVertDirty = true;
|
||||||
|
|
Loading…
Reference in New Issue