1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2025-07-02 22:14:37 +00:00

Rework + optimize map tile storage & rendering, initial working draft.

class Element is completely gone.
(The files are still there but no longer compiled in. Will delete later)

Broken still:
- support for vertical flip
- the editor
- culling
This commit is contained in:
fgenesis 2023-07-14 00:19:33 +02:00
parent e8c405cd9e
commit bd5b2b3495
31 changed files with 1704 additions and 1422 deletions

View file

@ -97,6 +97,7 @@ RenderObject::RenderObject()
shareAlphaWithChildren = false;
shareColorWithChildren = false;
neverFollowCamera = false;
}
RenderObject::~RenderObject()

View file

@ -242,6 +242,7 @@ public:
bool _hidden;
bool _fv, _fh;
bool _markedForDelete;
bool neverFollowCamera;
unsigned char pm; // unsigned char to save space

View file

@ -229,6 +229,8 @@ void RenderObjectLayer::prepareRender()
toRender.push_back(NULL); // terminate
core->totalRenderObjectCount += n;
// TODO: set followCameraMult = (0,0) when followCamera == 0 ?
switch(followCameraLock)
{
default:

View file

@ -41,6 +41,8 @@ Vector RenderObject::getFollowCameraPosition(const Vector& v) const
{
assert(layer != LR_NONE);
assert(!parent); // this makes no sense when we're not a root object
if(neverFollowCamera)
return v;
const RenderObjectLayer &rl = core->renderObjectLayers[layer];
Vector M = rl.followCameraMult;
float F = followCamera;

View file

@ -47,7 +47,7 @@ Texture::~Texture()
unload();
}
void Texture::readRGBA(unsigned char *pixels)
void Texture::readRGBA(unsigned char *pixels) const
{
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glBindTexture(GL_TEXTURE_2D, gltexid);
@ -207,7 +207,7 @@ bool Texture::upload(const ImageData& img, bool mipmap)
return true;
}
unsigned char * Texture::getBufferAndSize(int *wparam, int *hparam, size_t *sizeparam)
unsigned char * Texture::getBufferAndSize(int *wparam, int *hparam, size_t *sizeparam) const
{
const size_t bytes = size_t(width) * size_t(height) * 4;
unsigned char *data = (unsigned char*)malloc(bytes);

View file

@ -46,9 +46,9 @@ public:
int width, height;
void writeRGBA(int tx, int ty, int w, int h, const unsigned char *pixels);
void readRGBA(unsigned char *pixels);
void readRGBA(unsigned char *pixels) const;
unsigned char *getBufferAndSize(int *w, int *h, size_t *size); // returned memory must be free()'d
unsigned char *getBufferAndSize(int *w, int *h, size_t *size) const; // returned memory must be free()'d
std::string name, filename;
bool upload(const ImageData& img, bool mipmap);

View file

@ -269,24 +269,31 @@ Texture *TextureMgr::finalize(TexLoadTmp& tt)
return tex;
}
void TextureMgr::loadBatch(Texture * pdst[], const std::string texnames[], size_t n, LoadMode mode, ProgressCallback cb, void *cbUD)
size_t TextureMgr::loadBatch(Texture * pdst[], const std::string texnames[], size_t n, LoadMode mode, ProgressCallback cb, void *cbUD)
{
size_t doneCB = 0;
if(threads.empty())
{
size_t loaded = 0;
for(size_t i = 0; i < n; ++i)
{
Texture *tex = load(texnames[i], mode);
if(pdst)
pdst[i] = tex;
if(cb)
cb(++doneCB, cbUD);
loaded += !!tex;
}
return;
return loaded;
}
// Important that this is pre-allocated. We store pointers to elements and
// send them to threads, so this must never reallocate.
std::vector<TexLoadTmp> tmp(n);
size_t inprogress = 0, doneCB = 0;
size_t inprogress = 0;
size_t loaded = 0;
for(size_t i = 0; i < n; ++i)
{
TexLoadTmp& tt = tmp[i];
@ -301,6 +308,7 @@ void TextureMgr::loadBatch(Texture * pdst[], const std::string texnames[], size_
pdst[i] = tt.curTex;
if(cb)
cb(++doneCB, cbUD);
++loaded;
continue;
}
@ -320,7 +328,9 @@ void TextureMgr::loadBatch(Texture * pdst[], const std::string texnames[], size_
pdst[tt.arrayidx] = tex;
if(cb)
cb(++doneCB, cbUD);
loaded += !!tex;
}
return loaded;
}
Texture* TextureMgr::load(const std::string& texname, LoadMode mode)

View file

@ -30,7 +30,7 @@ public:
OVERWRITE, // always overwrite
};
void loadBatch(Texture *pdst[], const std::string texnames[], size_t n, LoadMode mode = KEEP, ProgressCallback cb = 0, void *cbUD = 0);
size_t loadBatch(Texture *pdst[], const std::string texnames[], size_t n, LoadMode mode = KEEP, ProgressCallback cb = 0, void *cbUD = 0);
Texture *load(const std::string& texname, LoadMode mode);
void reloadAll(LoadMode mode);

View file

@ -2,9 +2,9 @@
#include "RenderGrid.h"
#include "Tileset.h"
#include "Base.h"
#include <algorithm>
TileStorage::TileStorage(const TileEffectStorage& eff)
: effstore(eff)
TileStorage::TileStorage()
{
}
@ -13,16 +13,31 @@ TileStorage::~TileStorage()
destroyAll();
}
void TileStorage::moveToFront(size_t idx)
TileStorage::Sizes TileStorage::stats() const
{
_moveToFront(idx);
refreshAll();
Sizes sz;
sz.tiles = tiles.size();
sz.update = indicesToUpdate.size();
sz.collide = indicesToCollide.size();
return sz;
}
void TileStorage::moveToBack(size_t idx)
void TileStorage::moveToFront(const size_t *indices, size_t n)
{
_moveToBack(idx);
refreshAll();
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)
@ -44,24 +59,58 @@ void TileStorage::doInteraction(const Vector& pos, const Vector& vel, float mult
}
}
void TileStorage::_moveToFront(size_t idx)
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
TileData tile = tiles[idx];
tiles.erase(tiles.begin() + idx);
tiles.push_back(tile);
if(n == 1)
{
TileData tile = tiles[*indices];
tiles.erase(tiles.begin() + *indices);
tiles.push_back(tile);
return;
}
_moveToPos(size(), indices, n);
}
void TileStorage::_moveToBack(size_t idx)
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
TileData tile = tiles[idx];
tiles.erase(tiles.begin() + idx);
tiles.insert(tiles.begin(), tile);
if(n == 1)
{
TileData tile = tiles[*indices];
tiles.erase(tiles.begin() + *indices);
tiles.insert(tiles.begin(), tile);
return;
}
_moveToPos(0, indices, n);
}
void TileStorage::moveToOther(TileStorage& other, const size_t *indices, size_t 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]]);
@ -81,6 +130,7 @@ void TileStorage::moveToOther(TileStorage& other, const size_t *indices, size_t
refreshAll();
other.refreshAll();
return firstNewIdx;
}
static void dropAttachments(TileData& t)
@ -133,13 +183,51 @@ void TileStorage::setTag(unsigned tag, const size_t* indices, size_t n)
// don't need to refresh here
}
void TileStorage::setEffect(int idx, const size_t* indices, size_t n)
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.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();
@ -149,13 +237,16 @@ void TileStorage::refreshAll()
for(size_t i = 0; i < n; ++i)
{
const TileData& t = tiles[i];
if(const TileEffectData *e = t.eff)
if(!(t.flags & TILEFLAG_HIDDEN))
{
if(t.flags & TILEFLAG_OWN_EFFDATA)
if(const TileEffectData *e = t.eff)
{
indicesToUpdate.push_back(i);
if(e->efxtype == EFX_WAVY)
indicesToCollide.push_back(i);
if(t.flags & TILEFLAG_OWN_EFFDATA)
{
indicesToUpdate.push_back(i);
if(e->efxtype == EFX_WAVY)
indicesToCollide.push_back(i);
}
}
}
}
@ -170,10 +261,14 @@ void TileStorage::clearSelection()
TileEffectData::TileEffectData(const TileEffectConfig& cfg)
: efxtype(cfg.type), efxidx(cfg.index)
, grid(NULL), blend(BLEND_DEFAULT)
, grid(NULL), alpha(1), blend(BLEND_DEFAULT)
{
switch(cfg.type)
{
case EFX_NONE:
assert(false);
break;
case EFX_WAVY:
{
float bity = 20; // FIXME
@ -184,7 +279,7 @@ TileEffectData::TileEffectData(const TileEffectConfig& cfg)
RenderGrid *g = new RenderGrid(2, cfg.u.wavy.segsy);
grid = g;
g->gridType = GRID_UNDEFINED; // by default it's GRID_WAVY, but that would reset during update
g->gridType = GRID_UNDEFINED; // we do the grid update manually
wavy.angleOffset = 0;
wavy.magnitude = 0;
@ -328,6 +423,15 @@ void TileEffectData::doInteraction(const TileData& t, const Vector& pos, const V
}
}
TileEffectStorage::TileEffectStorage()
{
}
TileEffectStorage::~TileEffectStorage()
{
clear();
}
void TileEffectStorage::assignEffect(TileData& t, int index) const
{
dropAttachments(t);
@ -343,6 +447,9 @@ void TileEffectStorage::assignEffect(TileData& t, int index) const
}
else if(idx < configs.size())
{
if(configs[idx].type == EFX_NONE)
return;
t.eff = new TileEffectData(configs[idx]);
t.flags |= TILEFLAG_OWN_EFFDATA;
}
@ -354,3 +461,49 @@ void TileEffectStorage::update(float dt)
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);
}
}
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;
}

