516 lines
10 KiB
C++
516 lines
10 KiB
C++
|
/*
|
||
|
** 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;
|
||
|
}
|