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 f1796475f1 undo parts of prev commits; move old fudge from font atlas code to tile rendering.
this should now be correct and much simpler to maintain from now on.
downside is that it breaks the appearance of quads with tex repeat on,
but i don't think anyone ever used this ever since i've added the Lua functions;
so i'm just calling it broken in older versions and move on.
2023-10-20 02:06:45 +02:00

757 lines
15 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; ++k) // 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;
}
}
void TileStorage::select(const size_t *indices, size_t n)
{
changeFlags(TILEFLAG_SELECTED, 0, indices, n);
}
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(const TileEffectData& o)
: efxtype(o.efxtype), efxidx(o.efxidx), grid(NULL)
, alpha(o.alpha), blend(o.blend)
, ownGrid(false), shared(false), wavy(o.wavy)
{
}
void TileEffectData::deleteGrid()
{
if(ownGrid)
{
ownGrid = false;
delete grid;
}
}
TileEffectData::~TileEffectData()
{
deleteGrid();
}
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
deleteGrid();
g = &t->rep->grid;
}
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.4,
// 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.
// And NOTE: without this, maps may look deceivingly correct, but they really are not.
tc.v2 = 1 - tc.v2;
tc.v1 = 1 - tc.v1;
std::swap(tc.v1, tc.v2);
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);
// link eff->grid to rep->grid. create own instance if necessary.
/*if(eff)
{
const unsigned char gridtype = eff->grid ? eff->grid->gridType : GRID_UNDEFINED;
if(flags & TILEFLAG_OWN_EFFDATA)
{
assert(!eff->shared);
eff->deleteGrid();
}
else
{
eff = new TileEffectData(*eff);
flags |= TILEFLAG_OWN_EFFDATA;
}
assert(!eff->ownGrid);
eff->grid = &rep->grid;
eff->grid->gridType = gridtype;
}*/
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(eff && eff->grid)
return eff->grid; // this points to rep.grid if eff is present and repeat is on
if(flags & TILEFLAG_REPEAT)
return &rep->getGrid();
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
}