View file

@ -28,8 +28,13 @@ Further observations:
And on map reload everything is back to the same value for each tile with the same effect and params.
So we can totally exclude the editor.
Assumptions:
- Most tiles that exist are going to be rendered
- Only few tiles have an effect attached
Gotaches:
- Keeping a pointer to a TileData is not safe.
- Tile indexes are not stable. Moving a tile changes the index it can be addressed with
*/
class ElementTemplate;
@ -39,12 +44,13 @@ class TileRender;
enum EFXType
{
EFX_NONE,
EFX_SEGS,
EFX_ALPHA,
EFX_WAVY
};
// static configuration for one effect type
// static configuration for one effect type. POD.
struct TileEffectConfig
{
public:
@ -85,9 +91,10 @@ enum TileFlags
TILEFLAG_SOLID_IN = 0x08, // instead of OT_INVISIBLE, generate OT_INVISIBLEIN
TILEFLAG_HURT = 0x10, // always generate OT_HURT
TILEFLAG_FH = 0x20, // flipped horizontally
TILEFLAG_OWN_EFFDATA = 0x40, // tile owns its TileEffectData, can modify & must delete
TILEFLAG_OWN_EFFDATA = 0x40, // tile owns its TileEffectData, can update, must delete
TILEFLAG_HIDDEN = 0x80, // don't render tile
TILEFLAG_SELECTED = 0x100
TILEFLAG_SELECTED = 0x100, // ephemeral: selected in editor
TILEFLAG_EDITOR_HIDDEN = 0x200 // tile is hidden for editor reasons. temporarily set when multi-selecting and moving. doesn't count as hidden externally and is only for rendering.
};
struct TileData;
@ -100,7 +107,7 @@ struct TileEffectData
void doInteraction(const TileData& t, const Vector& pos, const Vector& vel, float mult, float touchWidth);
const EFXType efxtype;
const unsigned efxidx; // index to ElementEffect
const unsigned efxidx; // index of TileEffect
RenderGrid *grid;
InterpolatedVector alpha;
BlendType blend;
@ -116,46 +123,70 @@ struct TileEffectData
void update(float dt);
};
Wavy wavy;
private:
TileEffectData(const TileEffectData&); // no-copy
};
// POD and as compact as possible
// POD and as compact as possible. Intended for rendering as quickly as possible.
// the idea is that these are linearly adjacent in memory in the order they are rendered,
// to maximize cache & prefetch efficiency
struct TileData
{
float x, y, rotation, texscale;
float scalex, scaley;
float beforeScaleOffsetX, beforeScaleOffsetY;
float x, y, scalex, scaley, texscaleX, texscaleY;
float beforeScaleOffsetX, beforeScaleOffsetY; // almost always 0. // TODO: this is nasty, ideally get rid of this
float rotation;
unsigned flags; // TileFlags
unsigned tag;
ElementTemplate *et; // texture, texcoords, etc is here
TileEffectData *eff;
unsigned tag; // FIXME: make this int
const ElementTemplate *et; // never NULL. texture, texcoords, etc is here. // TODO: maybe replace with unsigned tilesetID? but that's an extra indirection or two during rendering...
TileEffectData *eff; // mostly NULL
// helpers for external access
inline void setVisible(bool on) { if(on) flags &= ~TILEFLAG_HIDDEN; else flags |= TILEFLAG_HIDDEN; }
inline bool isVisible() const { return !(flags & TILEFLAG_HIDDEN); }
bool isCoordinateInside(float cx, float cy, float minsize = 0) const;
};
class TileEffectStorage
{
public:
TileEffectStorage();
~TileEffectStorage();
void finalize(); // first fill configs[], then call this
void assignEffect(TileData& t, int index) const;
void update(float dt);
void clear(); // do NOT call this while there are tiles that may reference one in prepared[]
std::vector<TileEffectData*> prepared;
std::vector<TileEffectConfig> configs;
private:
void clearPrepared();
std::vector<TileEffectData*> prepared;
TileEffectStorage(const TileEffectStorage&); // no-copy
};
class TileStorage
{
friend class TileRender;
public:
TileStorage(const TileEffectStorage& eff);
TileStorage();
~TileStorage();
void moveToFront(size_t idx);
void moveToBack(size_t idx);
void moveToOther(TileStorage& other, const size_t *indices, size_t n);
void moveToFront(const size_t *indices, size_t n);
void moveToBack(const size_t *indices, size_t n);
// returns starting index of new tiles. Since new tiles are always appended at the end,
// the new indices corresponding to the moved tiles are [retn .. retn+n)
size_t moveToOther(TileStorage& other, const size_t *indices, size_t n);
size_t cloneSome(const TileEffectStorage& effstore, const size_t *indices, size_t n);
void deleteSome(const size_t *indices, size_t n);
void setTag(unsigned tag, const size_t *indices, size_t n);
void setEffect(int idx, const size_t *indices, size_t n);
void setEffect(const TileEffectStorage& effstore, int idx, const size_t *indices, size_t n);
void changeFlags(unsigned flagsToSet, unsigned flagsToUnset, const size_t *indices, size_t n);
void update(float dt);
void doInteraction(const Vector& pos, const Vector& vel, float mult, float touchWidth);
@ -164,16 +195,27 @@ public:
void clearSelection();
struct Sizes
{
size_t tiles, update, collide;
};
Sizes stats() const;
size_t size() const { return tiles.size(); }
std::vector<TileData> tiles; // must call refreshAll() after changing this
private:
std::vector<TileData> tiles;
std::vector<size_t> indicesToUpdate;
std::vector<size_t> indicesToCollide;
const TileEffectStorage& effstore;
void _refreshTile(const TileData& t);
void _moveToFront(size_t idx);
void _moveToBack(size_t idx);
void _moveToFront(const size_t *indices, size_t n);
void _moveToBack(const size_t *indices, size_t n);
void _moveToPos(size_t where, const size_t *indices, size_t n);
TileStorage(const TileStorage&); // no-copy
};

