Savegame: store fog of war (Closes #1177)

deque
scrawl 10 years ago
parent 7b46e9f914
commit a4a9794417

@ -9,6 +9,8 @@
#include <OgreRenderTexture.h>
#include <OgreViewport.h>
#include <components/esm/fogstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@ -46,7 +48,6 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag
LocalMap::~LocalMap()
{
deleteBuffers();
}
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);
}
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);
if (tex.isNull()) return;
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);
// Not actually removing the Textures here. That doesnt appear to work properly. It seems MyGUI still keeps some pointers.
mBuffers.clear();
}
void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
{
if (!mInterior)
{
/*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog",
"Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/
std::string textureName = "Cell_"+coordStr(cell->getCell()->getGridX(), cell->getCell()->getGridY())+"_fog";
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
{
Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y);
Vector2 length = max-min;
// divide into segments
const int segsX = std::ceil( length.x / 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 y=0; y<segsY; ++y)
{
/*saveTexture(
mInteriorName + "_" + coordStr(x,y) + "_fog",
mInteriorName + "_" + coordStr(x,y) + "_fog.png");*/
std::string textureName = cell->getCell()->mName + "_" + coordStr(x,y) + "_fog";
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));
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,
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 ())
return;
mInterior = true;
mBounds = bounds;
float zMin = mBounds.getMinimum().z;
float zMax = mBounds.getMaximum().z;
mBounds = bounds;
// Get the cell's NorthMarker rotation. This is used to rotate the entire map.
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();
mCellCamera->setOrientation(Quaternion::IDENTITY);
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
// Rotate the cell and merge the rotated corners to the bounding box
Vector2 _center(bounds.getCenter().x, bounds.getCenter().y);
Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_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(c4.x, c4.y, 0));
// apply a little padding
mBounds.setMinimum (mBounds.getMinimum() - Vector3(500,500,0));
mBounds.setMaximum (mBounds.getMaximum() + Vector3(500,500,0));
// Do NOT change padding! This will break older savegames.
// If the padding really needs to be changed, then it must be saved in the ESM::FogState and
// 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);
@ -179,6 +248,9 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
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));
// divide into segments
@ -187,19 +259,96 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
mInteriorName = cell->getCell()->mName;
int i=0;
for (int x=0; x<segsX; ++x)
{
for (int y=0; y<segsY; ++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,
cell->getCell()->mName + "_" + coordStr(x,y));
ESM::FogTexture& esm = fog->mFogTextures[i];
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,
const float zlow, const float zhigh,
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");
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);
@ -304,6 +428,9 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi
if (mBuffers.find(texName) == mBuffers.end())
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 texV = (sFogOfWarResolution-1) * nY;
@ -414,6 +541,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
}
// 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);
tex->getBuffer()->unlock();
}

@ -11,6 +11,11 @@ namespace MWWorld
class CellStore;
}
namespace ESM
{
class FogTexture;
}
namespace MWRender
{
class RenderingManager;
@ -24,6 +29,11 @@ namespace MWRender
LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering);
~LocalMap();
/**
* Clear all savegame-specific data (i.e. fog of war textures)
*/
void clear();
/**
* Request the local map for an exterior cell.
* @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);
/**
* Save the fog of war for the current cell to disk.
* @remarks This should be called before loading a
* new cell, as well as when the game is quit.
* @param current cell
* Save the fog of war for this cell to its CellStore.
* @remarks This should be called when unloading a cell, and for all active cells prior to saving the game.
*/
void saveFogOfWar(MWWorld::CellStore* cell);
@ -104,17 +112,20 @@ namespace MWRender
const float xw, const float yw,
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);
// a buffer for the "fog of war" texture of the current cell.
// interior cells could be divided into multiple textures,
// so we store in a map.
// A buffer for the "fog of war" textures of the current cell.
// Both interior and exterior maps are possibly divided into multiple textures.
std::map <std::string, std::vector<Ogre::uint32> > mBuffers;
void deleteBuffers();
bool mInterior;
int mCellX, mCellY;
Ogre::AxisAlignedBox mBounds;

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

@ -107,12 +107,15 @@ public:
void cellAdded (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 removeWater();
void preCellChange (MWWorld::CellStore* store);
///< this event is fired immediately before changing cell
/// Write current fog of war for this cell to the CellStore
void writeFog (MWWorld::CellStore* store);
void addObject (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);
cellState.mId.save (writer);
cellState.save (writer);
cell.writeFog(writer);
cell.writeReferences (writer);
writer.endRecord (ESM::REC_CSTA);
}
@ -319,6 +320,9 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type,
state.load (reader);
cellStore->loadState (state);
if (state.mHasFogOfWar)
cellStore->readFog(reader);
if (cellStore->getState()!=CellStore::State_Loaded)
cellStore->load (mStore, mReader);

@ -11,6 +11,7 @@
#include <components/esm/containerstate.hpp>
#include <components/esm/npcstate.hpp>
#include <components/esm/creaturestate.hpp>
#include <components/esm/fogstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -533,6 +534,21 @@ namespace MWWorld
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
@ -697,4 +713,14 @@ namespace MWWorld
{
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 <stdexcept>
#include <boost/shared_ptr.hpp>
#include "livecellref.hpp"
#include "esmstore.hpp"
#include "cellreflist.hpp"
#include <components/esm/fogstate.hpp>
#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld
namespace ESM
{
struct CellState;
struct FogState;
}
namespace MWWorld
{
class Ptr;
/// \brief Mutable state of a cell
class CellStore
{
@ -31,6 +37,11 @@ namespace MWWorld
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;
State mState;
bool mHasState;
@ -84,6 +95,11 @@ namespace MWWorld
void setWaterLevel (float level);
void setFog (ESM::FogState* fog);
///< \note Takes ownership of the pointer
ESM::FogState* getFog () const;
int count() const;
///< Return total number of references, including deleted ones.
@ -134,6 +150,10 @@ namespace MWWorld
void saveState (ESM::CellState& state) const;
void writeFog (ESM::ESMWriter& writer) const;
void readFog (ESM::ESMReader& reader);
void writeReferences (ESM::ESMWriter& writer) const;
void readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap);

@ -231,6 +231,8 @@ namespace MWWorld
void World::clear()
{
mRendering->clear();
mLocalScripts.clear();
mPlayer->clear();
@ -277,6 +279,14 @@ namespace MWWorld
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);
mStore.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
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
npcstats creaturestats weatherstate quickkeys
npcstats creaturestats weatherstate quickkeys fogstate
)
add_component_dir (misc

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

@ -17,9 +17,11 @@ namespace ESM
float mWaterLevel;
int mHasFogOfWar; // Do we have fog of war state (0 or 1)? (see fogstate.hpp)
void load (ESMReader &esm);
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