545 lines
10 KiB
C++
545 lines
10 KiB
C++
/*
|
|
** tilemapvx.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2014 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
|
|
**
|
|
** mkxp 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 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp 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 mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "tilemapvx.h"
|
|
|
|
#include "tileatlasvx.h"
|
|
#include "etc-internal.h"
|
|
#include "bitmap.h"
|
|
#include "table.h"
|
|
#include "viewport.h"
|
|
#include "gl-util.h"
|
|
#include "sharedstate.h"
|
|
#include "glstate.h"
|
|
#include "vertex.h"
|
|
#include "quad.h"
|
|
#include "quadarray.h"
|
|
#include "shader.h"
|
|
#include "tilemap-common.h"
|
|
|
|
#include <vector>
|
|
#include <sigc++/connection.h>
|
|
|
|
/* Flash tiles pulsing opacity */
|
|
static const uint8_t flashAlpha[] =
|
|
{
|
|
/* Fade in */
|
|
0x78, 0x78, 0x78, 0x78, 0x96, 0x96, 0x96, 0x96,
|
|
0xB4, 0xB4, 0xB4, 0xB4, 0xD2, 0xD2, 0xD2, 0xD2,
|
|
/* Fade out */
|
|
0xF0, 0xF0, 0xF0, 0xF0, 0xD2, 0xD2, 0xD2, 0xD2,
|
|
0xB4, 0xB4, 0xB4, 0xB4, 0x96, 0x96, 0x96, 0x96
|
|
};
|
|
|
|
static elementsN(flashAlpha);
|
|
|
|
struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
|
|
{
|
|
Bitmap *bitmaps[BM_COUNT];
|
|
|
|
Table *mapData;
|
|
Table *flags;
|
|
Vec2i origin;
|
|
|
|
/* Subregion of the map that is drawn to screen (map viewport) */
|
|
IntRect mapViewp;
|
|
/* Position on screen the map subregion is drawn at */
|
|
Vec2i dispPos;
|
|
Scene::Geometry sceneGeo;
|
|
|
|
std::vector<SVertex> groundVert;
|
|
std::vector<SVertex> aboveVert;
|
|
|
|
TEXFBO atlas;
|
|
VBO::ID vbo;
|
|
GLMeta::VAO vao;
|
|
|
|
size_t allocQuads;
|
|
|
|
size_t groundQuads;
|
|
size_t aboveQuads;
|
|
|
|
uint16_t frameIdx;
|
|
Vec2 aniOffset;
|
|
|
|
FlashMap flashMap;
|
|
uint8_t flashAlphaIdx;
|
|
|
|
bool atlasDirty;
|
|
bool buffersDirty;
|
|
bool mapViewportDirty;
|
|
|
|
sigc::connection mapDataCon;
|
|
sigc::connection flagsCon;
|
|
|
|
sigc::connection prepareCon;
|
|
sigc::connection bmChangedCons[BM_COUNT];
|
|
sigc::connection bmDisposedCons[BM_COUNT];
|
|
|
|
struct AboveLayer : public ViewportElement
|
|
{
|
|
TilemapVXPrivate *p;
|
|
|
|
AboveLayer(TilemapVXPrivate *p, Viewport *viewport)
|
|
: ViewportElement(viewport, 200),
|
|
p(p)
|
|
{}
|
|
|
|
void draw()
|
|
{
|
|
p->drawAbove();
|
|
p->drawFlashLayer();
|
|
}
|
|
|
|
ABOUT_TO_ACCESS_NOOP
|
|
};
|
|
|
|
AboveLayer above;
|
|
|
|
TilemapVXPrivate(Viewport *viewport)
|
|
: ViewportElement(viewport),
|
|
mapData(0),
|
|
flags(0),
|
|
allocQuads(0),
|
|
groundQuads(0),
|
|
aboveQuads(0),
|
|
frameIdx(0),
|
|
flashAlphaIdx(0),
|
|
atlasDirty(true),
|
|
buffersDirty(false),
|
|
mapViewportDirty(false),
|
|
above(this, viewport)
|
|
{
|
|
memset(bitmaps, 0, sizeof(bitmaps));
|
|
|
|
shState->requestAtlasTex(ATLASVX_W, ATLASVX_H, atlas);
|
|
|
|
vbo = VBO::gen();
|
|
|
|
GLMeta::vaoFillInVertexData<SVertex>(vao);
|
|
vao.vbo = vbo;
|
|
vao.ibo = shState->globalIBO().ibo;
|
|
GLMeta::vaoInit(vao);
|
|
|
|
onGeometryChange(scene->getGeometry());
|
|
|
|
prepareCon = shState->prepareDraw.connect
|
|
(sigc::mem_fun(this, &TilemapVXPrivate::prepare));
|
|
}
|
|
|
|
virtual ~TilemapVXPrivate()
|
|
{
|
|
GLMeta::vaoFini(vao);
|
|
VBO::del(vbo);
|
|
|
|
shState->releaseAtlasTex(atlas);
|
|
|
|
prepareCon.disconnect();
|
|
|
|
mapDataCon.disconnect();
|
|
flagsCon.disconnect();
|
|
|
|
for (size_t i = 0; i < BM_COUNT; ++i)
|
|
{
|
|
bmChangedCons[i].disconnect();
|
|
bmDisposedCons[i].disconnect();
|
|
}
|
|
}
|
|
|
|
void invalidateAtlas()
|
|
{
|
|
atlasDirty = true;
|
|
}
|
|
|
|
void invalidateBuffers()
|
|
{
|
|
buffersDirty = true;
|
|
}
|
|
|
|
void rebuildAtlas()
|
|
{
|
|
TileAtlasVX::build(atlas, bitmaps);
|
|
}
|
|
|
|
void updateMapViewport()
|
|
{
|
|
/* Note: We include one extra row at the top above
|
|
* the normal map viewport to ensure the legs of table
|
|
* tiles off screen are properly drawn */
|
|
|
|
IntRect newMvp;
|
|
|
|
const Vec2i combOrigin = origin + sceneGeo.orig;
|
|
const Vec2i geoSize = sceneGeo.rect.size();
|
|
|
|
newMvp.setPos(getTilePos(combOrigin) - Vec2i(0, 1));
|
|
|
|
/* Ensure that the size is big enough to cover the whole viewport,
|
|
* and add one tile row/column as a buffer for scrolling */
|
|
newMvp.setSize((geoSize / 32) + !!(geoSize % 32) + Vec2i(1, 2));
|
|
|
|
if (newMvp != mapViewp)
|
|
{
|
|
mapViewp = newMvp;
|
|
flashMap.setViewport(newMvp);
|
|
buffersDirty = true;
|
|
}
|
|
|
|
dispPos = sceneGeo.rect.pos() - wrap(combOrigin, 32) - Vec2i(0, 32);
|
|
}
|
|
|
|
static size_t quadBytes(size_t quads)
|
|
{
|
|
return quads * 4 * sizeof(SVertex);
|
|
}
|
|
|
|
void rebuildBuffers()
|
|
{
|
|
if (!mapData)
|
|
return;
|
|
|
|
groundVert.clear();
|
|
aboveVert.clear();
|
|
|
|
TileAtlasVX::readTiles(*this, *mapData, flags,
|
|
mapViewp.x, mapViewp.y, mapViewp.w, mapViewp.h);
|
|
|
|
groundQuads = groundVert.size() / 4;
|
|
aboveQuads = aboveVert.size() / 4;
|
|
size_t totalQuads = groundQuads + aboveQuads;
|
|
|
|
VBO::bind(vbo);
|
|
|
|
if (totalQuads > allocQuads)
|
|
{
|
|
VBO::allocEmpty(quadBytes(totalQuads), GL_DYNAMIC_DRAW);
|
|
allocQuads = totalQuads;
|
|
}
|
|
|
|
VBO::uploadSubData(0, quadBytes(groundQuads), dataPtr(groundVert));
|
|
VBO::uploadSubData(quadBytes(groundQuads), quadBytes(aboveQuads), dataPtr(aboveVert));
|
|
|
|
VBO::unbind();
|
|
|
|
shState->ensureQuadIBO(totalQuads);
|
|
}
|
|
|
|
void prepare()
|
|
{
|
|
if (!mapData)
|
|
return;
|
|
|
|
if (atlasDirty)
|
|
{
|
|
rebuildAtlas();
|
|
atlasDirty = false;
|
|
}
|
|
|
|
if (mapViewportDirty)
|
|
{
|
|
updateMapViewport();
|
|
mapViewportDirty = false;
|
|
}
|
|
|
|
if (buffersDirty)
|
|
{
|
|
rebuildBuffers();
|
|
buffersDirty = false;
|
|
}
|
|
|
|
flashMap.prepare();
|
|
}
|
|
|
|
SVertex *allocVert(std::vector<SVertex> &vec, size_t count)
|
|
{
|
|
size_t size = vec.size();
|
|
vec.resize(size + count);
|
|
|
|
return &vec[size];
|
|
}
|
|
|
|
/* SceneElement */
|
|
void draw()
|
|
{
|
|
drawGround();
|
|
drawFlashLayer();
|
|
}
|
|
|
|
void drawGround()
|
|
{
|
|
if (groundQuads == 0)
|
|
return;
|
|
|
|
ShaderBase *shader;
|
|
|
|
if (!nullOrDisposed(bitmaps[BM_A1]))
|
|
{
|
|
/* Animated tileset */
|
|
TilemapVXShader &tmShader = shState->shaders().tilemapVX;
|
|
tmShader.bind();
|
|
tmShader.setAniOffset(aniOffset);
|
|
|
|
shader = &tmShader;
|
|
}
|
|
else
|
|
{
|
|
/* Static tileset */
|
|
shader = &shState->shaders().simple;
|
|
shader->bind();
|
|
}
|
|
|
|
shader->setTexSize(Vec2i(atlas.width, atlas.height));
|
|
shader->applyViewportProj();
|
|
shader->setTranslation(dispPos);
|
|
|
|
TEX::bind(atlas.tex);
|
|
GLMeta::vaoBind(vao);
|
|
|
|
gl.DrawElements(GL_TRIANGLES, groundQuads*6, _GL_INDEX_TYPE, 0);
|
|
|
|
GLMeta::vaoUnbind(vao);
|
|
}
|
|
|
|
void drawAbove()
|
|
{
|
|
if (aboveQuads == 0)
|
|
return;
|
|
|
|
SimpleShader &shader = shState->shaders().simple;
|
|
shader.bind();
|
|
shader.setTexSize(Vec2i(atlas.width, atlas.height));
|
|
shader.applyViewportProj();
|
|
shader.setTranslation(dispPos);
|
|
|
|
TEX::bind(atlas.tex);
|
|
GLMeta::vaoBind(vao);
|
|
|
|
gl.DrawElements(GL_TRIANGLES, aboveQuads*6, _GL_INDEX_TYPE,
|
|
(GLvoid*) (groundQuads*6*sizeof(index_t)));
|
|
|
|
GLMeta::vaoUnbind(vao);
|
|
}
|
|
|
|
void drawFlashLayer()
|
|
{
|
|
/* Flash tiles are drawn twice at half opacity, once over the
|
|
* ground layer, and once over the above layer */
|
|
float alpha = (flashAlpha[flashAlphaIdx] / 255.f) / 2;
|
|
flashMap.draw(alpha, dispPos);
|
|
}
|
|
|
|
void onGeometryChange(const Scene::Geometry &geo)
|
|
{
|
|
sceneGeo = geo;
|
|
|
|
buffersDirty = true;
|
|
mapViewportDirty = true;
|
|
}
|
|
|
|
ABOUT_TO_ACCESS_NOOP
|
|
|
|
/* TileAtlasVX::Reader */
|
|
void onQuads(const FloatRect *t, const FloatRect *p,
|
|
size_t n, bool overPlayer)
|
|
{
|
|
SVertex *vert = allocVert(overPlayer ? aboveVert : groundVert, n*4);
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
Quad::setTexPosRect(&vert[i*4], t[i], p[i]);
|
|
}
|
|
};
|
|
|
|
void TilemapVX::BitmapArray::set(int i, Bitmap *bitmap)
|
|
{
|
|
if (!p)
|
|
return;
|
|
|
|
if (i < 0 || i >= BM_COUNT)
|
|
return;
|
|
|
|
if (p->bitmaps[i] == bitmap)
|
|
return;
|
|
|
|
p->bitmaps[i] = bitmap;
|
|
p->atlasDirty = true;
|
|
|
|
p->bmChangedCons[i].disconnect();
|
|
p->bmChangedCons[i] = bitmap->modified.connect
|
|
(sigc::mem_fun(p, &TilemapVXPrivate::invalidateAtlas));
|
|
|
|
p->bmDisposedCons[i].disconnect();
|
|
p->bmDisposedCons[i] = bitmap->wasDisposed.connect
|
|
(sigc::mem_fun(p, &TilemapVXPrivate::invalidateAtlas));
|
|
}
|
|
|
|
Bitmap *TilemapVX::BitmapArray::get(int i) const
|
|
{
|
|
if (!p)
|
|
return 0;
|
|
|
|
if (i < 0 || i >= BM_COUNT)
|
|
return 0;
|
|
|
|
return p->bitmaps[i];
|
|
}
|
|
|
|
TilemapVX::TilemapVX(Viewport *viewport)
|
|
{
|
|
p = new TilemapVXPrivate(viewport);
|
|
bmProxy.p = p;
|
|
}
|
|
|
|
TilemapVX::~TilemapVX()
|
|
{
|
|
dispose();
|
|
}
|
|
|
|
void TilemapVX::update()
|
|
{
|
|
guardDisposed();
|
|
|
|
/* Animate tiles */
|
|
if (++p->frameIdx >= 30*3*4)
|
|
p->frameIdx = 0;
|
|
|
|
const uint8_t aniIndicesA[3*4] =
|
|
{ 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1 };
|
|
const uint8_t aniIndicesC[3*4] =
|
|
{ 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 };
|
|
|
|
uint8_t aniIdxA = aniIndicesA[p->frameIdx / 30];
|
|
uint8_t aniIdxC = aniIndicesC[p->frameIdx / 30];
|
|
|
|
p->aniOffset = Vec2(aniIdxA * 2 * 32, aniIdxC * 32);
|
|
|
|
/* Animate flash */
|
|
if (++p->flashAlphaIdx >= flashAlphaN)
|
|
p->flashAlphaIdx = 0;
|
|
}
|
|
|
|
TilemapVX::BitmapArray &TilemapVX::getBitmapArray()
|
|
{
|
|
guardDisposed();
|
|
|
|
return bmProxy;
|
|
}
|
|
|
|
DEF_ATTR_RD_SIMPLE(TilemapVX, MapData, Table*, p->mapData)
|
|
DEF_ATTR_RD_SIMPLE(TilemapVX, FlashData, Table*, p->flashMap.getData())
|
|
DEF_ATTR_RD_SIMPLE(TilemapVX, Flags, Table*, p->flags)
|
|
DEF_ATTR_RD_SIMPLE(TilemapVX, OX, int, p->origin.x)
|
|
DEF_ATTR_RD_SIMPLE(TilemapVX, OY, int, p->origin.y)
|
|
|
|
Viewport *TilemapVX::getViewport() const
|
|
{
|
|
guardDisposed();
|
|
|
|
return p->getViewport();
|
|
}
|
|
|
|
bool TilemapVX::getVisible() const
|
|
{
|
|
guardDisposed();
|
|
|
|
return p->getVisible();
|
|
}
|
|
|
|
void TilemapVX::setViewport(Viewport *value)
|
|
{
|
|
guardDisposed();
|
|
|
|
p->setViewport(value);
|
|
p->above.setViewport(value);
|
|
}
|
|
|
|
void TilemapVX::setMapData(Table *value)
|
|
{
|
|
guardDisposed();
|
|
|
|
if (p->mapData == value)
|
|
return;
|
|
|
|
p->mapData = value;
|
|
p->buffersDirty = true;
|
|
|
|
p->mapDataCon.disconnect();
|
|
p->mapDataCon = value->modified.connect
|
|
(sigc::mem_fun(p, &TilemapVXPrivate::invalidateBuffers));
|
|
}
|
|
|
|
void TilemapVX::setFlashData(Table *value)
|
|
{
|
|
guardDisposed();
|
|
|
|
p->flashMap.setData(value);
|
|
}
|
|
|
|
void TilemapVX::setFlags(Table *value)
|
|
{
|
|
guardDisposed();
|
|
|
|
if (p->flags == value)
|
|
return;
|
|
|
|
p->flags = value;
|
|
p->buffersDirty = true;
|
|
|
|
p->flagsCon.disconnect();
|
|
p->flagsCon = value->modified.connect
|
|
(sigc::mem_fun(p, &TilemapVXPrivate::invalidateBuffers));
|
|
}
|
|
|
|
void TilemapVX::setVisible(bool value)
|
|
{
|
|
guardDisposed();
|
|
|
|
p->setVisible(value);
|
|
p->above.setVisible(value);
|
|
}
|
|
|
|
void TilemapVX::setOX(int value)
|
|
{
|
|
guardDisposed();
|
|
|
|
if (p->origin.x == value)
|
|
return;
|
|
|
|
p->origin.x = value;
|
|
p->mapViewportDirty = true;
|
|
}
|
|
|
|
void TilemapVX::setOY(int value)
|
|
{
|
|
guardDisposed();
|
|
|
|
if (p->origin.y == value)
|
|
return;
|
|
|
|
p->origin.y = value;
|
|
p->mapViewportDirty = true;
|
|
}
|
|
|
|
void TilemapVX::releaseResources()
|
|
{
|
|
delete p;
|
|
bmProxy.p = 0;
|
|
}
|