Savegame: store fog of war (Closes #1177)

deque
scrawl 11 years ago
parent 7b46e9f914
commit a4a9794417

@ -9,6 +9,8 @@
#include <OgreRenderTexture.h> #include <OgreRenderTexture.h>
#include <OgreViewport.h> #include <OgreViewport.h>
#include <components/esm/fogstate.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -46,7 +48,6 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag
LocalMap::~LocalMap() LocalMap::~LocalMap()
{ {
deleteBuffers();
} }
const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle) const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle)
@ -55,59 +56,83 @@ const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Ve
Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y); Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y);
} }
void LocalMap::deleteBuffers() std::string LocalMap::coordStr(const int x, const int y)
{ {
mBuffers.clear(); return StringConverter::toString(x) + "_" + StringConverter::toString(y);
} }
void LocalMap::saveTexture(const std::string& texname, const std::string& filename) void LocalMap::clear()
{ {
TexturePtr tex = TextureManager::getSingleton().getByName(texname); // Not actually removing the Textures here. That doesnt appear to work properly. It seems MyGUI still keeps some pointers.
if (tex.isNull()) return; mBuffers.clear();
HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer();
readbuffer->lock(HardwareBuffer::HBL_NORMAL );
const PixelBox &readrefpb = readbuffer->getCurrentLock();
uchar *readrefdata = static_cast<uchar*>(readrefpb.data);
Image img;
img = img.loadDynamicImage (readrefdata, tex->getWidth(),
tex->getHeight(), tex->getFormat());
img.save("./" + filename);
readbuffer->unlock();
}
std::string LocalMap::coordStr(const int x, const int y)
{
return StringConverter::toString(x) + "_" + StringConverter::toString(y);
} }
void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
{ {
if (!mInterior) if (!mInterior)
{ {
/*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog", std::string textureName = "Cell_"+coordStr(cell->getCell()->getGridX(), cell->getCell()->getGridY())+"_fog";
"Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/ std::auto_ptr<ESM::FogState> fog (new ESM::FogState());
fog->mFogTextures.push_back(ESM::FogTexture());
TexturePtr tex = TextureManager::getSingleton().getByName(textureName);
if (tex.isNull())
return;
Ogre::Image image;
tex->convertToImage(image);
Ogre::DataStreamPtr encoded = image.encode("tga");
fog->mFogTextures.back().mImageData.resize(encoded->size());
encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size());
cell->setFog(fog.release());
} }
else else
{ {
Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y);
Vector2 length = max-min; Vector2 length = max-min;
// divide into segments
const int segsX = std::ceil( length.x / sSize ); const int segsX = std::ceil( length.x / sSize );
const int segsY = std::ceil( length.y / sSize ); const int segsY = std::ceil( length.y / sSize );
mInteriorName = cell->getCell()->mName;
std::auto_ptr<ESM::FogState> fog (new ESM::FogState());
fog->mBounds.mMinX = mBounds.getMinimum().x;
fog->mBounds.mMaxX = mBounds.getMaximum().x;
fog->mBounds.mMinY = mBounds.getMinimum().y;
fog->mBounds.mMaxY = mBounds.getMaximum().y;
fog->mNorthMarkerAngle = mAngle;
fog->mFogTextures.reserve(segsX*segsY);
for (int x=0; x<segsX; ++x) for (int x=0; x<segsX; ++x)
{ {
for (int y=0; y<segsY; ++y) for (int y=0; y<segsY; ++y)
{ {
/*saveTexture( std::string textureName = cell->getCell()->mName + "_" + coordStr(x,y) + "_fog";
mInteriorName + "_" + coordStr(x,y) + "_fog",
mInteriorName + "_" + coordStr(x,y) + "_fog.png");*/ TexturePtr tex = TextureManager::getSingleton().getByName(textureName);
if (tex.isNull())
return;
Ogre::Image image;
tex->convertToImage(image);
fog->mFogTextures.push_back(ESM::FogTexture());
Ogre::DataStreamPtr encoded = image.encode("tga");
fog->mFogTextures.back().mImageData.resize(encoded->size());
encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size());
fog->mFogTextures.back().mX = x;
fog->mFogTextures.back().mY = y;
} }
} }
cell->setFog(fog.release());
} }
} }
@ -126,29 +151,34 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax)
mCameraPosNode->setPosition(Vector3(0,0,0)); mCameraPosNode->setPosition(Vector3(0,0,0));
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
if (mBuffers.find(name) == mBuffers.end())
{
if (cell->getFog())
loadFogOfWar(name, cell->getFog()->mFogTextures.back());
else
createFogOfWar(name);
}
} }
void LocalMap::requestMap(MWWorld::CellStore* cell, void LocalMap::requestMap(MWWorld::CellStore* cell,
AxisAlignedBox bounds) AxisAlignedBox bounds)
{ {
// if we're in an empty cell, don't bother rendering anything // If we're in an empty cell, bail out
// The operations in this function are only valid for finite bounds
if (bounds.isNull ()) if (bounds.isNull ())
return; return;
mInterior = true; mInterior = true;
mBounds = bounds;
float zMin = mBounds.getMinimum().z; mBounds = bounds;
float zMax = mBounds.getMaximum().z;
// Get the cell's NorthMarker rotation. This is used to rotate the entire map.
const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell); const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell);
Radian angle = Ogre::Math::ATan2 (north.x, north.y); Radian angle = Ogre::Math::ATan2 (north.x, north.y) + Ogre::Degree(2);
mAngle = angle.valueRadians(); mAngle = angle.valueRadians();
mCellCamera->setOrientation(Quaternion::IDENTITY); // Rotate the cell and merge the rotated corners to the bounding box
mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f)));
// rotate the cell and merge the rotated corners to the bounding box
Vector2 _center(bounds.getCenter().x, bounds.getCenter().y); Vector2 _center(bounds.getCenter().x, bounds.getCenter().y);
Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM);
Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM);
@ -168,9 +198,48 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
mBounds.merge(Vector3(c3.x, c3.y, 0)); mBounds.merge(Vector3(c3.x, c3.y, 0));
mBounds.merge(Vector3(c4.x, c4.y, 0)); mBounds.merge(Vector3(c4.x, c4.y, 0));
// apply a little padding // Do NOT change padding! This will break older savegames.
mBounds.setMinimum (mBounds.getMinimum() - Vector3(500,500,0)); // If the padding really needs to be changed, then it must be saved in the ESM::FogState and
mBounds.setMaximum (mBounds.getMaximum() + Vector3(500,500,0)); // assume the old (500) value as default for older savegames.
const int padding = 500;
// Apply a little padding
mBounds.setMinimum (mBounds.getMinimum() - Vector3(padding,padding,0));
mBounds.setMaximum (mBounds.getMaximum() + Vector3(padding,padding,0));
float zMin = mBounds.getMinimum().z;
float zMax = mBounds.getMaximum().z;
// If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks
// to see if this state is still valid.
// Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models.
// If they changed by too much (for bounds, < padding is considered acceptable) then parts of the interior might not
// be covered by the map anymore.
// The following code detects this, and discards the CellStore's fog state if it needs to.
if (cell->getFog())
{
ESM::FogState* fog = cell->getFog();
Ogre::Vector3 newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin);
Ogre::Vector3 newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax);
Ogre::Vector3 minDiff = newMin - mBounds.getMinimum();
Ogre::Vector3 maxDiff = newMax - mBounds.getMaximum();
if (std::abs(minDiff.x) > 500 || std::abs(minDiff.y) > 500
|| std::abs(maxDiff.x) > 500 || std::abs(maxDiff.y) > 500
|| std::abs(mAngle - fog->mNorthMarkerAngle) > Ogre::Degree(5).valueRadians())
{
// Nuke it
cell->setFog(NULL);
}
else
{
// Looks sane, use it
mBounds = Ogre::AxisAlignedBox(newMin, newMax);
mAngle = fog->mNorthMarkerAngle;
}
}
Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y); Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y);
@ -179,6 +248,9 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
Vector2 length = max-min; Vector2 length = max-min;
mCellCamera->setOrientation(Quaternion::IDENTITY);
mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f)));
mCameraPosNode->setPosition(Vector3(center.x, center.y, 0)); mCameraPosNode->setPosition(Vector3(center.x, center.y, 0));
// divide into segments // divide into segments
@ -187,19 +259,96 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
mInteriorName = cell->getCell()->mName; mInteriorName = cell->getCell()->mName;
int i=0;
for (int x=0; x<segsX; ++x) for (int x=0; x<segsX; ++x)
{ {
for (int y=0; y<segsY; ++y) for (int y=0; y<segsY; ++y)
{ {
Vector2 start = min + Vector2(sSize*x,sSize*y); Vector2 start = min + Vector2(sSize*x,sSize*y);
Vector2 newcenter = start + 4096; Vector2 newcenter = start + sSize/2;
std::string texturePrefix = cell->getCell()->mName + "_" + coordStr(x,y);
render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, texturePrefix);
if (!cell->getFog())
createFogOfWar(texturePrefix);
else
{
ESM::FogState* fog = cell->getFog();
// We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same.
assert (i < int(fog->mFogTextures.size()));
render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, ESM::FogTexture& esm = fog->mFogTextures[i];
cell->getCell()->mName + "_" + coordStr(x,y)); loadFogOfWar(texturePrefix, esm);
}
++i;
} }
} }
} }
void LocalMap::createFogOfWar(const std::string& texturePrefix)
{
const std::string texName = texturePrefix + "_fog";
TexturePtr tex = createFogOfWarTexture(texName);
// create a buffer to use for dynamic operations
std::vector<uint32> buffer;
// initialize to (0, 0, 0, 1)
buffer.resize(sFogOfWarResolution*sFogOfWarResolution, (255 << 24));
// upload to the texture
memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
tex->getBuffer()->unlock();
mBuffers[texturePrefix] = buffer;
}
Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName)
{
TexturePtr tex = TextureManager::getSingleton().getByName(texName);
if (tex.isNull())
{
tex = TextureManager::getSingleton().createManual(
texName,
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D,
sFogOfWarResolution, sFogOfWarResolution,
0,
PF_A8R8G8B8,
TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
}
else
tex->unload();
return tex;
}
void LocalMap::loadFogOfWar (const std::string& texturePrefix, ESM::FogTexture& esm)
{
std::vector<char>& data = esm.mImageData;
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
Ogre::Image image;
image.load(stream, "tga");
assert (image.getWidth() == sFogOfWarResolution && image.getHeight() == sFogOfWarResolution);
std::string texName = texturePrefix + "_fog";
Ogre::TexturePtr tex = createFogOfWarTexture(texName);
tex->loadImage(image);
// create a buffer to use for dynamic operations
std::vector<uint32> buffer;
buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
memcpy(&buffer[0], image.getData(), image.getSize());
mBuffers[texturePrefix] = buffer;
}
void LocalMap::render(const float x, const float y, void LocalMap::render(const float x, const float y,
const float zlow, const float zhigh, const float zlow, const float zhigh,
const float xw, const float yw, const std::string& texture) const float xw, const float yw, const std::string& texture)
@ -249,31 +398,6 @@ void LocalMap::render(const float x, const float y,
vp->setMaterialScheme("local_map"); vp->setMaterialScheme("local_map");
rtt->update(); rtt->update();
// create "fog of war" texture
TexturePtr tex2 = TextureManager::getSingleton().createManual(
texture + "_fog",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D,
xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize,
0,
PF_A8R8G8B8,
TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
// create a buffer to use for dynamic operations
std::vector<uint32> buffer;
buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
// initialize to (0, 0, 0, 1)
for (int p=0; p<sFogOfWarResolution*sFogOfWarResolution; ++p)
{
buffer[p] = (255 << 24);
}
memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
tex2->getBuffer()->unlock();
mBuffers[texture] = buffer;
} }
mRenderingManager->enableLights(true); mRenderingManager->enableLights(true);
@ -304,6 +428,9 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi
if (mBuffers.find(texName) == mBuffers.end()) if (mBuffers.find(texName) == mBuffers.end())
return false; return false;
nX = std::max(0.f, std::min(1.f, nX));
nY = std::max(0.f, std::min(1.f, nY));
int texU = (sFogOfWarResolution-1) * nX; int texU = (sFogOfWarResolution-1) * nX;
int texV = (sFogOfWarResolution-1) * nY; int texV = (sFogOfWarResolution-1) * nY;
@ -414,6 +541,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
} }
// copy to the texture // copy to the texture
// NOTE: Could be optimized later. We actually only need to update the region that changed.
// Not a big deal at the moment, the FoW is only 32x32 anyway.
memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4); memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
tex->getBuffer()->unlock(); tex->getBuffer()->unlock();
} }

