1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-11-25 17:53:47 +00:00
Aquaria/BBGE/Tile.cpp
fgenesis 368271c40e More work on tile rendering:
- Use indexed triangle rendering, no more GL_QUADS for tiles
- Fix aquarian text on maps
- Tile tex repeat mode works again

Known broken:
- Editor
- Tile repeat with grid effects
2023-08-09 02:41:04 +02:00

724 lines
14 KiB
C++

#include "Tile.h"
#include "RenderGrid.h"
#include "Tileset.h"
#include "Base.h"
#include <algorithm>
#include "Texture.h"
TileStorage::TileStorage()
{
}
TileStorage::~TileStorage()
{
destroyAll();
}
TileStorage::Sizes TileStorage::stats() const
{
Sizes sz;
sz.tiles = tiles.size();
sz.update = indicesToUpdate.size();
sz.collide = indicesToCollide.size();
return sz;
}
void TileStorage::moveToFront(const size_t *indices, size_t n)
{
if(n)
{
_moveToFront(indices, n);
refreshAll();
}
}
void TileStorage::moveToBack(const size_t *indices, size_t n)
{
if(n)
{
_moveToBack(indices, n);
refreshAll();
}
}
void TileStorage::update(float dt)
{
for(size_t i = 0; i < indicesToUpdate.size(); ++i)
{
TileData& t = tiles[indicesToUpdate[i]];
assert(t.flags & TILEFLAG_OWN_EFFDATA); // known to be set if this ends up on the list
t.eff->update(dt, &t);
}
}
void TileStorage::doInteraction(const Vector& pos, const Vector& vel, float mult, float touchWidth)
{
for(size_t i = 0; i < indicesToCollide.size(); ++i)
{
TileData& t = tiles[indicesToCollide[i]];
t.eff->doInteraction(t, pos, vel, mult, touchWidth);
}
}
void TileStorage::_moveToFront(const size_t *indices, size_t n)
{
// move tile to front -> move it to the back of the list, to be rendered last aka on top of everything else
if(n == 1)
{
TileData tile = tiles[*indices];
tiles.erase(tiles.begin() + *indices);
tiles.push_back(tile);
return;
}
_moveToPos(size(), indices, n);
}
void TileStorage::_moveToBack(const size_t *indices, size_t n)
{
// move tile to back -> move it to the front of the list, to be rendered first aka underneath everything else
if(n == 1)
{
TileData tile = tiles[*indices];
tiles.erase(tiles.begin() + *indices);
tiles.insert(tiles.begin(), tile);
return;
}
_moveToPos(0, indices, n);
}
void TileStorage::_moveToPos(size_t where, const size_t * indices, size_t n)
{
std::vector<size_t> tmp(indices, indices + n);
std::sort(tmp.begin(), tmp.end());
std::vector<TileData> tt(n);
// sorted indices -> preserve relative order of tiles
for(size_t i = 0; i < n; ++i)
tt[i] = tiles[tmp[i]];
// SORTED indices, erasing from the BACK -> we don't get a destructive index shift
for(size_t i = tmp.size(); i --> 0; )
tiles.erase(tiles.begin() + tmp[i]);
tiles.insert(tiles.begin() + where, tt.begin(), tt.end());
}
size_t TileStorage::moveToOther(TileStorage& other, const size_t *indices, size_t n)
{
const size_t firstNewIdx = other.tiles.size();
for(size_t i = 0; i < n; ++i)
other.tiles.push_back(tiles[indices[i]]);
std::vector<TileData> tmp;
tmp.swap(tiles);
tiles.reserve(tmp.size() - n);
for(size_t i = 0; i < tmp.size(); ++i)
{
for(size_t k = 0; k < n; ++i) // not particularly efficient, could be much better by sorting first but eh
if(indices[k] == i)
goto skip;
tiles.push_back(tmp[i]);
skip: ;
}
refreshAll();
other.refreshAll();
return firstNewIdx;
}
static void dropEffect(TileData& t)
{
if(t.flags & TILEFLAG_OWN_EFFDATA)
{
delete t.eff;
t.flags &= ~TILEFLAG_OWN_EFFDATA;
}
t.eff = NULL;
}
static void dropRepeat(TileData& t)
{
if(t.rep)
{
delete t.rep;
t.rep = NULL;
}
}
static void dropAll(TileData& t)
{
dropEffect(t);
dropRepeat(t);
}
void TileStorage::deleteSome(const size_t* indices, size_t n)
{
std::vector<TileData> tmp;
tmp.swap(tiles);
tiles.reserve(tmp.size() - n);
for(size_t i = 0; i < tmp.size(); ++i)
{
for(size_t k = 0; k < n; ++i) // not particularly efficient, could be much better by sorting first but eh
if(indices[k] == i)
{
dropAll(tmp[i]);
goto skip;
}
tiles.push_back(tmp[i]);
skip: ;
}
refreshAll();
}
void TileStorage::destroyAll()
{
const size_t n = tiles.size();
for(size_t i = 0; i < n; ++i)
dropAll(tiles[i]);
tiles.clear();
indicesToCollide.clear();
indicesToUpdate.clear();
}
void TileStorage::setTag(unsigned tag, const size_t* indices, size_t n)
{
for(size_t i = 0; i < n; ++i)
tiles[indices[i]].tag = tag;
// don't need to refresh here
}
void TileStorage::setEffect(const TileEffectStorage& effstore, int idx, const size_t* indices, size_t n)
{
for(size_t i = 0; i < n; ++i)
effstore.assignEffect(tiles[indices[i]], idx);
refreshAll();
}
void TileStorage::changeFlags(unsigned flagsToSet, unsigned flagsToUnset, const size_t* indices, size_t n)
{
for(size_t i = 0; i < n; ++i)
{
unsigned& f = tiles[indices[i]].flags;
unsigned tmp = f & ~flagsToUnset;
f = tmp | flagsToSet;
}
}
size_t TileStorage::cloneSome(const TileEffectStorage& effstore, const size_t* indices, size_t n)
{
const size_t ret = tiles.size(); // new starting index of clone tiles
// cloning tiles is very simple, but owned pointers will be duplicated and need to be fixed up
const size_t N = ret + n;
tiles.resize(N);
for(size_t i = 0; i < n; ++i)
tiles[ret + i] = tiles[indices[i]];
// cleanup pointers
for(size_t i = ret; i < N; ++i) // loop only over newly added tiles
{
TileData& t = tiles[i];
if(t.rep)
{
t.rep = new TileRepeatData(*t.rep); // must be done BEFORE assigning eff
}
if((t.flags & TILEFLAG_OWN_EFFDATA) && t.eff)
{
int efx = t.eff->efxidx;
t.eff = NULL; // not our pointer, just pretend it was never there
t.flags &= TILEFLAG_OWN_EFFDATA;
effstore.assignEffect(t, efx); // recreate effect properly
}
}
refreshAll();
return ret;
}
void TileStorage::refreshAll()
{
indicesToCollide.clear();
indicesToUpdate.clear();
const size_t n = tiles.size();
for(size_t i = 0; i < n; ++i)
{
TileData& t = tiles[i];
t.refreshRepeat();
if(!(t.flags & TILEFLAG_HIDDEN))
{
if(const TileEffectData *e = t.eff)
{
if(t.flags & TILEFLAG_OWN_EFFDATA)
{
indicesToUpdate.push_back(i);
if(e->efxtype == EFX_WAVY)
indicesToCollide.push_back(i);
}
}
}
}
}
void TileStorage::clearSelection()
{
const size_t n = tiles.size();
for(size_t i = 0; i < n; ++i)
tiles[i].flags &= ~TILEFLAG_SELECTED;
}
TileEffectData::TileEffectData(const TileEffectConfig& cfg, const TileData *t)
: efxtype(cfg.type), efxidx(cfg.index)
, grid(NULL), alpha(1), blend(BLEND_DEFAULT)
, ownGrid(false), shared(false)
{
switch(cfg.type)
{
case EFX_NONE:
assert(false);
break;
case EFX_WAVY:
{
assert(t);
float bity = t->et->h/float(cfg.u.wavy.segsy);
wavy.wavy.resize(cfg.u.wavy.segsy, 0.0f);
wavy.flip = cfg.u.wavy.flip;
wavy.min = bity;
wavy.max = bity*1.2f;
DynamicRenderGrid *g = _ensureGrid(2, cfg.u.wavy.segsy, t);
g->gridType = GRID_UNDEFINED; // we do the grid update manually
wavy.angleOffset = 0;
wavy.magnitude = 0;
wavy.lerpIn = 0;
wavy.hitPerc = 0;
wavy.effectMult = 0;
wavy.waving = false;
wavy.flip = false;
wavy.touching = false;
}
break;
case EFX_SEGS:
{
DynamicRenderGrid *g = _ensureGrid(cfg.u.segs.x, cfg.u.segs.y, t);
g->setSegs(cfg.u.segs.dgox, cfg.u.segs.dgoy, cfg.u.segs.dgmx, cfg.u.segs.dgmy, cfg.u.segs.dgtm, cfg.u.segs.dgo);
}
break;
case EFX_ALPHA:
{
alpha.x = cfg.u.alpha.val0;
alpha.interpolateTo(cfg.u.alpha.val1, cfg.u.alpha.time, -1, cfg.u.alpha.pingpong, cfg.u.alpha.ease);
blend = cfg.u.alpha.blend;
}
break;
}
}
TileEffectData::~TileEffectData()
{
if(ownGrid)
delete grid;
}
DynamicRenderGrid *TileEffectData::_ensureGrid(size_t w, size_t h, const TileData *t)
{
DynamicRenderGrid *g = grid;
if(ownGrid)
{
assert(g);
return g;
}
if(t && t->rep)
{
assert(!shared); // a shared instance MUST have its own grid and MUST NOT refer to the grid of any tile
if(ownGrid)
delete g;
g = &t->rep->grid;
ownGrid = false;
}
if(!g)
{
g = new DynamicRenderGrid;
ownGrid = true;
}
grid = g;
TexCoordBox tc;
if(t)
tc = t->getTexcoords();
else
tc.setStandard();
g->init(w, h, tc);
if(t && t->rep)
t->rep->refresh(*t);
return g;
}
void TileEffectData::Wavy::update(float dt)
{
if (touching)
{
touching = false;
float ramp = touchVel.getLength2D()/800.0f;
if (ramp < 0) ramp = 0;
if (ramp > 1) ramp = 1;
magnitude = 100 * ramp + 16;
if (touchVel.x < 0)
magnitude = -magnitude;
angleOffset = (hitPerc-0.5f)*PI;
wavySave = wavy;
lerpIn = 0;
}
if (waving)
{
// TODO: set waving=false if magnitude==0 ?
float spd = PI*1.1f;
float magRedSpd = 48;
float lerpSpd = 5.0;
float wavySz = float(wavy.size());
for (size_t i = 0; i < wavy.size(); i++)
{
float weight = float(i)/wavySz;
if (flip)
weight = 1.0f-weight;
if (weight < 0.125f)
weight *= 0.5f;
wavy[i] = sinf(angleOffset + (float(i)/wavySz)*PI)*(magnitude*effectMult)*weight;
if (!wavySave.empty())
{
if (lerpIn < 1)
wavy[i] = wavy[i] * lerpIn + (wavySave[i] * (1.0f-lerpIn));
}
}
if (lerpIn < 1)
{
lerpIn += dt*lerpSpd;
if (lerpIn > 1)
lerpIn = 1;
}
angleOffset += dt*spd;
if (magnitude > 0)
{
magnitude -= magRedSpd*dt;
if (magnitude < 0)
magnitude = 0;
}
else
{
magnitude += magRedSpd*dt;
if (magnitude > 0)
magnitude = 0;
}
}
}
void TileEffectData::update(float dt, const TileData *t)
{
switch(efxtype)
{
case EFX_WAVY:
wavy.update(dt);
if(const size_t N = wavy.wavy.size())
grid->setFromWavy(&wavy.wavy[0], N, t->et->w);
break;
case EFX_SEGS:
grid->update(dt);
break;
case EFX_ALPHA:
alpha.update(dt);
break;
}
}
void TileEffectData::doInteraction(const TileData& t, const Vector& pos, const Vector& vel, float mult, float touchWidth)
{
assert(efxtype == EFX_WAVY);
const Vector tp(t.x, t.y);
if (pos.x > tp.x-touchWidth && pos.x < tp.x+touchWidth)
{
float h = t.et->h*t.scaley;
float h2 = h * 0.5f;
if (pos.y < tp.y+h2 && pos.y > tp.y-h2)
{
wavy.touching = true;
wavy.waving = true;
float hitPerc = tp.y - h2 - pos.y;
hitPerc /= h;
hitPerc = (1.0f-hitPerc)-1.0f;
wavy.hitPerc = hitPerc;
wavy.touchVel = vel;
wavy.effectMult = mult;
}
}
}
TileEffectStorage::TileEffectStorage()
{
}
TileEffectStorage::~TileEffectStorage()
{
clear();
}
void TileEffectStorage::assignEffect(TileData& t, int index) const
{
dropEffect(t);
if(index < 0)
return;
size_t idx = size_t(index);
if(idx >= configs.size())
return;
bool needinstance = false;
if(idx < configs.size())
{
needinstance = configs[idx].needsOwnInstanceForTile(t);
}
if(needinstance)
{
if(configs[idx].type == EFX_NONE)
return;
t.eff = new TileEffectData(configs[idx], &t);
t.flags |= TILEFLAG_OWN_EFFDATA;
}
else if(idx < prepared.size() && prepared[idx])
{
t.eff = prepared[idx];
}
}
void TileEffectStorage::update(float dt)
{
for(size_t i = 0; i < prepared.size(); ++i)
if(TileEffectData *eff = prepared[i])
eff->update(dt, NULL);
}
void TileEffectStorage::clear()
{
clearPrepared();
configs.clear();
}
void TileEffectStorage::clearPrepared()
{
for(size_t i = 0; i < prepared.size(); ++i)
delete prepared[i];
prepared.clear();
}
void TileEffectStorage::finalize()
{
clearPrepared();
prepared.resize(configs.size(), (TileEffectData*)NULL);
for(size_t i = 0; i < configs.size(); ++i)
{
TileEffectConfig& c = configs[i];
c.index = unsigned(i); // just in case
// segs and alpha are independent of the tile they are applied to,
// so we can create shared instances of the effect.
if(c.type == EFX_SEGS || c.type == EFX_ALPHA)
{
prepared[i] = new TileEffectData(c, NULL);
prepared[i]->shared = true;
}
}
}
bool TileData::isCoordinateInside(float cx, float cy, float minsize) const
{
float hw = fabsf(et->w * scalex)*0.5f;
float hh = fabsf(et->h * scaley)*0.5f;
if (hw < minsize)
hw = minsize;
if (hh < minsize)
hh = minsize;
return cx >= x - hw && cx <= x + hw
&& cy >= y - hh && cy <= y + hh;
}
TileRepeatData::TileRepeatData()
: texscaleX(1), texscaleY(1)
, texOffX(0), texOffY(0)
{
}
TileRepeatData::TileRepeatData(const TileRepeatData& o)
: texscaleX(o.texscaleX), texscaleY(o.texscaleY)
, texOffX(o.texOffX), texOffY(o.texOffY)
{
}
TexCoordBox TileRepeatData::calcTexCoords(const TileData& t) const
{
const ElementTemplate& et = *t.et;
float tw, th;
if(et.tex)
{
tw = et.tex->width;
th = et.tex->height;
}
else
{
tw = et.w;
th = et.h;
}
TexCoordBox tc;
tc.u1 = texOffX;
tc.v1 = texOffY;
tc.u2 = (et.w*t.scalex*texscaleX)/tw + texOffX;
tc.v2 = (et.h*t.scaley*texscaleY)/th + texOffY;
// HACK: partially repeated textures have a weird Y axis. assuming a repeat factor of 0.5,
// instead of texcoords from 0 -> 0.4 everything is biased towards the opposite end, ie. 0.6 -> 1.
// This is especially true for partial repeats, we always need to bias towards the other end.
// I have no idea why this has to be like this for tiles, but this is NOT the case for fonts.
// And NOTE: without this, maps may look deceivingly correct, but they really are not.
const float percentY = tc.v2 - tc.v1;
const float remainder = 1.0f - fmodf(percentY, 1.0f);
tc.v1 += remainder; // bias towards next int
tc.v2 += remainder;
return tc;
}
void TileRepeatData::refresh(const TileData& t)
{
TexCoordBox tc = calcTexCoords(t);
/*if(t.eff)
if(const DynamicRenderGrid *g = t.eff->grid)
{
grid.init(g->width(), g->height(), grid.getTexCoords());
grid.gridType = g->gridType;
}*/
if(grid.empty())
grid.init(2, 2, tc);
else
{
grid.setTexCoords(tc);
grid.reset();
grid.updateVBO();
}
}
TileRepeatData* TileData::setRepeatOn(float texscalex, float texscaley, float offx, float offy)
{
flags |= TILEFLAG_REPEAT;
if(!rep)
rep = new TileRepeatData;
rep->texscaleX = texscalex;
rep->texscaleY = texscaley;
rep->texOffX = offx;
rep->texOffY = offy;
rep->refresh(*this);
// FIXME: if eff, link eff->grid to rep->grid
return rep;
}
void TileData::setRepeatOff()
{
flags &= ~TILEFLAG_REPEAT;
// don't delete this->rep; if we're in editor mode we don't want to lose the repeat data just yet
// also, a TileEffectData may point to rep->grid
}
void TileData::refreshRepeat()
{
if(rep)
{
rep->refresh(*this);
}
}
bool TileData::hasStandardTexcoords() const
{
// repeat applies per-tile texcoords, so if that's set it's non-standard
return !rep && et->tc.isStandard();
}
const TexCoordBox& TileData::getTexcoords() const
{
return !(flags & TILEFLAG_REPEAT)
? et->tc
: rep->grid.getTexCoords();
}
const RenderGrid *TileData::getGrid() const
{
if(flags & TILEFLAG_REPEAT)
return &rep->getGrid();
if(eff && eff->grid)
return eff->grid;
return et->grid;
}
bool TileEffectConfig::needsOwnInstanceForTile(const TileData& t) const
{
const bool rep = !!(t.flags & TILEFLAG_REPEAT);
switch(type)
{
case EFX_NONE:
case EFX_ALPHA:
return false;
case EFX_WAVY:
return true;
case EFX_SEGS:
return rep || !t.hasStandardTexcoords();
}
assert(false);
return true; // uhhhh
}