zoom out much more smoother

This commit is contained in:
Eduardo Bart 2012-01-31 15:06:55 -02:00
parent deedef235d
commit 51b0822267
7 changed files with 195 additions and 85 deletions

View File

@ -28,7 +28,8 @@ EventDispatcher g_dispatcher;
void EventDispatcher::flush() void EventDispatcher::flush()
{ {
poll(); while(!m_eventList.empty())
poll();
while(!m_scheduledEventList.empty()) while(!m_scheduledEventList.empty())
m_scheduledEventList.pop(); m_scheduledEventList.pop();
@ -44,7 +45,8 @@ void EventDispatcher::poll()
scheduledEvent->execute(); scheduledEvent->execute();
} }
while(!m_eventList.empty()) { int maxEvents = m_eventList.size();
for(int i=0;i<maxEvents;++i) {
EventPtr event = m_eventList.front(); EventPtr event = m_eventList.front();
m_eventList.pop_front(); m_eventList.pop_front();
event->execute(); event->execute();

View File

@ -61,10 +61,10 @@ public:
TSize<T> operator/(const float v) const { return TSize<T>((T)wd/v, (T)ht/v); } TSize<T> operator/(const float v) const { return TSize<T>((T)wd/v, (T)ht/v); }
TSize<T>& operator/=(const float v) { wd/=v; ht/=v; return *this; } TSize<T>& operator/=(const float v) { wd/=v; ht/=v; return *this; }
bool operator<=(const TSize<T>&other) const { return wd<=other.wd || ht<=other.ht; } bool operator<=(const TSize<T>&other) const { return wd<=other.wd && ht<=other.ht; }
bool operator>=(const TSize<T>&other) const { return wd>=other.wd || ht>=other.ht; } bool operator>=(const TSize<T>&other) const { return wd>=other.wd && ht>=other.ht; }
bool operator<(const TSize<T>&other) const { return wd<other.wd || ht<other.ht; } bool operator<(const TSize<T>&other) const { return wd<other.wd && ht<other.ht; }
bool operator>(const TSize<T>&other) const { return wd>other.wd || ht>other.ht; } bool operator>(const TSize<T>&other) const { return wd>other.wd && ht>other.ht; }
TSize<T>& operator=(const TSize<T>& other) { wd = other.wd; ht = other.ht; return *this; } TSize<T>& operator=(const TSize<T>& other) { wd = other.wd; ht = other.ht; return *this; }
bool operator==(const TSize<T>& other) const { return other.wd==wd && other.ht==ht; } bool operator==(const TSize<T>& other) const { return other.wd==wd && other.ht==ht; }

View File

@ -216,10 +216,20 @@ TilePtr Map::createTile(const Position& pos)
return tile; return tile;
} }
const TilePtr& Map::getTile(const Position& pos)
{
auto it = m_tiles.find(pos);
if(it != m_tiles.end())
return it->second;
static TilePtr nulltile;
return nulltile;
}
void Map::cleanTile(const Position& pos) void Map::cleanTile(const Position& pos)
{ {
if(TilePtr tile = getTile(pos)) { if(TilePtr tile = getTile(pos)) {
tile->clean(); tile->clean();
m_tiles.erase(m_tiles.find(pos));
notificateTileUpdateToMapViews(pos); notificateTileUpdateToMapViews(pos);
} }

View File

@ -45,7 +45,7 @@ public:
// tile related // tile related
TilePtr createTile(const Position& pos); TilePtr createTile(const Position& pos);
const TilePtr& getTile(const Position& pos) { return m_tiles[pos]; } const TilePtr& getTile(const Position& pos);
void cleanTile(const Position& pos); void cleanTile(const Position& pos);
bool removeThing(const ThingPtr& thing); bool removeThing(const ThingPtr& thing);

View File

@ -31,12 +31,14 @@
#include "tile.h" #include "tile.h"
#include "statictext.h" #include "statictext.h"
#include "animatedtext.h" #include "animatedtext.h"
#include <framework/core/eventdispatcher.h>
MapView::MapView() MapView::MapView()
{ {
int frameBufferSize = std::min(g_graphics.getMaxTextureSize(), (int)DEFAULT_FRAMBUFFER_SIZE); Size frameBufferSize(std::min(g_graphics.getMaxTextureSize(), (int)DEFAULT_FRAMBUFFER_WIDTH),
std::min(g_graphics.getMaxTextureSize(), (int)DEFAULT_FRAMBUFFER_HEIGHT));
m_framebuffer = FrameBufferPtr(new FrameBuffer(Size(frameBufferSize, frameBufferSize))); m_framebuffer = FrameBufferPtr(new FrameBuffer(frameBufferSize));
m_framebuffer->setClearColor(Fw::black); m_framebuffer->setClearColor(Fw::black);
m_lockedFirstVisibleFloor = -1; m_lockedFirstVisibleFloor = -1;
setVisibleDimension(Size(15, 11)); setVisibleDimension(Size(15, 11));
@ -50,42 +52,47 @@ MapView::MapView()
void MapView::draw(const Rect& rect) void MapView::draw(const Rect& rect)
{ {
// update visible tiles cache when needed // update visible tiles cache when needed
bool updated = updateVisibleTilesCache(); if(m_mustUpdateVisibleTilesCache)
updateVisibleTilesCache();
float scaleFactor = m_tileSize/(float)Otc::TILE_PIXELS; float scaleFactor = m_tileSize/(float)Otc::TILE_PIXELS;
Position cameraPosition = getCameraPosition(); Position cameraPosition = getCameraPosition();
int tileDrawFlags = 0; int tileDrawFlags = 0;
if(isNearView()) if(m_viewRange == NEAR_VIEW)
tileDrawFlags = Otc::DrawGround | Otc::DrawWalls | Otc::DrawCommonItems | Otc::DrawCreatures | Otc::DrawEffects; tileDrawFlags = Otc::DrawGround | Otc::DrawWalls | Otc::DrawCommonItems | Otc::DrawCreatures | Otc::DrawEffects;
else if(isMidView()) else if(m_viewRange == MID_VIEW)
tileDrawFlags = Otc::DrawGround | Otc::DrawWalls | Otc::DrawCommonItems; tileDrawFlags = Otc::DrawGround | Otc::DrawWalls | Otc::DrawCommonItems;
else if(isFarView()) else if(m_viewRange == FAR_VIEW)
tileDrawFlags = Otc::DrawGround | Otc::DrawWalls; tileDrawFlags = Otc::DrawGround | Otc::DrawWalls | Otc::DrawCommonItems;
else // huge far view else // HUGE_VIEW
tileDrawFlags = Otc::DrawGround; tileDrawFlags = Otc::DrawGround;
bool animate = m_animated; bool animate = m_animated;
// avoid animation of mid/far views // only animate in near views
if(!isNearView()) if(m_viewRange != NEAR_VIEW)
animate = false; animate = false;
if(updated || animate) { if(m_mustDrawVisibleTilesCache || animate) {
m_framebuffer->bind(); m_framebuffer->bind(m_mustCleanFramebuffer);
if(m_mustCleanFramebuffer)
m_mustCleanFramebuffer = false;
for(const TilePtr& tile : m_cachedVisibleTiles) { for(const TilePtr& tile : m_cachedVisibleTiles) {
tile->draw(transformPositionTo2D(tile->getPosition()), scaleFactor, tileDrawFlags); tile->draw(transformPositionTo2D(tile->getPosition()), scaleFactor, tileDrawFlags);
//TODO: restore missiles //TODO: restore missiles
} }
m_framebuffer->generateMipmaps(); m_framebuffer->generateMipmaps();
m_framebuffer->release(); m_framebuffer->release();
m_mustDrawVisibleTilesCache = false;
} }
g_painter.setCustomProgram(m_shaderProgram); g_painter.setCustomProgram(m_shaderProgram);
g_painter.setColor(Fw::white); g_painter.setColor(Fw::white);
Point drawOffset(m_tileSize, m_tileSize); Point drawOffset = ((m_drawDimension - m_visibleDimension - Size(1,1)).toPoint()/2) * m_tileSize;
if(m_followingCreature) if(m_followingCreature)
drawOffset += m_followingCreature->getWalkOffset() * scaleFactor; drawOffset += m_followingCreature->getWalkOffset() * scaleFactor;
Rect srcRect = Rect(drawOffset, m_visibleDimension * m_tileSize); Rect srcRect = Rect(drawOffset, m_visibleDimension * m_tileSize);
@ -150,89 +157,104 @@ void MapView::draw(const Rect& rect)
} }
// draw a arrow for position the center in non near views // draw a arrow for position the center in non near views
if(!isNearView()) { if(m_viewRange != NEAR_VIEW) {
g_painter.setColor(Fw::red); g_painter.setColor(Fw::red);
g_painter.drawFilledRect(Rect(rect.center(), 4, 4)); g_painter.drawFilledRect(Rect(rect.center(), 4, 4));
} }
} }
bool MapView::updateVisibleTilesCache() void MapView::updateVisibleTilesCache(int start)
{ {
// update only when needed if(start == 0) {
if(!m_mustUpdateVisibleTilesCache) if(m_updateTilesCacheEvent) {
return false; m_updateTilesCacheEvent->cancel();
m_updateTilesCacheEvent = nullptr;
}
int firstFloor = getFirstVisibleFloor(); m_cachedFirstVisibleFloor = getFirstVisibleFloor();
int lastFloor = getLastVisibleFloor(); m_cachedLastVisibleFloor = getLastVisibleFloor();
if(lastFloor < firstFloor) if(m_cachedLastVisibleFloor < m_cachedFirstVisibleFloor)
lastFloor = firstFloor; m_cachedLastVisibleFloor = m_cachedFirstVisibleFloor;
m_cachedFirstVisibleFloor = firstFloor; m_cachedFloorVisibleCreatures.clear();
m_cachedLastVisibleFloor = lastFloor; m_cachedVisibleTiles.clear();
m_mustCleanFramebuffer = true;
m_mustDrawVisibleTilesCache = true;
m_mustUpdateVisibleTilesCache = false;
}
// there is no tile to render on invalid positions
Position cameraPosition = getCameraPosition(); Position cameraPosition = getCameraPosition();
if(!cameraPosition.isValid())
return;
int count = 0;
bool stop = false;
// clear current visible tiles cache // clear current visible tiles cache
m_cachedVisibleTiles.clear(); m_cachedVisibleTiles.clear();
m_cachedFloorVisibleCreatures.clear(); m_mustDrawVisibleTilesCache = true;
// there is no tile to render on invalid positions
if(!cameraPosition.isValid())
return true;
// cache visible tiles in draw order // cache visible tiles in draw order
// draw from last floor (the lower) to first floor (the higher) // draw from last floor (the lower) to first floor (the higher)
for(int iz = m_cachedLastVisibleFloor; iz >= m_cachedFirstVisibleFloor; --iz) { for(int iz = m_cachedLastVisibleFloor; iz >= m_cachedFirstVisibleFloor && !stop; --iz) {
// draw tiles like linus pauling's rule order // draw tiles like linus pauling's rule order
const int numDiagonals = m_drawDimension.width() + m_drawDimension.height() - 1; const int numDiagonals = m_drawDimension.width() + m_drawDimension.height() - 1;
for(int diagonal = 0; diagonal < numDiagonals; ++diagonal) { for(int diagonal = 0; diagonal < numDiagonals && !stop; ++diagonal) {
// loop through / diagonal tiles // loop through / diagonal tiles
for(int ix = std::min(diagonal, m_drawDimension.width() - 1), iy = std::max(diagonal - m_drawDimension.width() + 1, 0); ix >= 0 && iy < m_drawDimension.height(); --ix, ++iy) { for(int ix = std::min(diagonal, m_drawDimension.width() - 1), iy = std::max(diagonal - m_drawDimension.width() + 1, 0); ix >= 0 && iy < m_drawDimension.height() && !stop; --ix, ++iy) {
// only start really looking tiles in the desired start
if(count < start) {
count++;
continue;
}
// avoid rendering too much tiles at once on far views
if(count - start + 1 > MAX_TILE_UPDATES && m_viewRange >= FAR_VIEW) {
stop = true;
break;
}
// position on current floor // position on current floor
//TODO: check position limits
Position tilePos = cameraPosition.translated(ix - m_virtualCenterOffset.x, iy - m_virtualCenterOffset.y); Position tilePos = cameraPosition.translated(ix - m_virtualCenterOffset.x, iy - m_virtualCenterOffset.y);
// adjust tilePos to the wanted floor // adjust tilePos to the wanted floor
tilePos.coveredUp(cameraPosition.z - iz); tilePos.coveredUp(cameraPosition.z - iz);
if(const TilePtr& tile = g_map.getTile(tilePos)) { if(const TilePtr& tile = g_map.getTile(tilePos)) {
// skip tiles that have nothing // skip tiles that have nothing
if(tile->getThingCount() == 0) if(tile->isEmpty())
continue; continue;
// skip tiles that are completely behind another tile // skip tiles that are completely behind another tile
if(g_map.isCompletelyCovered(tilePos, firstFloor)) if(g_map.isCompletelyCovered(tilePos, m_cachedLastVisibleFloor))
continue; continue;
m_cachedVisibleTiles.push_back(tile); m_cachedVisibleTiles.push_back(tile);
} }
count++;
} }
} }
/*
for(int range=0; range <= m_drawDimension.width() || range <= m_drawDimension.height(); ++range) {
for(int ix=0;ix<=range;++ix) {
}
}*/
} }
m_cachedFloorVisibleCreatures = g_map.getSpectators(cameraPosition, false); if(stop) {
// schedule next update continuation
m_mustUpdateVisibleTilesCache = false; m_updateTilesCacheEvent = g_dispatcher.addEvent(std::bind(&MapView::updateVisibleTilesCache, asMapView(), count));
return true;
}
void MapView::recalculateTileSize()
{
int possiblesTileSizes[] = {32,16,8,4,2,1};
int foundSize = 0;
for(int candidateTileSize : possiblesTileSizes) {
Size candidateFramebufferSize = m_drawDimension * candidateTileSize;
// found a valid size
if(candidateFramebufferSize.width() <= m_framebuffer->getSize().width() && candidateFramebufferSize.height() <= m_framebuffer->getSize().height()) {
foundSize = candidateTileSize;
break;
}
} }
assert(foundSize > 0); if(start == 0)
m_tileSize = foundSize; m_cachedFloorVisibleCreatures = g_map.getSpectators(cameraPosition, false);
} }
void MapView::onTileUpdate(const Position& pos) void MapView::onTileUpdate(const Position& pos)
{ {
requestVisibleTilesCacheUpdate(); //if(m_viewRange == NEAR_VIEW)
requestVisibleTilesCacheUpdate();
} }
void MapView::lockFirstVisibleFloor(int firstVisibleFloor) void MapView::lockFirstVisibleFloor(int firstVisibleFloor)
@ -269,16 +291,72 @@ void MapView::setVisibleDimension(const Size& visibleDimension)
} }
if(visibleDimension.width() <= 3 || visibleDimension.height() <= 3) { if(visibleDimension.width() <= 3 || visibleDimension.height() <= 3) {
logTraceError("cannot render less than 3x3 tiles"); logTraceError("reach max zoom in");
return; return;
} }
m_visibleDimension = visibleDimension; int possiblesTileSizes[] = {32,16,8,4,2,1};
m_drawDimension = visibleDimension + Size(3,3); int tileSize = 0;
m_virtualCenterOffset = (m_drawDimension/2 - Size(1,1)).toPoint(); Size drawDimension = visibleDimension + Size(3,3);
recalculateTileSize(); for(int candidateTileSize : possiblesTileSizes) {
Size candidateDrawSize = drawDimension * candidateTileSize;
requestVisibleTilesCacheUpdate(); // found a valid size
if(candidateDrawSize <= m_framebuffer->getSize()) {
tileSize = candidateTileSize;
break;
}
}
if(tileSize == 0) {
logTraceError("reached max zoom out");
return;
}
if(tileSize != m_tileSize) {
dump << "tile size =" << tileSize;
}
Point virtualCenterOffset = (drawDimension/2 - Size(1,1)).toPoint();
ViewRange viewRange;
if(visibleDimension.area() <= NEAR_VIEW_AREA)
viewRange = NEAR_VIEW;
else if(visibleDimension.area() <= MID_VIEW_AREA)
viewRange = MID_VIEW;
else if(visibleDimension.area() <= FAR_VIEW_AREA)
viewRange = FAR_VIEW;
else
viewRange = HUGE_VIEW;
dump << visibleDimension;
if(m_viewRange != viewRange) {
if(viewRange == NEAR_VIEW) dump << "near view";
else if(viewRange == MID_VIEW) dump << "mid view";
else if(viewRange == FAR_VIEW) dump << "far view";
else if(viewRange == HUGE_VIEW) dump << "huge view";
}
// draw actually more than what is needed to avoid massive recalculations on far views
if(viewRange >= FAR_VIEW) {
Size oldDimension = drawDimension;
drawDimension = (m_framebuffer->getSize() / tileSize);
virtualCenterOffset += (drawDimension - oldDimension).toPoint() / 2;
}
bool mustUpdate = (m_drawDimension != drawDimension ||
m_viewRange != viewRange ||
m_virtualCenterOffset != virtualCenterOffset ||
m_tileSize != tileSize);
m_visibleDimension = visibleDimension;
m_drawDimension = drawDimension;
m_tileSize = tileSize;
m_viewRange = viewRange;
m_virtualCenterOffset = virtualCenterOffset;
if(mustUpdate)
requestVisibleTilesCacheUpdate();
} }
int MapView::getFirstVisibleFloor() int MapView::getFirstVisibleFloor()
@ -290,7 +368,7 @@ int MapView::getFirstVisibleFloor()
Position cameraPosition = getCameraPosition(); Position cameraPosition = getCameraPosition();
// avoid rendering multile floors on far views // avoid rendering multile floors on far views
if(!isNearView() && !isMidView()) if(m_viewRange >= FAR_VIEW)
return cameraPosition.z; return cameraPosition.z;
// if nothing is limiting the view, the first visible floor is 0 // if nothing is limiting the view, the first visible floor is 0
@ -324,9 +402,6 @@ int MapView::getFirstVisibleFloor()
firstFloor = coveredPos.z + 1; firstFloor = coveredPos.z + 1;
break; break;
} }
if(upperPos.z == 0)
break;
} }
} }
} }
@ -340,7 +415,7 @@ int MapView::getLastVisibleFloor()
Position cameraPosition = getCameraPosition(); Position cameraPosition = getCameraPosition();
// avoid rendering multile floors on far views // avoid rendering multile floors on far views
if(!isNearView() && !isMidView()) if(m_viewRange >= FAR_VIEW)
return cameraPosition.z; return cameraPosition.z;
// view only underground floors when below sea level // view only underground floors when below sea level