@ -11,6 +11,11 @@ namespace MWWorld
class CellStore; class CellStore;
} }
namespace ESM
{
class FogTexture;
}
namespace MWRender namespace MWRender
{ {
class RenderingManager; class RenderingManager;
@ -24,6 +29,11 @@ namespace MWRender
LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering); LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering);
~LocalMap(); ~LocalMap();
/**
* Clear all savegame-specific data (i.e. fog of war textures)
*/
void clear();
/** /**
* Request the local map for an exterior cell. * Request the local map for an exterior cell.
* @remarks It will either be loaded from a disk cache, * @remarks It will either be loaded from a disk cache,
@ -54,10 +64,8 @@ namespace MWRender
void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation); void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation);
/** /**
* Save the fog of war for the current cell to disk. * Save the fog of war for this cell to its CellStore.
* @remarks This should be called before loading a * @remarks This should be called when unloading a cell, and for all active cells prior to saving the game.
* new cell, as well as when the game is quit.
* @param current cell
*/ */
void saveFogOfWar(MWWorld::CellStore* cell); void saveFogOfWar(MWWorld::CellStore* cell);
@ -104,17 +112,20 @@ namespace MWRender
const float xw, const float yw, const float xw, const float yw,
const std::string& texture); const std::string& texture);
void saveTexture(const std::string& texname, const std::string& filename); // Creates a fog of war texture and initializes it to full black
void createFogOfWar(const std::string& texturePrefix);
// Loads a fog of war texture from its ESM struct
void loadFogOfWar(const std::string& texturePrefix, ESM::FogTexture& esm); // FogTexture not const because MemoryDataStream doesn't accept it
Ogre::TexturePtr createFogOfWarTexture(const std::string& name);
std::string coordStr(const int x, const int y); std::string coordStr(const int x, const int y);
// a buffer for the "fog of war" texture of the current cell. // A buffer for the "fog of war" textures of the current cell.
// interior cells could be divided into multiple textures, // Both interior and exterior maps are possibly divided into multiple textures.
// so we store in a map.
std::map <std::string, std::vector<Ogre::uint32> > mBuffers; std::map <std::string, std::vector<Ogre::uint32> > mBuffers;
void deleteBuffers();
bool mInterior; bool mInterior;
int mCellX, mCellY; int mCellX, mCellY;
Ogre::AxisAlignedBox mBounds; Ogre::AxisAlignedBox mBounds;