View file

@ -9,6 +9,8 @@
TileRender::TileRender(const TileStorage& tiles)
: storage(tiles), renderBorders(false)
{
this->cull = false;
this->neverFollowCamera = true;
}
TileRender::~TileRender()
@ -46,6 +48,7 @@ void TileRender::onRender(const RenderState& rs) const
const RenderObjectLayer& rl = core->renderObjectLayers[this->layer];
const Vector M = rl.followCameraMult; // affected by parallaxLock
const float F = rl.followCamera;
const bool parallax = rl.followCamera > 0;
// Formula from RenderObject::getFollowCameraPosition() and optimized for speed
const Vector C = core->screenCenter;
@ -61,15 +64,18 @@ void TileRender::onRender(const RenderState& rs) const
for(size_t i = 0; i < storage.tiles.size(); ++i)
{
const TileData& tile = storage.tiles[i];
if(tile.flags & TILEFLAG_HIDDEN)
if(tile.flags & (TILEFLAG_HIDDEN | TILEFLAG_EDITOR_HIDDEN))
continue;
const Vector tilepos(tile.x, tile.y);
const Vector tmp = T + (F * tilepos);
const Vector pos = tilepos * M1 + (tmp * M); // lerp, used to select whether to use original v or parallax-corrected v
Vector pos(tile.x, tile.y);
if(parallax)
{
const Vector tmp = T + (F * pos);
pos = pos * M1 + (tmp * M); // lerp, used to select whether to use original v or parallax-corrected v
}
ElementTemplate * const et = tile.et;
if(Texture * const tex = et->tex.content())
const ElementTemplate * const et = tile.et;
if(const Texture * const tex = et->tex.content())
{
unsigned texid = tex->gltexid;
unsigned rep = tile.flags & TILEFLAG_REPEAT;
@ -96,7 +102,7 @@ void TileRender::onRender(const RenderState& rs) const
// this is only relevant in editor mode and is always 0 otherwise
glTranslatef(tile.beforeScaleOffsetX, tile.beforeScaleOffsetY, 0);
glScalef(tile.scalex * et->w, tile.scaley * et->h, 1);
glScalef(tile.scalex, tile.scaley, 1);
//glScalef(tile.scalex * et->w, tile.scaley * et->h, 1); // TODO use this + fixed verts
BlendType blend = BLEND_DEFAULT;
@ -141,6 +147,9 @@ void TileRender::onRender(const RenderState& rs) const
}
else
{
glPushMatrix();
glScalef(et->w, et->h, 1);
RenderState rx(rs);
rx.alpha = alpha;
grid->render(rx, upperLeftTextureCoordinates, lowerRightTextureCoordinates);
@ -150,6 +159,8 @@ void TileRender::onRender(const RenderState& rs) const
grid->renderDebugPoints(rs);
lastTexId = 0;
}
glPopMatrix();
}
if(renderBorders)
@ -183,7 +194,11 @@ void TileRender::onRender(const RenderState& rs) const
glPopMatrix();
}
RenderObject::lastTextureApplied = lastTexId;
RenderObject::lastTextureRepeat = !!lastTexRepeat;
}
void TileRender::onUpdate(float dt)
{
//this->position = core->screenCenter;
}

