1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-03-30 09:36:43 +00:00

Merge remote-tracking branch 'scrawl/master'

This commit is contained in:
Marc Zinnschlag 2014-05-11 11:10:51 +02:00
commit 368c868623
23 changed files with 474 additions and 145 deletions

View file

@ -200,8 +200,8 @@ namespace MWBase
virtual bool getFullHelp() const = 0; virtual bool getFullHelp() const = 0;
virtual void setInteriorMapTexture(const int x, const int y) = 0; virtual void setActiveMap(int x, int y, bool interior) = 0;
///< set the index of the map texture that should be used (for interiors) ///< set the indices of the map texture that should be used
/// sets the visibility of the drowning bar /// sets the visibility of the drowning bar
virtual void setDrowningBarVisibility(bool visible) = 0; virtual void setDrowningBarVisibility(bool visible) = 0;

View file

@ -65,11 +65,11 @@ namespace MWGui
{ {
MyGUI::ImageBox* map = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::ImageBox* map = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize),
MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast<std::string>(mx) + "_" + boost::lexical_cast<std::string>(my)); MyGUI::Align::Top | MyGUI::Align::Left);
MyGUI::ImageBox* fog = map->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::ImageBox* fog = map->createWidget<MyGUI::ImageBox>("ImageBox",
MyGUI::IntCoord(0, 0, widgetSize, widgetSize), MyGUI::IntCoord(0, 0, widgetSize, widgetSize),
MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast<std::string>(mx) + "_" + boost::lexical_cast<std::string>(my) + "_fog"); MyGUI::Align::Top | MyGUI::Align::Left);
if (!mMapDragAndDrop) if (!mMapDragAndDrop)
{ {
@ -93,11 +93,6 @@ namespace MWGui
{ {
mFogOfWar = !mFogOfWar; mFogOfWar = !mFogOfWar;
applyFogOfWar(); applyFogOfWar();
// clear all previous door markers
for (std::vector<MyGUI::Widget*>::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it)
MyGUI::Gui::getInstance().destroyWidget(*it);
mDoorMarkerWidgets.clear();
} }
void LocalMapBase::applyFogOfWar() void LocalMapBase::applyFogOfWar()
@ -165,8 +160,9 @@ namespace MWGui
markerPos.cellX = cellX; markerPos.cellX = cellX;
markerPos.cellY = cellY; markerPos.cellY = cellY;
widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellX-mCurX) * 512, // Image space is -Y up, cells are Y up
nY * 512 + (1+cellY-mCurY) * 512); widgetPos = MyGUI::IntPoint(nX * 512 + (1+(cellX-mCurX)) * 512,
nY * 512 + (1-(cellY-mCurY)) * 512);
} }
markerPos.nX = nX; markerPos.nX = nX;
@ -241,7 +237,7 @@ namespace MWGui
8, 8); 8, 8);
++counter; ++counter;
MyGUI::Button* markerWidget = mLocalMap->createWidget<MyGUI::Button>("ButtonImage", MyGUI::Button* markerWidget = mLocalMap->createWidget<MyGUI::Button>("ButtonImage",
widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast<std::string>(counter)); widgetCoord, MyGUI::Align::Default);
markerWidget->setImageResource("DoorMarker"); markerWidget->setImageResource("DoorMarker");
markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipType", "Layout");
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
@ -343,7 +339,7 @@ namespace MWGui
8, 8); 8, 8);
++counter; ++counter;
MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast<std::string>(counter)); widgetCoord, MyGUI::Align::Default);
markerWidget->setImageTexture(markerTexture); markerWidget->setImageTexture(markerTexture);
markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserString("IsMarker", "true");
markerWidget->setUserData(markerPos); markerWidget->setUserData(markerPos);
@ -375,7 +371,7 @@ namespace MWGui
widgetPos.top - 4, widgetPos.top - 4,
8, 8); 8, 8);
MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
widgetCoord, MyGUI::Align::Default, "MarkerMarked"); widgetCoord, MyGUI::Align::Default);
markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setImageTexture("textures\\menu_map_smark.dds");
markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserString("IsMarker", "true");
markerWidget->setUserData(markerPos); markerWidget->setUserData(markerPos);
@ -447,7 +443,7 @@ namespace MWGui
static int _counter=0; static int _counter=0;
MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget<MyGUI::Button>("ButtonImage", MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget<MyGUI::Button>("ButtonImage",
widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast<std::string>(_counter)); widgetCoord, MyGUI::Align::Default);
markerWidget->setImageResource("DoorMarker"); markerWidget->setImageResource("DoorMarker");
markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipType", "Layout");
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");