@ -216,13 +216,14 @@ OEngine::Render::Fader* RenderingManager::getFader()
return mRendering.getFader(); return mRendering.getFader();
} }
MWRender::Camera* RenderingManager::getCamera() const MWRender::Camera* RenderingManager::getCamera() const
{ {
return mCamera; return mCamera;
} }
void RenderingManager::removeCell (MWWorld::CellStore *store) void RenderingManager::removeCell (MWWorld::CellStore *store)
{ {
mLocalMap->saveFogOfWar(store);
mObjects->removeCell(store); mObjects->removeCell(store);
mActors->removeCell(store); mActors->removeCell(store);
mDebugging->cellRemoved(store); mDebugging->cellRemoved(store);
@ -671,7 +672,7 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell)
mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); mLocalMap->requestMap(cell, mObjects->getDimensions(cell));
} }
void RenderingManager::preCellChange(MWWorld::CellStore* cell) void RenderingManager::writeFog(MWWorld::CellStore* cell)
{ {
mLocalMap->saveFogOfWar(cell); mLocalMap->saveFogOfWar(cell);
} }
@ -1057,4 +1058,9 @@ void RenderingManager::spawnEffect(const std::string &model, const std::string &
mEffectManager->addEffect(model, texture, worldPosition, scale); mEffectManager->addEffect(model, texture, worldPosition, scale);
} }
void RenderingManager::clear()
{
mLocalMap->clear();
}
} // namespace } // namespace

