mkxp/src/tilemapvx.cpp

516 lines
10 KiB
C++
Raw Normal View History

/*
** tilemapvx.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2014 Jonas Kulla <Nyocurio@gmail.com>
**
** 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 <vector>
#include <sigc++/connection.h>
#include <sigc++/bind.h>
/* Map viewport size */
// FIXME: This will be wrong if resolution is changed
static const int viewpW = 18;
static const int viewpH = 14;
/* How many tiles are max visible on screen at once */
static const Vec2i screenTiles(18, 14);
// FIXME: Implement flash
struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
{
TilemapVX::BitmapArray bitmapsProxy;
Bitmap *bitmaps[BM_COUNT];
Table *mapData;
Table *flashData;
Table *flags;
Vec2i offset;
Vec2i dispPos;
/* Map viewport position */
Vec2i viewpPos;
Vec2i sceneOffset;
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;
uint8_t aniIdxA;
uint8_t aniIdxC;
Vec2 aniOffset;
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();
}
};
AboveLayer above;
TilemapVXPrivate(Viewport *viewport)
: ViewportElement(viewport),
mapData(0),
flashData(0),
flags(0),
allocQuads(0),
groundQuads(0),
aboveQuads(0),
frameIdx(0),
aniIdxA(0),
aniIdxC(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);
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 onBitmapDisposed(int i)
{
bitmaps[i] = 0;
bmChangedCons[i].disconnect();
bmDisposedCons[i].disconnect();
atlasDirty = true;
}
void invalidateBuffers()
{
buffersDirty = true;
}
void rebuildAtlas()
{
TileAtlasVX::build(atlas, bitmaps);
}
void updatePosition()
{
dispPos.x = -(offset.x - viewpPos.x * 32) + sceneOffset.x;
dispPos.y = -(offset.y - viewpPos.y * 32) + sceneOffset.y;
}
void onGeometryChange(const Scene::Geometry &geo)
{
sceneOffset.x = geo.rect.x - geo.xOrigin;
sceneOffset.y = geo.rect.y - geo.yOrigin;
sceneGeo = geo;
updatePosition();
}
void updateMapViewport()
{
int tileOX, tileOY;
if (offset.x >= 0)
tileOX = offset.x / 32;
else
tileOX = -(-(offset.x-31) / 32);
if (offset.y >= 0)
tileOY = offset.y / 32;
else
tileOY = -(-(offset.y-31) / 32);
bool dirty = false;
if (tileOX < viewpPos.x || tileOX + screenTiles.x > viewpPos.x + viewpW)
{
viewpPos.x = tileOX;
dirty = true;
}
if (tileOY < viewpPos.y || tileOY + screenTiles.y > viewpPos.y + viewpH)
{
viewpPos.y = tileOY;
dirty = true;
}
if (dirty)
{
buffersDirty = true;
}
updatePosition();
}
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,
viewpPos.x, viewpPos.y, viewpW, viewpH);
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), &groundVert[0]);
VBO::uploadSubData(quadBytes(groundQuads), quadBytes(aboveQuads), &aboveVert[0]);
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;
}
}
SVertex *allocVert(std::vector<SVertex> &vec, size_t count)
{
size_t size = vec.size();
vec.resize(size + count);
return &vec[size];
}
/* SceneElement */
void draw()
{
TilemapVXShader &shader = shState->shaders().tilemapVX;
shader.bind();
shader.setTexSize(Vec2i(atlas.width, atlas.height));
shader.applyViewportProj();
shader.setTranslation(dispPos);
shader.setAniOffset(aniOffset);
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);
}
/* TileAtlasVX::Reader */
void onQuads1(const FloatRect &t1, const FloatRect &p1,
bool overPlayer)
{
SVertex *vert;
if (overPlayer)
vert = allocVert(aboveVert, 4);
else
vert = allocVert(groundVert, 4);
Quad::setTexPosRect(vert, t1, p1);
}
void onQuads2(const FloatRect t[2], const FloatRect p[2])
{
SVertex *vert = allocVert(groundVert, 8);
Quad::setTexPosRect(&vert[0], t[0], p[0]);
Quad::setTexPosRect(&vert[4], t[1], p[1]);
}
void onQuads4(const FloatRect t[4], const FloatRect p[4])
{
SVertex *vert = allocVert(groundVert, 16);
Quad::setTexPosRect(&vert[ 0], t[0], p[0]);
Quad::setTexPosRect(&vert[ 4], t[1], p[1]);
Quad::setTexPosRect(&vert[ 8], t[2], p[2]);
Quad::setTexPosRect(&vert[12], t[3], p[3]);
}
};
void TilemapVX::BitmapArray::set(int i, Bitmap *bitmap)
{
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::bind(sigc::mem_fun(p, &TilemapVXPrivate::onBitmapDisposed), i));
}
Bitmap *TilemapVX::BitmapArray::get(int i) const
{
if (i < 0 || i >= BM_COUNT)
return 0;
return p->bitmaps[i];
}
TilemapVX::TilemapVX(Viewport *viewport)
{
(void) viewport;
p = new TilemapVXPrivate(viewport);
p->bitmapsProxy.p = p;
}
TilemapVX::~TilemapVX()
{
delete p;
}
void TilemapVX::update()
{
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 };
p->aniIdxA = aniIndicesA[p->frameIdx / 30];
p->aniIdxC = aniIndicesC[p->frameIdx / 30];
p->aniOffset = Vec2(p->aniIdxA * 2 * 32, p->aniIdxC * 32);
}
TilemapVX::BitmapArray &TilemapVX::getBitmapArray() const
{
return p->bitmapsProxy;
}
DEF_ATTR_RD_SIMPLE(TilemapVX, MapData, Table*, p->mapData)
DEF_ATTR_RD_SIMPLE(TilemapVX, FlashData, Table*, p->flashData)
DEF_ATTR_RD_SIMPLE(TilemapVX, Flags, Table*, p->flags)
DEF_ATTR_RD_SIMPLE(TilemapVX, OX, int, p->offset.x)
DEF_ATTR_RD_SIMPLE(TilemapVX, OY, int, p->offset.y)
Viewport *TilemapVX::getViewport() const
{
return p->getViewport();
}
bool TilemapVX::getVisible() const
{
return p->getVisible();
}
void TilemapVX::setViewport(Viewport *value)
{
p->setViewport(value);
p->above.setViewport(value);
}
void TilemapVX::setMapData(Table *value)
{
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)
{
if (p->flashData == value)
return;
p->flashData = value;
}
void TilemapVX::setFlags(Table *value)
{
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)
{
p->setVisible(value);
p->above.setVisible(value);
}
void TilemapVX::setOX(int value)
{
if (p->offset.x == value)
return;
p->offset.x = value;
p->mapViewportDirty = true;
}
void TilemapVX::setOY(int value)
{
if (p->offset.y == value)
return;
p->offset.y = value;
p->mapViewportDirty = true;
}