diff --git a/Aquaria/Game.cpp b/Aquaria/Game.cpp index 844db0e..5200789 100644 --- a/Aquaria/Game.cpp +++ b/Aquaria/Game.cpp @@ -318,14 +318,7 @@ void Game::transitionToScene(std::string scene) ElementTemplate *Game::getElementTemplateByIdx(size_t idx) { - for (size_t i = 0; i < elementTemplates.size(); i++) - { - if (elementTemplates[i].idx == idx) - { - return &elementTemplates[i]; - } - } - return 0; + return tileset.getByIdx(idx); } Element* Game::createElement(size_t idx, Vector position, size_t bgLayer, RenderObject *copy, ElementTemplate *et) @@ -338,8 +331,7 @@ Element* Game::createElement(size_t idx, Vector position, size_t bgLayer, Render Element *element = new Element(); if (et) { - element->setTexture(et->gfx); - element->alpha = et->alpha; + element->setTexturePointer(et->getTexture()); } element->position = position; @@ -377,7 +369,6 @@ Element* Game::createElement(size_t idx, Vector position, size_t bgLayer, Render } addRenderObject(element, LR_ELEMENTS1+bgLayer); dsq->addElement(element); - //element->updateCullVariables(); return element; } @@ -1335,6 +1326,8 @@ bool Game::loadSceneXML(std::string scene) } clearObsRows(); + std::string tilesetToLoad; + XMLElement *level = doc.FirstChildElement("Level"); if (level) { @@ -1344,7 +1337,7 @@ bool Game::loadSceneXML(std::string scene) tileset = level->Attribute("elementTemplatePack"); // legacy, still present in some very old maps if (tileset) { - loadElementTemplates(tileset); + tilesetToLoad = tileset; levelSF->SetAttribute("tileset", tileset); } else @@ -1756,6 +1749,7 @@ bool Game::loadSceneXML(std::string scene) if(fullTilesetReload) { fullTilesetReload = false; + tileset.clear(); // used by SceneEditor // no elements exist right now -> textures will be cleared and reloaded dsq->texmgr.clearUnused(); @@ -1763,6 +1757,10 @@ bool Game::loadSceneXML(std::string scene) // figure out which textures in the tileset are used and preload those that are actually used { + std::ostringstream os; + os << "Scene has " << elemsDefs.size() << " elements"; + debugLog(os.str()); + unsigned char usedIdx[1024] = {0}; for(size_t i = 0; i < elemsDefs.size(); ++i) { @@ -1770,27 +1768,8 @@ bool Game::loadSceneXML(std::string scene) if(idx < Countof(usedIdx)) usedIdx[idx] = 1; } - std::vector usedTex; - usedTex.reserve(elementTemplates.size()); // optimistically assume all textures in the tileset are used - for (size_t i = 0; i < elementTemplates.size(); i++) - { - unsigned idx = elementTemplates[i].idx; - if (idx < Countof(usedIdx) && usedIdx[idx]) - usedTex.push_back(elementTemplates[i].gfx); - } - std::sort(usedTex.begin(), usedTex.end()); - // drop duplicates - usedTex.resize(std::distance(usedTex.begin(), std::unique(usedTex.begin(), usedTex.end()))); - - std::ostringstream os; - os << "Scene has " << elemsDefs.size() << " elements that use " << usedTex.size() - << " distinct textures out of the " << elementTemplates.size() << " entries in the tileset"; - debugLog(os.str()); - - // preload all used textures - if(usedTex.size()) - dsq->texmgr.loadBatch(NULL, &usedTex[0], usedTex.size()); + loadElementTemplates(tilesetToLoad, &usedIdx[0], Countof(usedIdx)); } // Now that all SE tags have been processed, spawn them @@ -4690,79 +4669,55 @@ void Game::snapCam() warpCameraTo(*cameraFollow); } -ElementTemplate Game::getElementTemplateForLetter(int i) -{ - float cell = 64.0f/512.0f; - //for (int i = 0; i < 27; i++) - ElementTemplate t; - t.idx = 1024+i; - t.gfx = "Aquarian"; - int x = i,y=0; - while (x >= 6) - { - x -= 6; - y++; - } - - t.tu1 = x*cell; - t.tv1 = y*cell; - t.tu2 = t.tu1 + cell; - t.tv2 = t.tv1 + cell; - - t.tv2 = 1 - t.tv2; - t.tv1 = 1 - t.tv1; - std::swap(t.tv1,t.tv2); - - t.w = 512*cell; - t.h = 512*cell; - //elementTemplates.push_back(t); - return t; -} - -void Game::loadElementTemplates(std::string pack) +bool Game::loadElementTemplates(std::string pack, const unsigned char *usedIdx, size_t usedIdxLen) { stringToLower(pack); - elementTemplates.clear(); - std::string fn; if (dsq->mod.isActive()) fn = dsq->mod.getPath() + "tilesets/" + pack + ".txt"; else fn = "data/tilesets/" + pack + ".txt"; - if (!exists(fn)) + if(!tileset.loadFile(fn.c_str(), usedIdx, usedIdxLen)) { - errorLog ("Could not open tileset [" + fn + "]"); - return; + errorLog ("Could not load tileset [" + fn + "]"); + return false; } - InStream in(fn.c_str()); - std::string line; - while (std::getline(in, line)) + // Aquarian alphabet letters + if(const CountedPtr aqtex = dsq->getTexture("aquarian")) { - int idx=-1, w=-1, h=-1; - std::string gfx; - std::istringstream is(line); - is >> idx >> gfx >> w >> h; - ElementTemplate t; - t.idx = idx; - t.gfx = gfx; - if (w==0) w=-1; - if (h==0) h=-1; - t.w = w; - t.h = h; - elementTemplates.push_back(t); + const float cell = 64.0f/512.0f; + for (int i = 0; i < 27; i++) + { + ElementTemplate t; + t.idx = 1024+i; + t.tex = aqtex; + int x = i,y=0; + while (x >= 6) + { + x -= 6; + y++; + } + + t.tu1 = x*cell; + t.tv1 = y*cell; + t.tu2 = t.tu1 + cell; + t.tv2 = t.tv1 + cell; + + t.tv2 = 1 - t.tv2; + t.tv1 = 1 - t.tv1; + std::swap(t.tv1,t.tv2); + + t.w = 512*cell; + t.h = 512*cell; + + tileset.elementTemplates.push_back(t); + } } - in.close(); - - std::sort(elementTemplates.begin(), elementTemplates.end()); - - for (int i = 0; i < 27; i++) - { - elementTemplates.push_back(getElementTemplateForLetter(i)); - } + return true; } void Game::clearGrid(int v) @@ -4778,40 +4733,6 @@ void Game::resetFromTitle() overrideMusic = ""; } -void Game::setGrid(ElementTemplate *et, Vector position, float rot360) -{ - for (size_t i = 0; i < et->grid.size(); i++) - { - TileVector t(position); - int x = et->grid[i].x; - int y = et->grid[i].y; - if (rot360 >= 0 && rot360 < 90) - { - } - else if (rot360 >= 90 && rot360 < 180) - { - int swap = y; - y = x; - x = swap; - x = -x; - } - else if (rot360 >= 180 && rot360 < 270) - { - x = -x; - y = -y; - } - else if (rot360 >= 270 && rot360 < 360) - { - int swap = y; - y = x; - x = swap; - y = -y; - } - TileVector s(t.x+x, t.y+y); - setGrid(s, OT_INVISIBLE); - } -} - void Game::removeState() { const float fadeTime = 0.25; diff --git a/Aquaria/Game.h b/Aquaria/Game.h index 4b2743c..729ba1b 100644 --- a/Aquaria/Game.h +++ b/Aquaria/Game.h @@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "ScriptedEntity.h" #include "TileVector.h" #include "SceneEditor.h" +#include "Tileset.h" #include using namespace tinyxml2; @@ -84,20 +85,6 @@ struct MinimapIcon typedef std::list Ingredients; -class ElementTemplate -{ -public: - ElementTemplate() { alpha = 1; cull = true; w=-1; h=-1; idx=-1; tu1=tu2=tv1=tv2=0; } - inline bool operator<(const ElementTemplate& o) const { return idx < o.idx; } - std::string gfx; - std::vector grid; - int w,h; - float tu1, tu2, tv1, tv2; - bool cull; - float alpha; - size_t idx; -}; - class ObsRow { public: @@ -171,9 +158,10 @@ public: InGameMenu *getInGameMenu() { return themenu; } - void loadElementTemplates(std::string pack); + // pass usedIdx == NULL to preload all textures from tileset + // pass usedIdx != NULL to preload only textures where usedIdx[i] != 0 + bool loadElementTemplates(std::string pack, const unsigned char *usedIdx, size_t usedIdxLen); Element* createElement(size_t etidx, Vector position, size_t bgLayer=0, RenderObject *copy=0, ElementTemplate *et=0); - void setGrid(ElementTemplate *et, Vector position, float rot360=0); void updateParticlePause(); @@ -196,7 +184,7 @@ public: void handleShotCollisionsSkeletal(Entity *e); void handleShotCollisionsHair(Entity *e, int num = 0, float perc = 0); - std::vector elementTemplates; + Tileset tileset; std::string sceneName, sceneDisplayName; ElementTemplate *getElementTemplateByIdx(size_t idx); @@ -362,7 +350,6 @@ public: void setMusicToPlay(const std::string &musicToPlay); Vector lastCollidePosition; void switchBgLoop(int v); - ElementTemplate getElementTemplateForLetter(int i); CurrentRender *currentRender; SteamRender *steamRender; SongLineRender *songLineRender; diff --git a/Aquaria/TileVector.h b/Aquaria/TileVector.h index cc25959..86f6536 100644 --- a/Aquaria/TileVector.h +++ b/Aquaria/TileVector.h @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "../BBGE/Vector.h" -const int TILE_SIZE = 20; +enum { TILE_SIZE = 20 }; class TileVector { diff --git a/BBGE/CMakeLists.txt b/BBGE/CMakeLists.txt index 8eaa5af..39a1372 100644 --- a/BBGE/CMakeLists.txt +++ b/BBGE/CMakeLists.txt @@ -109,6 +109,12 @@ set(BBGE_SRCS TextureMgr.h TTFFont.cpp TTFFont.h + Tile.cpp + Tile.h + TileRender.cpp + TileRender.h + Tileset.cpp + Tileset.h Vector.cpp Vector.h Window.cpp diff --git a/BBGE/RenderObject.h b/BBGE/RenderObject.h index e4805c2..8f5521c 100644 --- a/BBGE/RenderObject.h +++ b/BBGE/RenderObject.h @@ -205,6 +205,7 @@ public: // Defined in RenderObject_inline.h inline Vector getFollowCameraPosition() const; + inline Vector getFollowCameraPosition(const Vector& pos) const; void lookAt(const Vector &pos, float t, float minAngle, float maxAngle, float offset=0); inline RenderObject *getParent() const {return parent;} diff --git a/BBGE/RenderObject_inline.h b/BBGE/RenderObject_inline.h index d4b8f69..dcd42e2 100644 --- a/BBGE/RenderObject_inline.h +++ b/BBGE/RenderObject_inline.h @@ -33,19 +33,42 @@ inline bool RenderObject::isOnScreen() const } Vector RenderObject::getFollowCameraPosition() const +{ + return getFollowCameraPosition(position); +} + +Vector RenderObject::getFollowCameraPosition(const Vector& v) const { assert(layer != LR_NONE); assert(!parent); // this makes no sense when we're not a root object const RenderObjectLayer &rl = core->renderObjectLayers[layer]; - Vector mul = rl.followCameraMult; - float f = followCamera; - if(!f) - f = rl.followCamera; - if (f <= 0) - return position; + Vector M = rl.followCameraMult; + float F = followCamera; + if(!F) + F = rl.followCamera; + if (F <= 0) + return v; - const Vector pos = (position - core->screenCenter) * f + core->screenCenter; - return position * (Vector(1,1) - mul) + (pos * mul); // lerp + /* Originally, not accounting for parallax lock on an axis, this was: + pos = v - core->screenCenter; + pos *= F; + pos = core->screenCenter + pos; + */ + + // uppercase are effectively constants that are not per-object + // lowercase are per-object + + // more concise math: + //const Vector pos = (v - core->screenCenter) * F + core->screenCenter; + //return v * (Vector(1,1) - M) + (pos * M); // lerp + + // optimized and rearranged + const Vector C = core->screenCenter; + const Vector M1 = Vector(1,1) - M; + const Vector T = C * (1 - F); + + const Vector pos = T + (F * v); + return v * M1 + (pos * M); // lerp, used to select whether to use original v or parallax-corrected v } #endif diff --git a/BBGE/Tile.cpp b/BBGE/Tile.cpp new file mode 100644 index 0000000..43f749d --- /dev/null +++ b/BBGE/Tile.cpp @@ -0,0 +1,2 @@ +#include "Tile.h" + diff --git a/BBGE/Tile.h b/BBGE/Tile.h new file mode 100644 index 0000000..3cb54e4 --- /dev/null +++ b/BBGE/Tile.h @@ -0,0 +1,60 @@ +#ifndef BBGE_TILE_H +#define BBGE_TILE_H + +#include +#include "Vector.h" + +class ElementTemplate; +class Texture; + +// A Tile is a very stripped down RenderObject that bypasses the default +// rendering pipeline for efficiency reasons. +// Use a TileRender to draw a list of Tiles. + +/* Properties of tiles: +- Lots of these exist. Need to store & render efficiently +- Usually no dynamic behavior, BUT: + * can have a TileEffect that requires updating (sometimes) + * can react to entities nearby (rare) + * may have a draw grid (wobbly plants etc) +- Only modified in the editor -> Can be slow to modify or reorder +- Render order must be strictly followed to get the correct visual overlapping +- Never part of a parent/child hierarchy, all tiles are standalone +- Does not have offset, internalOffset, gravity, etc etc that RenderObject has +- Parallax scroll factor is solely influenced by layer, not individually +- RGB is never tinted, alpha may come from efx +*/ + +enum TileFlags +{ + TILEFLAG_NONE = 0, + TILEFLAG_REPEAT = 0x01, // texture repeats and uses texscale for the repeat factor + TILEFLAG_SOLID = 0x02, // generates OT_INVISIBLE + TILEFLAG_SOLID_THICK = 0x04, // generates more OT_INVISIBLE + TILEFLAG_SOLID_IN = 0x08, // instead of OT_INVISIBLE, generate OT_INVISIBLEIN + TILEFLAG_HURT = 0x10, // always generate OT_HURT + TILEFLAG_FH = 0x20, // flipped horizontally +}; + +// sort-of-POD +struct TileData +{ + float x, y, rotation, texscale; + Vector scale, beforeScaleOffset; + int efx; + unsigned flags; // TileFlags + unsigned tag; + ElementTemplate *et; +}; + + +class TileStorage +{ +public: + std::vector tiles; + void refresh(); // call when adding/removing/reordering tiles or changing efx +}; + + + +#endif // BBGE_TILE_H diff --git a/BBGE/TileRender.cpp b/BBGE/TileRender.cpp new file mode 100644 index 0000000..4d49b93 --- /dev/null +++ b/BBGE/TileRender.cpp @@ -0,0 +1,135 @@ +#include "TileRender.h" +#include "RenderBase.h" +#include "Core.h" +#include "Tileset.h" + +TileRender::TileRender(const TileStorage& tiles) + : storage(tiles) +{ +} + +TileRender::~TileRender() +{ +} + +void TileRender::onRender(const RenderState& rs) const +{ + // prepare. get parallax scroll factors + const RenderObjectLayer& rl = core->renderObjectLayers[this->layer]; + Vector M = rl.followCameraMult; // affected by parallaxLock + const float F = rl.followCamera; + + // Formula from RenderObject::getFollowCameraPosition() and optimized for speed + const Vector C = core->screenCenter; + const Vector M1 = Vector(1,1) - M; + const Vector T = C * (1 - F); + + unsigned lastTexRepeat = false; + unsigned lastTexId = 0; + BlendType blend = BLEND_DEFAULT; // TODO: influenced by efx + const bool renderBorders = true; // TODO: when layer selected in editor + + for(size_t i = 0; i < storage.tiles.size(); ++i) + { + const TileData& tile = storage.tiles[i]; + 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 + + rs.gpu.setBlend(blend); + + ElementTemplate * const et = tile.et; + if(Texture * const tex = et->tex.content()) + { + unsigned texid = tex->gltexid; + unsigned rep = tile.flags & TILEFLAG_REPEAT; + if(texid != lastTexId || rep != lastTexRepeat) + { + lastTexId = texid; + lastTexRepeat = rep; + tex->apply(!!rep); + } + } + else + glBindTexture(GL_TEXTURE_2D, 0); // unlikely + + glPushMatrix(); + glTranslatef(pos.x, pos.y, pos.z); + + glRotatef(tile.rotation, 0, 0, 1); + if(tile.flags & TILEFLAG_FH) + glRotatef(180, 0, 1, 0); + + // this is only relevant in editor mode and is always 0 otherwise + glTranslatef(tile.beforeScaleOffset.x, tile.beforeScaleOffset.y, tile.beforeScaleOffset.z); + + glScalef(tile.scale.x, tile.scale.y, 1); + + // TODO: only need to do this when prev. tile had different alpha + { + const float alpha = 1; // TODO: via efx + Vector col = rs.color; + glColor4f(col.x, col.y, col.z, rs.alpha*alpha); + } + + const float _w2 = float(int(et->w)) * 0.5f; + const float _h2 = float(int(et->h)) * 0.5f; + + // render texture + { + const Vector upperLeftTextureCoordinates(et->tu1, et->tv1); + const Vector lowerRightTextureCoordinates(et->tu2, et->tv2); + + glBegin(GL_QUADS); + { + glTexCoord2f(upperLeftTextureCoordinates.x, 1.0f-upperLeftTextureCoordinates.y); + glVertex2f(-_w2, +_h2); + + glTexCoord2f(lowerRightTextureCoordinates.x, 1.0f-upperLeftTextureCoordinates.y); + glVertex2f(+_w2, +_h2); + + glTexCoord2f(lowerRightTextureCoordinates.x, 1.0f-lowerRightTextureCoordinates.y); + glVertex2f(+_w2, -_h2); + + glTexCoord2f(upperLeftTextureCoordinates.x, 1.0f-lowerRightTextureCoordinates.y); + glVertex2f(-_w2, -_h2); + } + glEnd(); + } + + if(renderBorders) + { + lastTexId = 0; + glBindTexture(GL_TEXTURE_2D, 0); + + Vector color(0.5f,0.5f,0.5f); // TODO: (1,1,1) when selected + + + glColor4f(color.x, color.y, color.z, 1.0f); + glPointSize(16); + glBegin(GL_POINTS); + glVertex2f(0,0); + glEnd(); + + glLineWidth(2); + glBegin(GL_LINE_STRIP); + glVertex2f(_w2, _h2); + glVertex2f(_w2, -_h2); + glVertex2f(-_w2, -_h2); + glVertex2f(-_w2, _h2); + glVertex2f(_w2, _h2); + glEnd(); + } + + + glPopMatrix(); + } + + + RenderObject::lastTextureApplied = lastTexId; + RenderObject::lastTextureRepeat = !!lastTexRepeat; +} + +void TileRender::onUpdate(float dt) +{ +} diff --git a/BBGE/TileRender.h b/BBGE/TileRender.h new file mode 100644 index 0000000..e9941df --- /dev/null +++ b/BBGE/TileRender.h @@ -0,0 +1,24 @@ +#ifndef BBGE_TILERENDER_H +#define BBGE_TILERENDER_H + +#include "RenderObject.h" +#include "Tile.h" + +class Tileset; + +class TileRender : public RenderObject +{ +private: + const TileStorage& storage; +public: + + TileRender(const TileStorage& tiles); + virtual ~TileRender(); + virtual void onRender(const RenderState& rs) const; + virtual void onUpdate(float dt); + +private: +}; + + +#endif // BBGE_TILERENDER_H diff --git a/BBGE/Tileset.cpp b/BBGE/Tileset.cpp new file mode 100644 index 0000000..b917db9 --- /dev/null +++ b/BBGE/Tileset.cpp @@ -0,0 +1,94 @@ +#include "Tileset.h" +#include "SimpleIStringStream.h" +#include "Base.h" +#include "ttvfs_stdio.h" +#include "TextureMgr.h" +#include "Core.h" + +bool Tileset::loadFile(const char *fn, const unsigned char *usedIdx, size_t usedIdxLen) +{ + elementTemplates.clear(); + + InStream in(fn); + if(!in) + return false; + + std::string line, gfx; + while (std::getline(in, line)) + { + int idx=-1, w=0, h=0; + SimpleIStringStream is(line.c_str(), SimpleIStringStream::REUSE); + is >> idx >> gfx >> w >> h; + if(idx > 0) + { + ElementTemplate t; + t.idx = idx; + t.gfx = gfx; + t.w = w; + t.h = h; + elementTemplates.push_back(t); + } + } + in.close(); + + std::sort(elementTemplates.begin(), elementTemplates.end()); + + // begin preloading textures + + std::vector usedTex; + usedTex.reserve(elementTemplates.size()); // optimistically assume all textures in the tileset are used + + for (size_t i = 0; i < elementTemplates.size(); i++) + { + size_t idx = elementTemplates[i].idx; + if (!usedIdx || (idx < usedIdxLen && usedIdx[idx])) + usedTex.push_back(elementTemplates[i].gfx); + } + std::sort(usedTex.begin(), usedTex.end()); + // 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()); + + // preload all used textures + if(usedTex.size()) + core->texmgr.loadBatch(NULL, &usedTex[0], usedTex.size()); + + return true; +} + +void Tileset::clear() +{ + elementTemplates.clear(); +} + +ElementTemplate *Tileset::getByIdx(size_t idx) +{ + for (size_t i = 0; i < elementTemplates.size(); i++) + { + if (elementTemplates[i].idx == idx) + { + return &elementTemplates[i]; + } + } + return 0; +} + +Texture* ElementTemplate::getTexture() +{ + if(tex) + return tex.content(); + + tex = core->getTexture(gfx); + if(!w) + w = tex->width; + if(!h) + h = tex->height; + + return tex.content(); + +} + diff --git a/BBGE/Tileset.h b/BBGE/Tileset.h new file mode 100644 index 0000000..42afc99 --- /dev/null +++ b/BBGE/Tileset.h @@ -0,0 +1,40 @@ +#ifndef BBGE_TILESET_H +#define BBGE_TILESET_H + +#include "Vector.h" +#include +#include "Texture.h" + +class ElementTemplate +{ +public: + ElementTemplate() { w=0; h=0; idx=-1; tu1=tv1=0; tu2=tv2=1; } + 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 tex; + unsigned w,h; // custom size if used, otherwise texture size + + // fixed + float tu1, tu2, tv1, tv2; // texcoords + size_t idx; + std::string gfx; +}; + +class Tileset +{ +public: + // 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); + + std::vector elementTemplates; +}; + + +#endif