@ -107,12 +107,15 @@ public:
void cellAdded (MWWorld::CellStore *store); void cellAdded (MWWorld::CellStore *store);
void waterAdded(MWWorld::CellStore *store); void waterAdded(MWWorld::CellStore *store);
/// Clear all savegame-specific data (i.e. fog of war textures)
void clear();
void enableTerrain(bool enable); void enableTerrain(bool enable);
void removeWater(); void removeWater();
void preCellChange (MWWorld::CellStore* store); /// Write current fog of war for this cell to the CellStore
///< this event is fired immediately before changing cell void writeFog (MWWorld::CellStore* store);
void addObject (const MWWorld::Ptr& ptr); void addObject (const MWWorld::Ptr& ptr);
void removeObject (const MWWorld::Ptr& ptr); void removeObject (const MWWorld::Ptr& ptr);

@ -78,6 +78,7 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const
writer.startRecord (ESM::REC_CSTA); writer.startRecord (ESM::REC_CSTA);
cellState.mId.save (writer); cellState.mId.save (writer);
cellState.save (writer); cellState.save (writer);
cell.writeFog(writer);
cell.writeReferences (writer); cell.writeReferences (writer);
writer.endRecord (ESM::REC_CSTA); writer.endRecord (ESM::REC_CSTA);
} }
@ -319,6 +320,9 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type,
state.load (reader); state.load (reader);
cellStore->loadState (state); cellStore->loadState (state);
if (state.mHasFogOfWar)
cellStore->readFog(reader);
if (cellStore->getState()!=CellStore::State_Loaded) if (cellStore->getState()!=CellStore::State_Loaded)
cellStore->load (mStore, mReader); cellStore->load (mStore, mReader);