View file

@ -766,8 +766,6 @@ namespace MWGui
mMap->setCellPrefix("Cell"); mMap->setCellPrefix("Cell");
mHud->setCellPrefix("Cell"); mHud->setCellPrefix("Cell");
mMap->setActiveCell (cell->getCell()->getGridX(), cell->getCell()->getGridY());
mHud->setActiveCell (cell->getCell()->getGridX(), cell->getCell()->getGridY());
} }
else else
{ {
@ -783,10 +781,10 @@ namespace MWGui
} }
} }
void WindowManager::setInteriorMapTexture(const int x, const int y) void WindowManager::setActiveMap(int x, int y, bool interior)
{ {
mMap->setActiveCell(x,y, true); mMap->setActiveCell(x,y, interior);
mHud->setActiveCell(x,y, true); mHud->setActiveCell(x,y, interior);
} }
void WindowManager::setPlayerPos(const float x, const float y) void WindowManager::setPlayerPos(const float x, const float y)

View file

@ -192,8 +192,8 @@ namespace MWGui
virtual void toggleFullHelp(); ///< show extra info in item tooltips (owner, script) virtual void toggleFullHelp(); ///< show extra info in item tooltips (owner, script)
virtual bool getFullHelp() const; virtual bool getFullHelp() const;
virtual void setInteriorMapTexture(const int x, const int y); virtual void setActiveMap(int x, int y, bool interior);
///< set the index of the map texture that should be used (for interiors) ///< set the indices of the map texture that should be used
/// sets the visibility of the drowning bar /// sets the visibility of the drowning bar
virtual void setDrowningBarVisibility(bool visible); virtual void setDrowningBarVisibility(bool visible);

View file

@ -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"
@ -42,11 +44,28 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag
mLight->setDirection (Ogre::Vector3(0.3, 0.3, -0.7)); mLight->setDirection (Ogre::Vector3(0.3, 0.3, -0.7));
mLight->setVisible (false); mLight->setVisible (false);
mLight->setDiffuseColour (ColourValue(0.7,0.7,0.7)); mLight->setDiffuseColour (ColourValue(0.7,0.7,0.7));
mRenderTexture = TextureManager::getSingleton().createManual(
"localmap/rtt",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D,
sMapResolution, sMapResolution,
0,
PF_R8G8B8,
TU_RENDERTARGET);
mRenderTarget = mRenderTexture->getBuffer()->getRenderTarget();
mRenderTarget->setAutoUpdated(false);
Viewport* vp = mRenderTarget->addViewport(mCellCamera);
vp->setOverlaysEnabled(false);
vp->setShadowsEnabled(false);
vp->setBackgroundColour(ColourValue(0, 0, 0));
vp->setVisibilityMask(RV_Map);
vp->setMaterialScheme("local_map");
} }
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 +74,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()
{
mBuffers.clear();
}
void LocalMap::saveTexture(const std::string& texname, const std::string& filename)
{
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) std::string LocalMap::coordStr(const int x, const int y)
{ {
return StringConverter::toString(x) + "_" + StringConverter::toString(y); return StringConverter::toString(x) + "_" + StringConverter::toString(y);
} }
void LocalMap::clear()
{
// 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) 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 +169,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; mBounds = bounds;
float zMin = mBounds.getMinimum().z; // Get the cell's NorthMarker rotation. This is used to rotate the entire map.
float zMax = mBounds.getMaximum().z;
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 +216,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 +266,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 +277,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;
render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, std::string texturePrefix = cell->getCell()->mName + "_" + coordStr(x,y);
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()));
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);
}
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)
@ -229,51 +396,17 @@ void LocalMap::render(const float x, const float y,
if (tex.isNull()) if (tex.isNull())
{ {
// render // render
tex = TextureManager::getSingleton().createManual( mRenderTarget->update();
// create a new texture and blit to it
Ogre::TexturePtr tex = TextureManager::getSingleton().createManual(
texture, texture,
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D, TEX_TYPE_2D,
xw*sMapResolution/sSize, yw*sMapResolution/sSize, sMapResolution, sMapResolution,
0, 0,
PF_R8G8B8, PF_R8G8B8);
TU_RENDERTARGET); tex->getBuffer()->blit(mRenderTexture->getBuffer());
RenderTarget* rtt = tex->getBuffer()->getRenderTarget();
rtt->setAutoUpdated(false);
Viewport* vp = rtt->addViewport(mCellCamera);
vp->setOverlaysEnabled(false);
vp->setShadowsEnabled(false);
vp->setBackgroundColour(ColourValue(0, 0, 0));
vp->setVisibilityMask(RV_Map);
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); mRenderingManager->enableLights(true);
@ -304,6 +437,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;
@ -339,10 +475,7 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
mCellX = x; mCellX = x;
mCellY = y; mCellY = y;
} }
else MWBase::Environment::get().getWindowManager()->setActiveMap(x,y,mInterior);
{
MWBase::Environment::get().getWindowManager()->setInteriorMapTexture(x,y);
}
// convert from world coordinates to texture UV coordinates // convert from world coordinates to texture UV coordinates
std::string texBaseName; std::string texBaseName;
@ -414,6 +547,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();
} }