View file

@ -14,7 +14,8 @@ public:
TileRender(const TileStorage& tiles);
virtual ~TileRender();
virtual void onRender(const RenderState& rs) const;
virtual void onRender(const RenderState& rs) const OVERRIDE;
virtual void onUpdate(float dt) OVERRIDE;
bool renderBorders;

View file

@ -5,6 +5,15 @@
#include "TextureMgr.h"
#include "Core.h"
Tileset::Tileset()
{
}
Tileset::~Tileset()
{
clear();
}
bool Tileset::loadFile(const char *fn, const unsigned char *usedIdx, size_t usedIdxLen)
{
elementTemplates.clear();
@ -13,24 +22,34 @@ bool Tileset::loadFile(const char *fn, const unsigned char *usedIdx, size_t used
if(!in)
return false;
bool warn = false;
std::string line, gfx;
while (std::getline(in, line))
{
gfx.clear();
int idx=-1, w=0, h=0;
SimpleIStringStream is(line.c_str(), SimpleIStringStream::REUSE);
is >> idx >> gfx >> w >> h;
if(idx >= 0)
if(idx >= 0 && !gfx.empty())
{
ElementTemplate t;
t.idx = idx;
t.gfx = gfx;
t.w = w;
t.h = h;
elementTemplates.push_back(t);
if(idx < 1024)
{
ElementTemplate t;
t.idx = idx;
t.gfx = gfx;
t.w = w;
t.h = h;
elementTemplates.push_back(t);
}
else
warn = true;
}
}
in.close();
if(warn)
errorLog("Tileset indices of 1024 and above are reserved; ignored during load");
std::sort(elementTemplates.begin(), elementTemplates.end());
// begin preloading textures
@ -48,47 +67,174 @@ bool Tileset::loadFile(const char *fn, const unsigned char *usedIdx, size_t used
// drop duplicates
usedTex.resize(std::distance(usedTex.begin(), std::unique(usedTex.begin(), usedTex.end())));
std::ostringstream os;
os << "Loading " << usedTex.size()
<< " used textures out of the " << elementTemplates.size() << " tileset entries";
debugLog(os.str());
{
std::ostringstream os;
os << "Loading " << usedTex.size()
<< " used textures out of the " << elementTemplates.size() << " tileset entries";
debugLog(os.str());
}
// preload all used textures
size_t loaded = 0;
if(usedTex.size())
core->texmgr.loadBatch(NULL, &usedTex[0], usedTex.size());
loaded = core->texmgr.loadBatch(NULL, &usedTex[0], usedTex.size());
{
std::ostringstream os;
os << "Loaded " << loaded << " textures successfully";
debugLog(os.str());
}
// finalize
size_t nfailed = 0;
std::ostringstream failed;
for (size_t i = 0; i < elementTemplates.size(); i++)
{
ElementTemplate& et = elementTemplates[i];
// only check those that are actualy loaded; otherwise this would load in textures
// that we didn't bother to batch-load above
if(!usedIdx || (et.idx < usedIdxLen && usedIdx[et.idx]))
{
if(!et.getTexture()) // assigns width/height and caches texture pointer
{
++nfailed;
failed << et.gfx << " ";
}
}
}
if(nfailed)
{
std::ostringstream os;
os << "The following " << nfailed << " textures failed to load and would be used by tiles:";
debugLog(os.str());
debugLog(failed.str());
}
return true;
}
void Tileset::clear()
{
for(size_t i = 0; i < dummies.size(); ++i)
delete dummies[i];
dummies.clear();
elementTemplates.clear();
}
ElementTemplate *Tileset::getByIdx(size_t idx)
const ElementTemplate *Tileset::getByIdx(size_t idx)
{
for (size_t i = 0; i < elementTemplates.size(); i++)
{
if (elementTemplates[i].idx == idx)
ElementTemplate& et = elementTemplates[i];
if (et.idx == idx)
{
return &elementTemplates[i];
et.getTexture(); // HACK: make sure the texture is loaded before this gets used
return &et;
}
}
return 0;
// a tile that gets an ET attached must remember its tileset id even if the entry is not present
// in the tileset. since the tile does not store the idx as an integer, we need to return a dummy element.
for (size_t i = 0; i < dummies.size(); i++)
{
ElementTemplate *et = dummies[i];
if (et->idx == idx)
return et;
}
{
std::ostringstream os;
os << "Tileset idx " << idx << " not found, creating dummy";
debugLog(os.str());
}
ElementTemplate *dummy = new ElementTemplate;
dummy->idx = idx;
dummies.push_back(dummy);
return dummy;
}
const ElementTemplate* Tileset::getAdjacent(size_t idx, int direction, bool wraparound)
{
ElementTemplate *et = _getAdjacent(idx, direction, wraparound);
if(et)
et->getTexture(); // load just in case
return et;
}
Texture* ElementTemplate::getTexture()
{
if(tex)
if(loaded)
return tex.content();
tex = core->getTexture(gfx);
if(!w)
w = tex->width;
if(!h)
h = tex->height;
loaded = true;
tex = core->getTexture(gfx); // may end up NULL
if(tex)
{
if(!w)
w = tex->width;
if(!h)
h = tex->height;
}
else
{
if(!w)
w = 64;
if(!h)
h = 64;
}
return tex.content();
}
ElementTemplate * Tileset::_getAdjacent(size_t idx, int direction, bool wraparound)
{
assert(direction == 1 || direction == -1);
const size_t maxn = elementTemplates.size();
size_t closest = 0;
int mindiff = 0;
for (size_t i = 0; i < maxn; i++)
{
if (elementTemplates[i].idx == idx)
{
if(wraparound)
{
if(!i && direction < 0)
return &elementTemplates.back();
if(i + direction >= maxn)
return &elementTemplates[0];
}
else
i += direction; // may underflow
return i < maxn ? &elementTemplates[i] : NULL;
}
int diff = labs((int)elementTemplates[i].idx - (int)idx);
if(diff < mindiff || !mindiff)
{
mindiff = diff;
closest = i;
}
}
// not found? pick whatever was closest to the non-existing idx, and go back/forward from there
// avoid going "twice" in the given direction
if(closest < idx && direction < 0)
direction = 0; // this is already a step back, don't step again
else if(closest > idx && direction > 0)
direction = 0; // this is already a step forward, don't step again
else if(wraparound)
{
if(!closest && direction < 0)
return &elementTemplates.back();
if(closest + direction >= maxn)
return &elementTemplates[0];
}
size_t i = closest + direction;
return i < maxn ? &elementTemplates[i] : NULL;
}

View file

@ -8,32 +8,47 @@
class ElementTemplate
{
public:
ElementTemplate() { w=0; h=0; idx=-1; tu1=tv1=0; tu2=tv2=1; }
ElementTemplate() { w=0; h=0; idx=-1; tu1=tv1=0; tu2=tv2=1; loaded=false; }
inline bool operator<(const ElementTemplate& o) const { return idx < o.idx; }
Texture *getTexture(); // loads if not already loaded
// lazily assigned when tex is loaded
CountedPtr<Texture> tex;
CountedPtr<Texture> tex; // NULL if failed to load or not yet loaded
float w,h; // custom size if used, otherwise texture size
// fixed
float tu1, tu2, tv1, tv2; // texcoords
size_t idx;
std::string gfx;
bool loaded;
};
class Tileset
{
public:
Tileset();
~Tileset();
// pass usedIdx == NULL to preload all textures from tileset
// pass usedIdx != NULL to preload only textures where usedIdx[i] != 0
bool loadFile(const char *fn, const unsigned char *usedIdx, size_t usedIdxLen);
void clear();
ElementTemplate *getByIdx(size_t idx);
// return valid ET if found, or creates a dummy if not. never returns NULL.
const ElementTemplate *getByIdx(size_t idx);
// search for non-dummy ET in a given direction. used to cycle through ETs.
// never returns dummy ET. May return NULL.
const ElementTemplate *getAdjacent(size_t idx, int direction, bool wraparound);
std::vector<ElementTemplate> elementTemplates;
private:
ElementTemplate *_getAdjacent(size_t idx, int direction, bool wraparound);
std::vector<ElementTemplate*> dummies;
};