View File

@ -26,14 +26,28 @@
#include "declarations.h" #include "declarations.h"
#include <framework/graphics/declarations.h> #include <framework/graphics/declarations.h>
#include <framework/luascript/luaobject.h> #include <framework/luascript/luaobject.h>
#include <framework/core/declarations.h>
class MapView : public LuaObject class MapView : public LuaObject
{ {
enum { enum {
DEFAULT_FRAMBUFFER_SIZE = 3840, // 3840x2160 => 1080p optimized
NEAR_VIEW_AREA = 64*64, // 2560x1440 => 720p optimized
MID_VIEW_AREA = 128*128, // 1728x972 => 480p optimized
FAR_VIEW_AREA = 256*256 DEFAULT_FRAMBUFFER_WIDTH = 3840,
DEFAULT_FRAMBUFFER_HEIGHT = 2160,
NEAR_VIEW_AREA = 48*48,
MID_VIEW_AREA = 96*96,
FAR_VIEW_AREA = 384*384,
MAX_TILE_UPDATES = NEAR_VIEW_AREA*7
};
enum ViewRange {
NEAR_VIEW,
MID_VIEW,
FAR_VIEW,
HUGE_VIEW
}; };
public: public:
@ -41,8 +55,7 @@ public:
void draw(const Rect& rect); void draw(const Rect& rect);
private: private:
void recalculateTileSize(); void updateVisibleTilesCache(int start = 0);
bool updateVisibleTilesCache();
void requestVisibleTilesCacheUpdate() { m_mustUpdateVisibleTilesCache = true; } void requestVisibleTilesCacheUpdate() { m_mustUpdateVisibleTilesCache = true; }
protected: protected:
@ -71,10 +84,7 @@ public:
Size getVisibleSize() { return m_visibleDimension * m_tileSize; } Size getVisibleSize() { return m_visibleDimension * m_tileSize; }
CreaturePtr getFollowingCreature() { return m_followingCreature; } CreaturePtr getFollowingCreature() { return m_followingCreature; }
bool isNearView() { return m_drawDimension.area() <= NEAR_VIEW_AREA; } bool getViewRange() { return m_viewRange; }
bool isMidView() { return m_drawDimension.area() > NEAR_VIEW_AREA && m_drawDimension.area() <= MID_VIEW_AREA; }
bool isFarView() { return m_drawDimension.area() > MID_VIEW_AREA && m_drawDimension.area() <= FAR_VIEW_AREA; }
bool isHugeFarView() { return m_drawDimension.area() > FAR_VIEW_AREA; }
bool isAnimated() { return m_animated; } bool isAnimated() { return m_animated; }
Point transformPositionTo2D(const Position& position); Point transformPositionTo2D(const Position& position);
@ -91,13 +101,18 @@ private:
Size m_visibleDimension; Size m_visibleDimension;
Point m_virtualCenterOffset; Point m_virtualCenterOffset;
Position m_customCameraPosition; Position m_customCameraPosition;
Position m_framebufferCenterPosition;
Boolean<true> m_mustUpdateVisibleTilesCache; Boolean<true> m_mustUpdateVisibleTilesCache;
Boolean<true> m_mustDrawVisibleTilesCache;
Boolean<true> m_mustCleanFramebuffer;
Boolean<true> m_animated; Boolean<true> m_animated;
std::vector<TilePtr> m_cachedVisibleTiles; std::vector<TilePtr> m_cachedVisibleTiles;
std::vector<CreaturePtr> m_cachedFloorVisibleCreatures; std::vector<CreaturePtr> m_cachedFloorVisibleCreatures;
EventPtr m_updateTilesCacheEvent;
CreaturePtr m_followingCreature; CreaturePtr m_followingCreature;
FrameBufferPtr m_framebuffer; FrameBufferPtr m_framebuffer;
PainterShaderProgramPtr m_shaderProgram; PainterShaderProgramPtr m_shaderProgram;
ViewRange m_viewRange;
}; };
#endif #endif

View File

@ -40,6 +40,14 @@ void Tile::draw(const Point& dest, float scaleFactor, int drawFlags)
{ {
int drawElevation = 0; int drawElevation = 0;
// optimization far far views
if(drawFlags == Otc::DrawGround) {
const ThingPtr& thing = m_things.front();
if(thing)
thing->draw(dest, scaleFactor);
return;
}
// first bottom items // first bottom items
if(drawFlags & Otc::DrawGround || drawFlags & Otc::DrawWalls) { if(drawFlags & Otc::DrawGround || drawFlags & Otc::DrawWalls) {
for(const ThingPtr& thing : m_things) { for(const ThingPtr& thing : m_things) {