@ -11,6 +11,7 @@
#include <components/esm/containerstate.hpp> #include <components/esm/containerstate.hpp>
#include <components/esm/npcstate.hpp> #include <components/esm/npcstate.hpp>
#include <components/esm/creaturestate.hpp> #include <components/esm/creaturestate.hpp>
#include <components/esm/fogstate.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -533,6 +534,21 @@ namespace MWWorld
state.mWaterLevel = mWaterLevel; state.mWaterLevel = mWaterLevel;
state.mWaterLevel = mWaterLevel; state.mWaterLevel = mWaterLevel;
state.mHasFogOfWar = (mFogState.get() ? 1 : 0);
}
void CellStore::writeFog(ESM::ESMWriter &writer) const
{
if (mFogState.get())
{
mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior);
}
}
void CellStore::readFog(ESM::ESMReader &reader)
{
mFogState.reset(new ESM::FogState());
mFogState->load(reader);
} }
void CellStore::writeReferences (ESM::ESMWriter& writer) const void CellStore::writeReferences (ESM::ESMWriter& writer) const
@ -697,4 +713,14 @@ namespace MWWorld
{ {
return mPathgridGraph.aStarSearch(start, end); return mPathgridGraph.aStarSearch(start, end);
} }
void CellStore::setFog(ESM::FogState *fog)
{
mFogState.reset(fog);
}
ESM::FogState* CellStore::getFog() const
{
return mFogState.get();
}
} }