View file

@ -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);
@ -76,7 +84,6 @@ namespace MWRender
OEngine::Render::OgreRenderer* mRendering; OEngine::Render::OgreRenderer* mRendering;
MWRender::RenderingManager* mRenderingManager; MWRender::RenderingManager* mRenderingManager;
// 1024*1024 pixels for a cell
static const int sMapResolution = 512; static const int sMapResolution = 512;
// the dynamic texture is a bottleneck, so don't set this too high // the dynamic texture is a bottleneck, so don't set this too high
@ -104,16 +111,23 @@ 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(); // The render texture we will use to create the map images
Ogre::TexturePtr mRenderTexture;
Ogre::RenderTarget* mRenderTarget;
bool mInterior; bool mInterior;
int mCellX, mCellY; int mCellX, mCellY;

View file

@ -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

View file

@ -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);

View file

@ -314,6 +314,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
case ESM::REC_PLAY: case ESM::REC_PLAY:
case ESM::REC_CSTA: case ESM::REC_CSTA:
case ESM::REC_WTHR: case ESM::REC_WTHR:
case ESM::REC_DYNA:
MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap);
break; break;

View file

@ -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);

View file

@ -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();
}
} }

View file

@ -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);

View file

@ -141,8 +141,8 @@ void ESMStore::setUp()
int ESMStore::countSavedGameRecords() const int ESMStore::countSavedGameRecords() const
{ {
return return 1 // DYNA (dynamic name counter)
mPotions.getDynamicSize() +mPotions.getDynamicSize()
+mArmors.getDynamicSize() +mArmors.getDynamicSize()
+mBooks.getDynamicSize() +mBooks.getDynamicSize()
+mClasses.getDynamicSize() +mClasses.getDynamicSize()
@ -155,6 +155,13 @@ void ESMStore::setUp()
void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{ {
writer.startRecord(ESM::REC_DYNA);
writer.startSubRecord("COUN");
writer.writeT(mDynamicCount);
writer.endRecord("COUN");
writer.endRecord(ESM::REC_DYNA);
progress.increaseProgress();
mPotions.write (writer, progress); mPotions.write (writer, progress);
mArmors.write (writer, progress); mArmors.write (writer, progress);
mBooks.write (writer, progress); mBooks.write (writer, progress);
@ -197,6 +204,11 @@ void ESMStore::setUp()
return true; return true;
case ESM::REC_DYNA:
reader.getSubNameIs("COUN");
reader.getHT(mDynamicCount);
return true;
default: default:
return false; return false;

View file

@ -166,16 +166,17 @@ namespace MWWorld
template <class T> template <class T>
const T *insert(const T &x) { const T *insert(const T &x) {
std::ostringstream id;
id << "$dynamic" << mDynamicCount++;
Store<T> &store = const_cast<Store<T> &>(get<T>()); Store<T> &store = const_cast<Store<T> &>(get<T>());
if (store.search(x.mId) != 0) { if (store.search(id.str()) != 0) {
std::ostringstream msg; std::ostringstream msg;
msg << "Try to override existing record '" << x.mId << "'"; msg << "Try to override existing record '" << id.str() << "'";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
T record = x; T record = x;
std::ostringstream id;
id << "$dynamic" << mDynamicCount++;
record.mId = id.str(); record.mId = id.str();
T *ptr = store.insert(record); T *ptr = store.insert(record);
@ -189,10 +190,13 @@ namespace MWWorld
template <class T> template <class T>
const T *insertStatic(const T &x) { const T *insertStatic(const T &x) {
std::ostringstream id;
id << "$dynamic" << mDynamicCount++;
Store<T> &store = const_cast<Store<T> &>(get<T>()); Store<T> &store = const_cast<Store<T> &>(get<T>());
if (store.search(x.mId) != 0) { if (store.search(id.str()) != 0) {
std::ostringstream msg; std::ostringstream msg;
msg << "Try to override existing record '" << x.mId << "'"; msg << "Try to override existing record '" << id.str() << "'";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
T record = x; T record = x;
@ -225,17 +229,18 @@ namespace MWWorld
template <> template <>
inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc) { inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc) {
std::ostringstream id;
id << "$dynamic" << mDynamicCount++;
if (Misc::StringUtils::ciEqual(npc.mId, "player")) { if (Misc::StringUtils::ciEqual(npc.mId, "player")) {
return mNpcs.insert(npc); return mNpcs.insert(npc);
} else if (mNpcs.search(npc.mId) != 0) { } else if (mNpcs.search(id.str()) != 0) {
std::ostringstream msg; std::ostringstream msg;
msg << "Try to override existing record '" << npc.mId << "'"; msg << "Try to override existing record '" << id.str() << "'";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
ESM::NPC record = npc; ESM::NPC record = npc;
std::ostringstream id;
id << "$dynamic" << mDynamicCount++;
record.mId = id.str(); record.mId = id.str();
ESM::NPC *ptr = mNpcs.insert(record); ESM::NPC *ptr = mNpcs.insert(record);

View file

@ -20,10 +20,15 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state)
if (state.mHasLocals) if (state.mHasLocals)
{ {
std::string scriptId = mClass->getScript (ptr); std::string scriptId = mClass->getScript (ptr);
// Make sure we still have a script. It could have been coming from a content file that is no longer active.
mData.setLocals (*MWBase::Environment::get().getWorld()->getStore(). if (!scriptId.empty())
get<ESM::Script>().find (scriptId)); {
mData.getLocals().read (state.mLocals, scriptId); if (const ESM::Script* script = MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().search (scriptId))
{
mData.setLocals (*script);
mData.getLocals().read (state.mLocals, scriptId);
}
}
} }
mClass->readAdditionalState (ptr, state); mClass->readAdditionalState (ptr, state);

View file

@ -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);

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -100,6 +100,7 @@ enum RecNameInts
REC_DIAS = 0x53414944, REC_DIAS = 0x53414944,
REC_WTHR = 0x52485457, REC_WTHR = 0x52485457,
REC_KEYS = FourCC<'K','E','Y','S'>::value, REC_KEYS = FourCC<'K','E','Y','S'>::value,
REC_DYNA = FourCC<'D','Y','N','A'>::value,
// format 1 // format 1
REC_FILT = 0x544C4946 REC_FILT = 0x544C4946

View file

@ -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");
}
}

View file

@ -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

View file

@ -10,6 +10,10 @@
<Property key="VerticalAlignment" value="false"/> <Property key="VerticalAlignment" value="false"/>
<Property key="MoveToClick" value="true"/> <Property key="MoveToClick" value="true"/>
<Property key="WheelPage" value="36"/>
<Property key="ViewPage" value="36"/>
<Property key="Page" value="36"/>
<!-- Tracker must be last to be on top and receive mouse events --> <!-- Tracker must be last to be on top and receive mouse events -->
<Child type="Button" skin="MW_Box" offset="18 0 54 14" align="Stretch" name="Background"/> <Child type="Button" skin="MW_Box" offset="18 0 54 14" align="Stretch" name="Background"/>
@ -47,6 +51,10 @@
<Property key="MinTrackSize" value="14"/> <Property key="MinTrackSize" value="14"/>
<Property key="MoveToClick" value="true"/> <Property key="MoveToClick" value="true"/>
<Property key="WheelPage" value="36"/>
<Property key="ViewPage" value="36"/>
<Property key="Page" value="36"/>
<!-- Background widget trick that must go first to be placed behind Track and FirstPart/SecondPart widgets, provides the bar texture --> <!-- Background widget trick that must go first to be placed behind Track and FirstPart/SecondPart widgets, provides the bar texture -->
<Child type="Button" skin="MW_Box" offset="0 18 14 55" align="Stretch" name="Background"/> <Child type="Button" skin="MW_Box" offset="0 18 14 55" align="Stretch" name="Background"/>