@ -3,22 +3,28 @@
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
#include <boost/shared_ptr.hpp>
#include "livecellref.hpp" #include "livecellref.hpp"
#include "esmstore.hpp" #include "esmstore.hpp"
#include "cellreflist.hpp" #include "cellreflist.hpp"
#include <components/esm/fogstate.hpp>
#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld
namespace ESM namespace ESM
{ {
struct CellState; struct CellState;
struct FogState;
} }
namespace MWWorld namespace MWWorld
{ {
class Ptr; class Ptr;
/// \brief Mutable state of a cell /// \brief Mutable state of a cell
class CellStore class CellStore
{ {
@ -31,6 +37,11 @@ namespace MWWorld
private: private:
// Even though fog actually belongs to the player and not cells,
// it makes sense to store it here since we need it once for each cell.
// Note this is NULL until the cell is explored to save some memory
boost::shared_ptr<ESM::FogState> mFogState;
const ESM::Cell *mCell; const ESM::Cell *mCell;
State mState; State mState;
bool mHasState; bool mHasState;
@ -84,6 +95,11 @@ namespace MWWorld
void setWaterLevel (float level); void setWaterLevel (float level);
void setFog (ESM::FogState* fog);
///< \note Takes ownership of the pointer
ESM::FogState* getFog () const;
int count() const; int count() const;
///< Return total number of references, including deleted ones. ///< Return total number of references, including deleted ones.
@ -134,6 +150,10 @@ namespace MWWorld
void saveState (ESM::CellState& state) const; void saveState (ESM::CellState& state) const;
void writeFog (ESM::ESMWriter& writer) const;
void readFog (ESM::ESMReader& reader);
void writeReferences (ESM::ESMWriter& writer) const; void writeReferences (ESM::ESMWriter& writer) const;
void readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap); void readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap);

@ -231,6 +231,8 @@ namespace MWWorld
void World::clear() void World::clear()
{ {
mRendering->clear();
mLocalScripts.clear(); mLocalScripts.clear();
mPlayer->clear(); mPlayer->clear();
@ -277,6 +279,14 @@ namespace MWWorld
void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{ {
// Active cells could have a dirty fog of war, sync it to the CellStore first
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
iter!=mWorldScene->getActiveCells().end(); ++iter)
{
CellStore* cellstore = *iter;
mRendering->writeFog(cellstore);
}
mCells.write (writer, progress); mCells.write (writer, progress);
mStore.write (writer, progress); mStore.write (writer, progress);
mGlobalVariables.write (writer, progress); mGlobalVariables.write (writer, progress);

@ -45,7 +45,7 @@ add_component_dir (esm
loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat
loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter
savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate
npcstats creaturestats weatherstate quickkeys npcstats creaturestats weatherstate quickkeys fogstate
) )
add_component_dir (misc add_component_dir (misc

@ -8,10 +8,15 @@ void ESM::CellState::load (ESMReader &esm)
{ {
mWaterLevel = 0; mWaterLevel = 0;
esm.getHNOT (mWaterLevel, "WLVL"); esm.getHNOT (mWaterLevel, "WLVL");
mHasFogOfWar = false;
esm.getHNOT (mHasFogOfWar, "HFOW");
} }
void ESM::CellState::save (ESMWriter &esm) const void ESM::CellState::save (ESMWriter &esm) const
{ {
if (!mId.mPaged) if (!mId.mPaged)
esm.writeHNT ("WLVL", mWaterLevel); esm.writeHNT ("WLVL", mWaterLevel);
}
esm.writeHNT("HFOW", mHasFogOfWar);
}

@ -17,9 +17,11 @@ namespace ESM
float mWaterLevel; float mWaterLevel;
int mHasFogOfWar; // Do we have fog of war state (0 or 1)? (see fogstate.hpp)
void load (ESMReader &esm); void load (ESMReader &esm);
void save (ESMWriter &esm) const; void save (ESMWriter &esm) const;
}; };
} }
#endif #endif

@ -0,0 +1,40 @@
#include "fogstate.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
void ESM::FogState::load (ESMReader &esm)
{
esm.getHNOT(mBounds, "BOUN");
esm.getHNOT(mNorthMarkerAngle, "ANGL");
while (esm.isNextSub("FTEX"))
{
esm.getSubHeader();
FogTexture tex;
esm.getT(tex.mX);
esm.getT(tex.mY);
size_t imageSize = esm.getSubSize()-sizeof(int)*2;
tex.mImageData.resize(imageSize);
esm.getExact(&tex.mImageData[0], imageSize);
mFogTextures.push_back(tex);
}
}
void ESM::FogState::save (ESMWriter &esm, bool interiorCell) const
{
if (interiorCell)
{
esm.writeHNT("BOUN", mBounds);
esm.writeHNT("ANGL", mNorthMarkerAngle);
}
for (std::vector<FogTexture>::const_iterator it = mFogTextures.begin(); it != mFogTextures.end(); ++it)
{
esm.startSubRecord("FTEX");
esm.writeT(it->mX);
esm.writeT(it->mY);
esm.write(&it->mImageData[0], it->mImageData.size());
esm.endRecord("FTEX");
}
}

@ -0,0 +1,38 @@
#ifndef OPENMW_ESM_FOGSTATE_H
#define OPENMW_ESM_FOGSTATE_H
#include <vector>
namespace ESM
{
class ESMReader;
class ESMWriter;
struct FogTexture
{
int mX, mY; // Only used for interior cells
std::vector<char> mImageData;
};
// format 0, saved games only
// Fog of war state
struct FogState
{
// Only used for interior cells
float mNorthMarkerAngle;
struct Bounds
{
float mMinX;
float mMinY;
float mMaxX;
float mMaxY;
} mBounds;
std::vector<FogTexture> mFogTextures;
void load (ESMReader &esm);
void save (ESMWriter &esm, bool interiorCell) const;
};
}
#endif
Loading…
Cancel
Save