From f4898539e9c99f0f2c4a36c0f47bf0f90dcc0290 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 13:06:01 +0100 Subject: [PATCH 01/85] added some code that doesn't do anything yet, waiting for ogre 1.8 --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/occlusionquery.cpp | 27 +++++++++++++++++++++++ apps/openmw/mwrender/occlusionquery.hpp | 27 +++++++++++++++++++++++ apps/openmw/mwrender/renderingmanager.cpp | 3 +++ apps/openmw/mwrender/renderingmanager.hpp | 7 ++++-- 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwrender/occlusionquery.cpp create mode 100644 apps/openmw/mwrender/occlusionquery.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2630098f5c..7d4836d3b1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap + renderinginterface localmap occlusionquery ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp new file mode 100644 index 0000000000..395660bb5c --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -0,0 +1,27 @@ +#include "occlusionquery.hpp" + +#include +#include + +using namespace MWRender; +using namespace Ogre; + +OcclusionQuery::OcclusionQuery() : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0) +{ + try { + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + + mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); + } + catch (Ogre::Exception e) + { + mSupported = false; + } + + if (!mSupported) + std::cout << "Hardware occlusion queries not supported." << std::endl; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp new file mode 100644 index 0000000000..619b072ee0 --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -0,0 +1,27 @@ +#ifndef _GAME_OCCLUSION_QUERY_H +#define _GAME_OCCLUSION_QUERY_H + +#include + +namespace MWRender +{ + /// + /// \brief Implements hardware occlusion queries on the GPU + /// + class OcclusionQuery + { + public: + OcclusionQuery(); + + bool supported(); + ///< returns true if occlusion queries are supported on the user's hardware + + private: + Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; + Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + + bool mSupported; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e2aea19c6e..78287dadb5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -49,6 +49,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const Ogre::SceneNode *cameraYawNode = playerNode->createChildSceneNode(); Ogre::SceneNode *cameraPitchNode = cameraYawNode->createChildSceneNode(); cameraPitchNode->attachObject(mRendering.getCamera()); + + mOcclusionQuery = new OcclusionQuery(); //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); @@ -65,6 +67,7 @@ RenderingManager::~RenderingManager () delete mPlayer; delete mSkyManager; delete mLocalMap; + delete mOcclusionQuery; } MWRender::SkyManager* RenderingManager::getSkyManager() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 78a1d2fdb7..996396633c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -25,6 +25,7 @@ #include "actors.hpp" #include "player.hpp" #include "localmap.hpp" +#include "occlusionquery.hpp" namespace Ogre { @@ -131,9 +132,11 @@ class RenderingManager: private RenderingInterface { private: void setAmbientMode(); - + SkyManager* mSkyManager; - + + OcclusionQuery* mOcclusionQuery; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; From 743ea0c9be16a6864bf861db101dd5901452045a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 17:59:26 +0100 Subject: [PATCH 02/85] use hardware occlusion query for sun glare effect --- apps/openmw/mwrender/occlusionquery.cpp | 116 +++++++++++++++++++++- apps/openmw/mwrender/occlusionquery.hpp | 28 +++++- apps/openmw/mwrender/renderingmanager.cpp | 12 ++- apps/openmw/mwrender/renderingmanager.hpp | 4 +- apps/openmw/mwrender/sky.cpp | 52 ++++++---- apps/openmw/mwrender/sky.hpp | 48 ++++----- apps/openmw/mwworld/world.cpp | 17 ++-- 7 files changed, 216 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 395660bb5c..6c74561e95 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -2,13 +2,17 @@ #include #include +#include using namespace MWRender; using namespace Ogre; -OcclusionQuery::OcclusionQuery() : - mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0) +OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0) { + mRendering = renderer; + mSunNode = sunNode; + try { RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); @@ -23,5 +27,113 @@ OcclusionQuery::OcclusionQuery() : } if (!mSupported) + { std::cout << "Hardware occlusion queries not supported." << std::endl; + return; + } + + // This means that everything up to RENDER_QUEUE_MAIN can occlude the objects that are tested + const int queue = RENDER_QUEUE_MAIN+1; + + MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting"); + MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels"); + matQueryArea->setDepthWriteEnabled(false); + matQueryArea->setColourWriteEnabled(false); + matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects + MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); + matQueryVisible->setDepthWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); + matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); + mBBQueryTotal->setDefaultDimensions(150, 150); + mBBQueryTotal->createBillboard(Vector3::ZERO); + mBBQueryTotal->setMaterialName("QueryTotalPixels"); + mBBQueryTotal->setRenderQueueGroup(queue); + mSunNode->attachObject(mBBQueryTotal); + + mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); + mBBQueryVisible->setDefaultDimensions(150, 150); + mBBQueryVisible->createBillboard(Vector3::ZERO); + mBBQueryVisible->setMaterialName("QueryVisiblePixels"); + mBBQueryVisible->setRenderQueueGroup(queue); + mSunNode->attachObject(mBBQueryVisible); + + mRendering->getScene()->addRenderObjectListener(this); + mDoQuery = true; } + +OcclusionQuery::~OcclusionQuery() +{ + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); + if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); +} + +bool OcclusionQuery::supported() +{ + return mSupported; +} + +void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, + const LightList* pLightList, bool suppressRenderStateChanges) +{ + if (!mSupported) return; + + // The following code activates and deactivates the occlusion queries + // so that the queries only include the rendering of their intended targets + + // Close the last occlusion query + // Each occlusion query should only last a single rendering + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + + // Open a new occlusion query + if (mDoQuery == true) + { + if (rend == mBBQueryTotal) + mActiveQuery = mSunTotalAreaQuery; + else if (rend == mBBQueryVisible) + mActiveQuery = mSunVisibleAreaQuery; + + if (mActiveQuery != NULL) + { + mActiveQuery->beginOcclusionQuery(); + } + } +} + +void OcclusionQuery::update() +{ + if (!mSupported) return; + + // Stop occlusion queries until we get their information + // (may not happen on the same frame they are requested in) + mDoQuery = false; + + if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) + { + unsigned int totalPixels; + unsigned int visiblePixels; + + mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels); + mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels); + + if (totalPixels == 0) + { + // probably outside of the view frustum + mSunVisibility = 0; + } + else + { + mSunVisibility = float(visiblePixels) / float(totalPixels); + if (mSunVisibility > 1) mSunVisibility = 1; + } + + mDoQuery = true; + } +} + diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 619b072ee0..e6adc0d4b3 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -2,25 +2,49 @@ #define _GAME_OCCLUSION_QUERY_H #include +#include + +#include namespace MWRender { /// /// \brief Implements hardware occlusion queries on the GPU /// - class OcclusionQuery + class OcclusionQuery : public Ogre::RenderObjectListener { public: - OcclusionQuery(); + OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); + ~OcclusionQuery(); bool supported(); ///< returns true if occlusion queries are supported on the user's hardware + void update(); + ///< per-frame update + + float getSunVisibility() const {return mSunVisibility;}; + private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mActiveQuery; + + Ogre::BillboardSet* mBBQueryVisible; + Ogre::BillboardSet* mBBQueryTotal; + + Ogre::SceneNode* mSunNode; + + float mSunVisibility; bool mSupported; + bool mDoQuery; + + OEngine::Render::OgreRenderer* mRendering; + + protected: + virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, + const Ogre::LightList* pLightList, bool suppressRenderStateChanges); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7aa623879e..b4711e8df7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -54,12 +54,12 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const Ogre::SceneNode *cameraYawNode = playerNode->createChildSceneNode(); Ogre::SceneNode *cameraPitchNode = cameraYawNode->createChildSceneNode(); cameraPitchNode->attachObject(mRendering.getCamera()); - - mOcclusionQuery = new OcclusionQuery(); //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); + mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; @@ -149,9 +149,13 @@ void RenderingManager::moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Ve void RenderingManager::update (float duration){ mActors.update (duration); - + + mOcclusionQuery->update(); + mSkyManager->update(duration); - + + mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); + mRendering.update(duration); mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index acaf8565bb..450e461730 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -98,7 +98,9 @@ class RenderingManager: private RenderingInterface { void setSunDirection(const Ogre::Vector3& direction); void sunEnable(); void sunDisable(); - + + bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + void setGlare(bool glare); void skyEnable (); void skyDisable (); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 5e85780022..a70913b239 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -12,6 +12,7 @@ #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" +#include "occlusionquery.hpp" using namespace MWRender; using namespace Ogre; @@ -30,7 +31,7 @@ BillboardObject::BillboardObject() void BillboardObject::setVisible(const bool visible) { - mNode->setVisible(visible); + mBBSet->setVisible(visible); } void BillboardObject::setSize(const float size) @@ -88,7 +89,7 @@ void BillboardObject::init(const String& textureName, /// \todo These billboards are not 100% correct, might want to revisit them later mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); - mBBSet->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+2); + mBBSet->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); mBBSet->setCommonDirection( -position.normalisedCopy() ); mNode = rootNode->createChildSceneNode(); @@ -293,7 +294,7 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) } SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) : - mGlareFade(0), mGlareEnabled(false) + mGlare(0), mGlareFade(0) { mEnvironment = env; mViewport = pCamera->getViewport(); @@ -562,10 +563,23 @@ void SkyManager::update(float duration) mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun + if (mSunEnabled) { + // take 1/5 sec for fading the glare effect from invisible to full + if (mGlareFade > mGlare) + { + mGlareFade -= duration*5; + if (mGlareFade < mGlare) mGlareFade = mGlare; + } + else if (mGlareFade < mGlare) + { + mGlareFade += duration*5; + if (mGlareFade > mGlare) mGlareFade = mGlare; + } + + // increase the strength of the sun glare effect depending + // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); sun = Vector3(sun.x, sun.z, -sun.y); Vector3 cam = mViewport->getCamera()->getRealDirection(); @@ -573,21 +587,10 @@ void SkyManager::update(float duration) float val = 1- (angle.valueDegrees() / 180.f); val = (val*val*val*val)*2; - if (mGlareEnabled) - { - mGlareFade += duration*3; - if (mGlareFade > 1) mGlareFade = 1; - } - else - { - mGlareFade -= duration*3; - if (mGlareFade < 0.3) mGlareFade = 0; - } - - mSunGlare->setSize(val * (mGlareFade)); + mSunGlare->setSize(val * mGlareFade); } - mSunGlare->setVisible(mGlareFade>0 && mSunEnabled); + mSunGlare->setVisible(mSunEnabled); mSun->setVisible(mSunEnabled); mMasser->setVisible(mMasserEnabled); mSecunda->setVisible(mSecundaEnabled); @@ -689,15 +692,15 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else strength = 1.f; - mSunGlare->setVisibility(weather.mGlareView * strength); - mSun->setVisibility(strength); + mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); + mSun->setVisibility(mGlareFade >= 0.5 ? weather.mGlareView * mGlareFade * strength : 0); mAtmosphereNight->setVisible(weather.mNight && mEnabled); } -void SkyManager::setGlare(bool glare) +void SkyManager::setGlare(const float glare) { - mGlareEnabled = glare; + mGlare = glare; } Vector3 SkyManager::getRealSunPos() @@ -782,3 +785,8 @@ void SkyManager::setDate(int day, int month) mDay = day; mMonth = month; } + +Ogre::SceneNode* SkyManager::getSunNode() +{ + return mSun->getNode(); +} diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index bf52afd8dd..508af76732 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -109,58 +109,60 @@ namespace MWRender public: SkyManager(Ogre::SceneNode* pMwRoot, Ogre::Camera* pCamera, MWWorld::Environment* env); ~SkyManager(); - + void update(float duration); - + void enable(); - + void disable(); - + void setHour (double hour); ///< will be called even when sky is disabled. - + void setDate (int day, int month); ///< will be called even when sky is disabled. - + int getMasserPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + int getSecundaPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + void setMoonColour (bool red); ///< change Secunda colour to red - + void setCloudsOpacity(float opacity); ///< change opacity of the clouds - + void setWeather(const MWWorld::WeatherResult& weather); - + + Ogre::SceneNode* getSunNode(); + void sunEnable(); - + void sunDisable(); - + void setSunDirection(const Ogre::Vector3& direction); - + void setMasserDirection(const Ogre::Vector3& direction); - + void setSecundaDirection(const Ogre::Vector3& direction); - + void setMasserFade(const float fade); - + void setSecundaFade(const float fade); - + void masserEnable(); void masserDisable(); void secundaEnable(); void secundaDisable(); - + void setThunder(const float factor); - - void setGlare(bool glare); + + void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); private: @@ -203,12 +205,12 @@ namespace MWRender float mRemainingTransitionTime; - float mGlareFade; + float mGlare; // target + float mGlareFade; // actual void ModVertexAlpha(Ogre::Entity* ent, unsigned int meshType); bool mEnabled; - bool mGlareEnabled; bool mSunEnabled; bool mMasserEnabled; bool mSecundaEnabled; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index a636ce2887..261f66ff4f 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -705,13 +705,16 @@ namespace MWWorld mWeatherManager->update (duration); - // cast a ray from player to sun to detect if the sun is visible - // this is temporary until we find a better place to put this code - // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; - Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - sun = Vector3(sun.x, -sun.z, sun.y); - mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + if (!mRendering->occlusionQuerySupported()) + { + // cast a ray from player to sun to detect if the sun is visible + // this is temporary until we find a better place to put this code + // currently its here because we need to access the physics system + float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); + sun = Vector3(sun.x, -sun.z, sun.y); + mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + } } bool World::isCellExterior() const From e212a3235011986ad9fda7f58ac354c79e971f21 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 18:38:58 +0100 Subject: [PATCH 03/85] bugfix --- apps/openmw/mwrender/occlusionquery.cpp | 15 +++++++++++-- apps/openmw/mwrender/occlusionquery.hpp | 29 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 6c74561e95..baf55b0249 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -45,19 +45,21 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setColourWriteEnabled(false); matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); mBBQueryTotal->setRenderQueueGroup(queue); - mSunNode->attachObject(mBBQueryTotal); + mBBNode->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); mBBQueryVisible->setRenderQueueGroup(queue); - mSunNode->attachObject(mBBQueryVisible); + mBBNode->attachObject(mBBQueryVisible); mRendering->getScene()->addRenderObjectListener(this); mDoQuery = true; @@ -110,6 +112,15 @@ void OcclusionQuery::update() { if (!mSupported) return; + // Adjust the position of the sun billboards according to camera viewing distance + // we need to do this to make sure that _everything_ can occlude the sun + float dist = mRendering->getCamera()->getFarClipDistance(); + if (dist==0) dist = 10000000; + dist -= 1000; // bias + dist /= 1000.f; + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) mDoQuery = false; diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index e6adc0d4b3..2545e590e9 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -17,11 +17,32 @@ namespace MWRender OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); ~OcclusionQuery(); + /** + * @return true if occlusion queries are supported on the user's hardware + */ bool supported(); - ///< returns true if occlusion queries are supported on the user's hardware + /** + * per-frame update + */ void update(); - ///< per-frame update + + /** + * request occlusion test for a billboard at the given position, omitting an entity + * @param position of the billboard in ogre coordinates + * @param entity to exclude from the occluders + */ + void occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity); + + /** + * @return true if a request is still outstanding + */ + bool occlusionTestPending(); + + /** + * @return true if the object tested in the last request was occluded + */ + bool getTestResult(); float getSunVisibility() const {return mSunVisibility;}; @@ -35,8 +56,12 @@ namespace MWRender Ogre::SceneNode* mSunNode; + Ogre::SceneNode* mBBNode; + float mSunVisibility; + bool mTestResult; + bool mSupported; bool mDoQuery; From 9d30a139cc416a078c6f63871e7d584f982fc2a1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 20:41:23 +0100 Subject: [PATCH 04/85] added api --- apps/openmw/mwrender/occlusionquery.cpp | 73 +++++++++++++++++++++++-- apps/openmw/mwrender/occlusionquery.hpp | 20 ++++++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index baf55b0249..8dd30394c9 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -3,12 +3,16 @@ #include #include #include +#include +#include using namespace MWRender; using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : - mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0) + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), + mQuerySingleObjectRequested(false) { mRendering = renderer; mSunNode = sunNode; @@ -18,8 +22,9 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery(); - mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0); } catch (Ogre::Exception e) { @@ -47,6 +52,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); @@ -61,6 +68,13 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBQueryVisible->setRenderQueueGroup(queue); mBBNode->attachObject(mBBQueryVisible); + mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); + mBBQuerySingleObject->setDefaultDimensions(10, 10); + mBBQuerySingleObject->createBillboard(Vector3::ZERO); + mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); + mBBQuerySingleObject->setRenderQueueGroup(queue); + mObjectNode->attachObject(mBBQuerySingleObject); + mRendering->getScene()->addRenderObjectListener(this); mDoQuery = true; } @@ -70,6 +84,7 @@ OcclusionQuery::~OcclusionQuery() RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); + if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery); } bool OcclusionQuery::supported() @@ -100,11 +115,15 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunTotalAreaQuery; else if (rend == mBBQueryVisible) mActiveQuery = mSunVisibleAreaQuery; + else if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + } if (mActiveQuery != NULL) - { mActiveQuery->beginOcclusionQuery(); - } } } @@ -125,7 +144,9 @@ void OcclusionQuery::update() // (may not happen on the same frame they are requested in) mDoQuery = false; - if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) + if (!mSunTotalAreaQuery->isStillOutstanding() + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -144,7 +165,49 @@ void OcclusionQuery::update() if (mSunVisibility > 1) mSunVisibility = 1; } + if (mQuerySingleObjectStarted) + { + unsigned int visiblePixels; + + mSingleObjectQuery->pullOcclusionQuery(&visiblePixels); + + mBBQuerySingleObject->setVisible(false); + mObject->setRenderQueueGroup(mObjectOldRenderQueue); + + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; + } + mDoQuery = true; } } +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity) +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + mBBQuerySingleObject->setVisible(true); + + // we don't want the object to occlude itself + mObjectOldRenderQueue = entity->getRenderQueueGroup(); + if (mObjectOldRenderQueue < RENDER_QUEUE_MAIN+2) + entity->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); + + mObjectNode->setPosition(position); + + mQuerySingleObjectRequested = true; +} + +bool OcclusionQuery::occlusionTestPending() +{ + return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); +} + +bool OcclusionQuery::getTestResult() +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + return mTestResult; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 2545e590e9..bcc45d96c6 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -1,9 +1,15 @@ #ifndef _GAME_OCCLUSION_QUERY_H #define _GAME_OCCLUSION_QUERY_H -#include #include +namespace Ogre +{ + class HardwareOcclusionQuery; + class Entity; + class SceneNode; +} + #include namespace MWRender @@ -49,22 +55,30 @@ namespace MWRender private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mSingleObjectQuery; Ogre::HardwareOcclusionQuery* mActiveQuery; Ogre::BillboardSet* mBBQueryVisible; Ogre::BillboardSet* mBBQueryTotal; + Ogre::BillboardSet* mBBQuerySingleObject; Ogre::SceneNode* mSunNode; - Ogre::SceneNode* mBBNode; - float mSunVisibility; + Ogre::Entity* mObject; + + Ogre::SceneNode* mObjectNode; + int mObjectOldRenderQueue; + bool mTestResult; bool mSupported; bool mDoQuery; + bool mQuerySingleObjectRequested; + bool mQuerySingleObjectStarted; + OEngine::Render::OgreRenderer* mRendering; protected: From 17a4adfe88aded63fd21fe5d3ab658aa62c354c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 12:26:44 +0200 Subject: [PATCH 05/85] add raycast method that returns _all_ objects that were hit --- libs/openengine/bullet/physic.cpp | 31 +++++++++++++++++++++++++++++++ libs/openengine/bullet/physic.hpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 07bad30535..e6a7393cca 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -372,4 +372,35 @@ namespace Physic return std::pair(name,d); } + + std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + { + MyRayResultCallback resultCallback1; + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + resultCallback1.sort(); + std::vector< std::pair > results = resultCallback1.results; + + MyRayResultCallback resultCallback2; + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + resultCallback2.sort(); + std::vector< std::pair > actorResults = resultCallback2.results; + + std::vector< std::pair > results2; + + for (std::vector< std::pair >::iterator it=results.begin(); + it != results.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + for (std::vector< std::pair >::iterator it=actorResults.begin(); + it != actorResults.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + return results2; + } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 88e3699aee..97d2c004d1 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -201,6 +201,11 @@ namespace Physic */ std::pair rayTest(btVector3& from,btVector3& to); + /** + * Return all objects hit by a ray. + */ + std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + //event list of non player object std::list NPEventList; @@ -225,6 +230,30 @@ namespace Physic bool isDebugCreated; }; + + struct MyRayResultCallback : public btCollisionWorld::RayResultCallback + { + virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace) + { + results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) ); + return rayResult.m_hitFraction; + } + + static bool cmp( const std::pair& i, const std::pair& j ) + { + if( i.first < j.first ) return false; + if( j.first < i.first ) return true; + return false; + } + + void sort() + { + std::sort(results.begin(), results.end(), cmp); + } + + std::vector < std::pair > results; + }; + }} #endif From 53d4be5cf650edfb0730aa3424d19e70ebfcec40 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 20:52:56 +0200 Subject: [PATCH 06/85] object pickup should work everywhere --- apps/openmw/mwrender/occlusionquery.cpp | 105 +++++++++++++++------- apps/openmw/mwrender/occlusionquery.hpp | 22 +++-- apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwworld/physicssystem.cpp | 27 ++++++ apps/openmw/mwworld/physicssystem.hpp | 6 +- apps/openmw/mwworld/world.cpp | 73 +++++++++++++-- apps/openmw/mwworld/world.hpp | 6 ++ libs/openengine/bullet/physic.hpp | 4 +- 8 files changed, 198 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 8dd30394c9..66086a90b8 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -47,7 +47,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); matQueryVisible->setDepthWriteEnabled(false); - matQueryVisible->setColourWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); @@ -69,13 +69,14 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); - mBBQuerySingleObject->setDefaultDimensions(10, 10); + mBBQuerySingleObject->setDefaultDimensions(0.01, 0.01); mBBQuerySingleObject->createBillboard(Vector3::ZERO); mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); mBBQuerySingleObject->setRenderQueueGroup(queue); mObjectNode->attachObject(mBBQuerySingleObject); mRendering->getScene()->addRenderObjectListener(this); + mRendering->getScene()->addRenderQueueListener(this); mDoQuery = true; } @@ -95,8 +96,6 @@ bool OcclusionQuery::supported() void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, const LightList* pLightList, bool suppressRenderStateChanges) { - if (!mSupported) return; - // The following code activates and deactivates the occlusion queries // so that the queries only include the rendering of their intended targets @@ -111,26 +110,47 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass // Open a new occlusion query if (mDoQuery == true) { - if (rend == mBBQueryTotal) - mActiveQuery = mSunTotalAreaQuery; - else if (rend == mBBQueryVisible) - mActiveQuery = mSunVisibleAreaQuery; - else if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (rend == mBBQueryTotal) { - mQuerySingleObjectStarted = true; - mQuerySingleObjectRequested = false; - mActiveQuery = mSingleObjectQuery; + mActiveQuery = mSunTotalAreaQuery; + mWasVisible = true; + } + else if (rend == mBBQueryVisible) + { + mActiveQuery = mSunVisibleAreaQuery; } - - if (mActiveQuery != NULL) - mActiveQuery->beginOcclusionQuery(); } + if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + } + + if (mActiveQuery != NULL) + mActiveQuery->beginOcclusionQuery(); +} + +void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) +{ + if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false) + { + // for some reason our single object query returns wrong results when the sun query was never executed + // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) + // so we force it here once everything has been rendered + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } } void OcclusionQuery::update() { if (!mSupported) return; + mWasVisible = false; + // Adjust the position of the sun billboards according to camera viewing distance // we need to do this to make sure that _everything_ can occlude the sun float dist = mRendering->getCamera()->getFarClipDistance(); @@ -145,8 +165,7 @@ void OcclusionQuery::update() mDoQuery = false; if (!mSunTotalAreaQuery->isStillOutstanding() - && !mSunVisibleAreaQuery->isStillOutstanding() - && !mSingleObjectQuery->isStillOutstanding()) + && !mSunVisibleAreaQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -165,24 +184,33 @@ void OcclusionQuery::update() if (mSunVisibility > 1) mSunVisibility = 1; } - if (mQuerySingleObjectStarted) + mDoQuery = true; + } + if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) + { + unsigned int result; + + mSingleObjectQuery->pullOcclusionQuery(&result); + + //std::cout << "Single object query result: " << result << " pixels " << std::endl; + mTestResult = (result != 0); + + mBBQuerySingleObject->setVisible(false); + + // restore old render queues + for (std::vector::iterator it=mObjectsInfo.begin(); + it!=mObjectsInfo.end(); ++it) { - unsigned int visiblePixels; - - mSingleObjectQuery->pullOcclusionQuery(&visiblePixels); - - mBBQuerySingleObject->setVisible(false); - mObject->setRenderQueueGroup(mObjectOldRenderQueue); - - mQuerySingleObjectStarted = false; - mQuerySingleObjectRequested = false; + if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) return; + mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); } - mDoQuery = true; + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; } } -void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity) +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object) { assert( !occlusionTestPending() && "Occlusion test still pending"); @@ -190,11 +218,24 @@ void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* mBBQuerySingleObject->setVisible(true); // we don't want the object to occlude itself - mObjectOldRenderQueue = entity->getRenderQueueGroup(); - if (mObjectOldRenderQueue < RENDER_QUEUE_MAIN+2) - entity->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); + // put it in a render queue _after_ the occlusion query + mObjectsInfo.clear(); + for (int i=0; inumAttachedObjects(); ++i) + { + ObjectInfo info; + MovableObject* obj = object->getAttachedObject(i); + info.name = obj->getName(); + info.typeName = obj->getMovableType(); + info.oldRenderqueue = obj->getRenderQueueGroup(); + + mObjectsInfo.push_back(info); + + object->getAttachedObject(i)->setRenderQueueGroup(RENDER_QUEUE_MAIN+5); + } mObjectNode->setPosition(position); + // scale proportional to camera distance, in order to always give the billboard the same size in screen-space + mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); mQuerySingleObjectRequested = true; } diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index bcc45d96c6..987087e278 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -2,6 +2,7 @@ #define _GAME_OCCLUSION_QUERY_H #include +#include namespace Ogre { @@ -17,7 +18,14 @@ namespace MWRender /// /// \brief Implements hardware occlusion queries on the GPU /// - class OcclusionQuery : public Ogre::RenderObjectListener + struct ObjectInfo + { + int oldRenderqueue; + std::string name; + std::string typeName; + }; + + class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener { public: OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); @@ -36,9 +44,9 @@ namespace MWRender /** * request occlusion test for a billboard at the given position, omitting an entity * @param position of the billboard in ogre coordinates - * @param entity to exclude from the occluders + * @param object to exclude from the occluders */ - void occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity); + void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object); /** * @return true if a request is still outstanding @@ -66,10 +74,10 @@ namespace MWRender Ogre::SceneNode* mBBNode; float mSunVisibility; - Ogre::Entity* mObject; - Ogre::SceneNode* mObjectNode; - int mObjectOldRenderQueue; + std::vector mObjectsInfo; + + bool mWasVisible; bool mTestResult; @@ -84,6 +92,8 @@ namespace MWRender protected: virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, const Ogre::LightList* pLightList, bool suppressRenderStateChanges); + + virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation); }; } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 450e461730..569447bca3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -100,6 +100,7 @@ class RenderingManager: private RenderingInterface { void sunDisable(); bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }; void setGlare(bool glare); void skyEnable (); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bb2f9f8a92..1d8f14cc42 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -50,6 +50,33 @@ namespace MWWorld return mEngine->rayTest(from,to); } + + std::vector < std::pair > PhysicsSystem::getFacedObjects () + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + //let's avoid the capsule shape of the player. + centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + + return mEngine->rayTest2(from,to); + } + + btVector3 PhysicsSystem::getRayPoint(float extent) + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + //let's avoid the capsule shape of the player. + centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + return from * (1-extent) + to * extent; + } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 78cbde0837..7b2d77325e 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -35,7 +35,11 @@ namespace MWWorld bool toggleCollisionMode(); std::pair getFacedHandle (MWWorld::World& world); - + + btVector3 getRayPoint(float extent); + + std::vector < std::pair > getFacedObjects (); + // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 261f66ff4f..f185e411f8 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -498,13 +498,21 @@ namespace MWWorld std::string World::getFacedHandle() { - std::pair result = mPhysics->getFacedHandle (*this); + if (!mRendering->occlusionQuerySupported()) + { + std::pair result = mPhysics->getFacedHandle (*this); - if (result.first.empty() || - result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) - return ""; + if (result.first.empty() || + result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) + return ""; - return result.first; + return result.first; + } + else + { + // updated every few frames in update() + return mFacedHandle; + } } void World::deleteObject (Ptr ptr) @@ -715,6 +723,61 @@ namespace MWWorld sun = Vector3(sun.x, -sun.z, sun.y); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } + + // update faced handle (object the player is looking at) + // this uses a mixture of raycasts and occlusion queries. + else // if (mRendering->occlusionQuerySupported()) + { + MWRender::OcclusionQuery* query = mRendering->getOcclusionQuery(); + if (!query->occlusionTestPending()) + { + // get result of last query + if (mNumFacing == 0) mFacedHandle = ""; + else if (mNumFacing == 1) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced1Name : ""; + } + else if (mNumFacing == 2) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced2Name : mFaced1Name; + } + + // send new query + // figure out which object we want to test against + std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + + if (results.size() == 0) + { + mNumFacing = 0; + } + else if (results.size() == 1) + { + mFaced1 = getPtrViaHandle(results.front().second); + mFaced1Name = results.front().second; + mNumFacing = 1; + + btVector3 p = mPhysics->getRayPoint(results.front().first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + else + { + mFaced1Name = results.front().second; + mFaced2Name = results[1].second; + mFaced1 = getPtrViaHandle(results.front().second); + mFaced2 = getPtrViaHandle(results[1].second); + mNumFacing = 2; + + btVector3 p = mPhysics->getRayPoint(results[1].first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced2.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + } + } } bool World::isCellExterior() const diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 71cca3545d..7d8a1b9a0a 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -93,6 +93,12 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + std::string mFacedHandle; + Ptr mFaced1; + Ptr mFaced2; + std::string mFaced1Name; + std::string mFaced2Name; + int mNumFacing; int getDaysPerMonth (int month) const; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 97d2c004d1..3ecae7c0cc 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -241,8 +241,8 @@ namespace Physic static bool cmp( const std::pair& i, const std::pair& j ) { - if( i.first < j.first ) return false; - if( j.first < i.first ) return true; + if( i.first > j.first ) return false; + if( j.first > i.first ) return true; return false; } From 382fa6ac688097eb2980067255f4b8fe9395f3cd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 22:53:00 +0200 Subject: [PATCH 07/85] fixes --- apps/openmw/mwrender/occlusionquery.cpp | 10 +++++++--- libs/openengine/bullet/physic.cpp | 4 ++-- libs/openengine/bullet/physic.hpp | 7 +------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 66086a90b8..b5f3f9c102 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -49,6 +49,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setDepthWriteEnabled(false); matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + matQueryVisible->setCullingMode(CULL_NONE); + matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); @@ -133,16 +135,18 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) { - if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false) + if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false && mDoQuery) { // for some reason our single object query returns wrong results when the sun query was never executed // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) // so we force it here once everything has been rendered + mSunTotalAreaQuery->beginOcclusionQuery(); mSunTotalAreaQuery->endOcclusionQuery(); mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); - } + + } } void OcclusionQuery::update() @@ -201,7 +205,7 @@ void OcclusionQuery::update() for (std::vector::iterator it=mObjectsInfo.begin(); it!=mObjectsInfo.end(); ++it) { - if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) return; + if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) break; mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); } diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index e6a7393cca..b1cd9eadee 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -378,13 +378,11 @@ namespace Physic MyRayResultCallback resultCallback1; resultCallback1.m_collisionFilterMask = COL_WORLD; dynamicsWorld->rayTest(from, to, resultCallback1); - resultCallback1.sort(); std::vector< std::pair > results = resultCallback1.results; MyRayResultCallback resultCallback2; resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; dynamicsWorld->rayTest(from, to, resultCallback2); - resultCallback2.sort(); std::vector< std::pair > actorResults = resultCallback2.results; std::vector< std::pair > results2; @@ -401,6 +399,8 @@ namespace Physic results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); } + std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp); + return results2; } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 3ecae7c0cc..f6c52cbf95 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -239,18 +239,13 @@ namespace Physic return rayResult.m_hitFraction; } - static bool cmp( const std::pair& i, const std::pair& j ) + static bool cmp( const std::pair& i, const std::pair& j ) { if( i.first > j.first ) return false; if( j.first > i.first ) return true; return false; } - void sort() - { - std::sort(results.begin(), results.end(), cmp); - } - std::vector < std::pair > results; }; From 978b5f64d8767ef05b1010ecd8f862d36bba1b65 Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Sun, 25 Mar 2012 23:53:14 +0300 Subject: [PATCH 08/85] Merge cmake/findBullet patch for '_debug' postfixed libraries Merge in the patch from the cmake official repository: https://cmake.org/gitweb?p=cmake.git;a=commit;h=6f935d7f55af5c96d18d23b72991e87d1e029dda Without this fix cmake couldn't find bullet's debug libraries, and VS2010 openmw project file was generated with links to release libraries instead (which caused errors during build in debug configuration). --- cmake/FindBullet.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 7bdf75a0a3..552a0651af 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -51,13 +51,13 @@ find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h # Find the libraries _FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_d) +_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_d) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY LinearMath BulletMath) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG LinearMath_d BulletMath_d) +_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_d) +_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if From 35c3a0a3f921ef767121fea17cf2ebf1f4808f7e Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Mon, 26 Mar 2012 00:12:00 +0300 Subject: [PATCH 09/85] Add Boost_USE_STATIC_LIBS parameter for Windows If this parameter is not set in windows, cmake expects boost libraries to be named "boost_*.lib", while they are actually "libboost_*.lib" In findBoost.cmake (728): # Setting some more suffixes for the library set (Boost_LIB_PREFIX "") if (WIN32 AND Boost_USE_STATIC_LIBS AND NOT CYGWIN) set(Boost_LIB_PREFIX "lib") endif() --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f0bffa8f..d2801c8f22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,7 @@ set(OPENMW_LIBS_HEADER) # Platform specific if (WIN32) + set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) else (WIN32) From 98a33a7fc6c7f9e3f75f8ccbd93e7625a330eb1f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 23:28:51 +0200 Subject: [PATCH 10/85] fix --- apps/openmw/mwrender/occlusionquery.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index b5f3f9c102..971815f9bf 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -71,7 +71,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); - mBBQuerySingleObject->setDefaultDimensions(0.01, 0.01); + /// \todo ideally this should occupy exactly 1 pixel on the screen + mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003); mBBQuerySingleObject->createBillboard(Vector3::ZERO); mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); mBBQuerySingleObject->setRenderQueueGroup(queue); From 4ee03cd61fef8d71e0a4614ef512e34ce30fa278 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 26 Mar 2012 00:31:03 +0200 Subject: [PATCH 11/85] handle timeout --- apps/openmw/mwrender/occlusionquery.cpp | 16 +++++++++++++--- apps/openmw/mwrender/occlusionquery.hpp | 6 +++++- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 971815f9bf..6718a588bc 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), - mQuerySingleObjectRequested(false) + mQuerySingleObjectRequested(false), mResponding(true), mDelay(0) { mRendering = renderer; mSunNode = sunNode; @@ -93,7 +93,8 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - return mSupported; + if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; + return mSupported && mResponding; } void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, @@ -150,10 +151,13 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati } } -void OcclusionQuery::update() +void OcclusionQuery::update(float duration) { if (!mSupported) return; + mDelay += duration; + if (mDelay >= 2) mResponding = false; + mWasVisible = false; // Adjust the position of the sun billboards according to camera viewing distance @@ -172,6 +176,9 @@ void OcclusionQuery::update() if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) { + mDelay = 0; + mResponding = true; + unsigned int totalPixels; unsigned int visiblePixels; @@ -193,6 +200,9 @@ void OcclusionQuery::update() } if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) { + mDelay = 0; + mResponding = true; + unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 987087e278..004190cd90 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -39,7 +39,7 @@ namespace MWRender /** * per-frame update */ - void update(); + void update(float duration); /** * request occlusion test for a billboard at the given position, omitting an entity @@ -79,6 +79,10 @@ namespace MWRender bool mWasVisible; + bool mResponding; + + float mDelay; + bool mTestResult; bool mSupported; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b4711e8df7..2aec95e813 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -150,7 +150,7 @@ void RenderingManager::update (float duration){ mActors.update (duration); - mOcclusionQuery->update(); + mOcclusionQuery->update(duration); mSkyManager->update(duration); From bb3f3ce1db69ab935320bd9ddff0d540ccc25bc0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 26 Mar 2012 21:52:38 +0200 Subject: [PATCH 12/85] bugfix --- apps/openmw/mwrender/occlusionquery.cpp | 39 ++++++++++++++++--------- apps/openmw/mwrender/occlusionquery.hpp | 1 + apps/openmw/mwrender/sky.cpp | 3 +- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 6718a588bc..c3285a3359 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), - mQuerySingleObjectRequested(false), mResponding(true), mDelay(0) + mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; mSunNode = sunNode; @@ -93,7 +93,7 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; + //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; return mSupported && mResponding; } @@ -129,6 +129,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; mActiveQuery = mSingleObjectQuery; + mObjectWasVisible = true; } if (mActiveQuery != NULL) @@ -136,18 +137,29 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass } void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) -{ - if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false && mDoQuery) +{ + /** + * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa + * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called + * this can happen for example if the object that is tested is outside of the view frustum + * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually + */ + if (queueGroupId == RENDER_QUEUE_SKIES_LATE) { - // for some reason our single object query returns wrong results when the sun query was never executed - // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) - // so we force it here once everything has been rendered - - mSunTotalAreaQuery->beginOcclusionQuery(); - mSunTotalAreaQuery->endOcclusionQuery(); - mSunVisibleAreaQuery->beginOcclusionQuery(); - mSunVisibleAreaQuery->endOcclusionQuery(); - + if (mWasVisible == false && mDoQuery) + { + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } + if (mObjectWasVisible == false && mQuerySingleObjectRequested) + { + mSingleObjectQuery->beginOcclusionQuery(); + mSingleObjectQuery->endOcclusionQuery(); + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + } } } @@ -159,6 +171,7 @@ void OcclusionQuery::update(float duration) if (mDelay >= 2) mResponding = false; mWasVisible = false; + mObjectWasVisible = false; // Adjust the position of the sun billboards according to camera viewing distance // we need to do this to make sure that _everything_ can occlude the sun diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 004190cd90..a9fa0b5e4a 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -78,6 +78,7 @@ namespace MWRender std::vector mObjectsInfo; bool mWasVisible; + bool mObjectWasVisible; bool mResponding; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a70913b239..954b462095 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -294,7 +294,8 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) } SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) : - mGlare(0), mGlareFade(0) + mGlare(0), mGlareFade(0), mCloudBlendFactor(0), + mCloudOpacity(0), mCloudColour(1,1,1), mSkyColour(1,1,1), mCloudSpeed(0), mStarsOpacity(0) { mEnvironment = env; mViewport = pCamera->getViewport(); From e398c51f8a9b641b43658d14639f67301f8d0e11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 13:13:28 +0200 Subject: [PATCH 13/85] remove unneeded render queue workaround --- apps/openmw/mwrender/occlusionquery.cpp | 24 ------------------------ apps/openmw/mwrender/occlusionquery.hpp | 8 -------- 2 files changed, 32 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index c3285a3359..43a742a548 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -225,14 +225,6 @@ void OcclusionQuery::update(float duration) mBBQuerySingleObject->setVisible(false); - // restore old render queues - for (std::vector::iterator it=mObjectsInfo.begin(); - it!=mObjectsInfo.end(); ++it) - { - if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) break; - mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); - } - mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; } @@ -245,22 +237,6 @@ void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNod mBBQuerySingleObject->setVisible(true); - // we don't want the object to occlude itself - // put it in a render queue _after_ the occlusion query - mObjectsInfo.clear(); - for (int i=0; inumAttachedObjects(); ++i) - { - ObjectInfo info; - MovableObject* obj = object->getAttachedObject(i); - info.name = obj->getName(); - info.typeName = obj->getMovableType(); - info.oldRenderqueue = obj->getRenderQueueGroup(); - - mObjectsInfo.push_back(info); - - object->getAttachedObject(i)->setRenderQueueGroup(RENDER_QUEUE_MAIN+5); - } - mObjectNode->setPosition(position); // scale proportional to camera distance, in order to always give the billboard the same size in screen-space mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index a9fa0b5e4a..b3e5442cfe 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -18,13 +18,6 @@ namespace MWRender /// /// \brief Implements hardware occlusion queries on the GPU /// - struct ObjectInfo - { - int oldRenderqueue; - std::string name; - std::string typeName; - }; - class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener { public: @@ -75,7 +68,6 @@ namespace MWRender float mSunVisibility; Ogre::SceneNode* mObjectNode; - std::vector mObjectsInfo; bool mWasVisible; bool mObjectWasVisible; From 8f6d10f229a45bfa2c01364a4a0e9d1a2a78200e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 20:59:58 +0200 Subject: [PATCH 14/85] fix uninitalised stuff --- apps/openmw/mwrender/occlusionquery.cpp | 4 ++-- apps/openmw/mwworld/world.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 43a742a548..2c184c3451 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -11,7 +11,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), - mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; @@ -211,7 +211,7 @@ void OcclusionQuery::update(float duration) mDoQuery = true; } - if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) + if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) { mDelay = 0; mResponding = true; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index f185e411f8..567ed8f90b 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -157,7 +157,8 @@ namespace MWWorld const std::string& master, const boost::filesystem::path& resDir, bool newGame, Environment& environment, const std::string& encoding) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), - mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this) + mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this), + mNumFacing(0) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); From 43b1f896f42f986c6c19daaa3f3f78e6215e0953 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 22:36:02 +0200 Subject: [PATCH 15/85] fix object pickup when player is very close to object --- apps/openmw/mwworld/physicssystem.cpp | 9 ++------- apps/openmw/mwworld/world.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1d8f14cc42..83c3ef2bab 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -57,8 +57,6 @@ namespace MWWorld Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - //let's avoid the capsule shape of the player. - centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); @@ -71,11 +69,8 @@ namespace MWWorld Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - //let's avoid the capsule shape of the player. - centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); - btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); - btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); - return from * (1-extent) + to * extent; + btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); + return result; } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 567ed8f90b..dd30447338 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -749,6 +749,17 @@ namespace MWWorld // figure out which object we want to test against std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + // ignore the player + for (std::vector < std::pair < float, std::string > >::iterator it = results.begin(); + it != results.end(); ++it) + { + if ( (*it).second == mPlayer->getPlayer().getRefData().getHandle() ) + { + results.erase(it); + break; + } + } + if (results.size() == 0) { mNumFacing = 0; From 47c3e92db1b3264112657ea6f10178091fe0cf37 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 28 Mar 2012 21:42:41 +0200 Subject: [PATCH 16/85] removed the unused ray scene query --- apps/openmw/mwrender/renderingmanager.cpp | 3 --- apps/openmw/mwrender/renderingmanager.hpp | 1 - 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2dc35d8c84..00b43cf9fe 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -46,9 +46,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mMwRoot->pitch(Degree(-90)); mObjects.setMwRoot(mMwRoot); mActors.setMwRoot(mMwRoot); - - //used to obtain ingame information of ogre objects (which are faced or selected) - mRaySceneQuery = mRendering.getScene()->createRayQuery(Ray()); Ogre::SceneNode *playerNode = mMwRoot->createChildSceneNode ("player"); playerNode->pitch(Degree(90)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 569447bca3..edbc9aff27 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -158,7 +158,6 @@ class RenderingManager: private RenderingInterface { /// that the OGRE coordinate system matches that used internally in /// Morrowind. Ogre::SceneNode *mMwRoot; - Ogre::RaySceneQuery *mRaySceneQuery; OEngine::Physic::PhysicEngine* mPhysicsEngine; From e5a19209a4a8580dba93ca9560617e7dccf2306b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 29 Mar 2012 00:05:48 +0200 Subject: [PATCH 17/85] remove the timeout stuff --- apps/openmw/mwrender/occlusionquery.cpp | 13 ++----------- apps/openmw/mwrender/occlusionquery.hpp | 4 ---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 2c184c3451..228d8a4990 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; mSunNode = sunNode; @@ -94,7 +94,7 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; - return mSupported && mResponding; + return mSupported; } void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, @@ -167,9 +167,6 @@ void OcclusionQuery::update(float duration) { if (!mSupported) return; - mDelay += duration; - if (mDelay >= 2) mResponding = false; - mWasVisible = false; mObjectWasVisible = false; @@ -189,9 +186,6 @@ void OcclusionQuery::update(float duration) if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) { - mDelay = 0; - mResponding = true; - unsigned int totalPixels; unsigned int visiblePixels; @@ -213,9 +207,6 @@ void OcclusionQuery::update(float duration) } if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) { - mDelay = 0; - mResponding = true; - unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index b3e5442cfe..e81358eb6c 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -72,10 +72,6 @@ namespace MWRender bool mWasVisible; bool mObjectWasVisible; - bool mResponding; - - float mDelay; - bool mTestResult; bool mSupported; From 0d163d76ab2aca24a092f54a940132c17bf38679 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 03:59:24 +0200 Subject: [PATCH 18/85] Replaced some messy code with some differently messy code, this one at least seems to work --- components/bsa/bsa_archive.cpp | 158 ++++++++++++++------------------- 1 file changed, 69 insertions(+), 89 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 80d92dd521..0ba93d4ad2 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -45,6 +45,32 @@ struct ciLessBoost : std::binary_function } }; +struct mrComparer +{ +private: + int m_start, m_size; + + bool comparePortion(std::string file1, std::string file2, int start, int size) const + { + for(int i = start; i < start+size; i++) + { + char one = tolower(file1.at(i)); + char two = tolower(file2.at(i)); + if(one != two) + return (one < two); + } + return true; + } + +public: + mrComparer(int start, int size) : m_start(start), m_size(size) { } + + bool operator() (const std::string& first, const std::string& other) + { + return comparePortion(first, other, m_start, m_size); + } +}; + static bool fsstrict = false; /// An OGRE Archive wrapping a BSAFile archive @@ -55,16 +81,46 @@ class DirArchive: public Ogre::FileSystemArchive std::map, ciLessBoost> m; unsigned int cutoff; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool findFile(const String& filename, std::string& copy) const { - for(int i = start; i < start+size; i++) + copy = filename; + + std::replace(copy.begin(), copy.end(), '\\', '/'); + + if(copy.at(0) == '/') + copy.erase(0, 1); + + if(fsstrict == true) + return true; + + std::string folder; + int delimiter = 0; + size_t lastSlash = copy.rfind('/'); + if (lastSlash != std::string::npos) { - char one = file1.at(i); - char two = file2.at(i); - if(tolower(one) != tolower(two) ) - return false; + folder = copy.substr(0, lastSlash); + delimiter = lastSlash+1; } - return true; + + std::vector current; + { + std::map,ciLessBoost>::const_iterator found = m.find(folder); + if (found == m.end()) + { + std::replace(folder.begin(), folder.end(), '/', '\\'); + found = m.find(folder); + } + + current = found->second; + } + + mrComparer comp(delimiter, copy.size() - delimiter-1); + std::vector::iterator found = std::lower_bound(current.begin(), current.end(), copy, comp); + + if (found != current.end() && !(comp(copy, current.front()))) + return true; + + return false; } public: @@ -120,97 +176,21 @@ class DirArchive: public Ogre::FileSystemArchive void unload() {} bool exists(const String& filename) { - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - if(fsstrict == true) - { - //std::cout << "fsstrict " << copy << "\n"; + std::string copy; + + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - - std::vector& current = m[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::exists(*iter); - } - } - return false; } DataStreamPtr open(const String& filename, bool readonly = true) const { - std::map, ciLessBoost> mlocal = m; - std::string copy = filename; + std::string copy; - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - - if(fsstrict == true) - { + if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - std::vector current = mlocal[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::open(*iter, readonly); - } - } + DataStreamPtr p; return p; } From 71cb85dbc48118f49fc987be053e751d2b7a43b6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 04:54:33 +0200 Subject: [PATCH 19/85] Performance enhancment and minor copy-paste fix. --- components/bsa/bsa_archive.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 0ba93d4ad2..3981780bcb 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -50,7 +50,7 @@ struct mrComparer private: int m_start, m_size; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const { for(int i = start; i < start+size; i++) { @@ -59,7 +59,7 @@ private: if(one != two) return (one < two); } - return true; + return false; } public: From ce38876a74b09195d6d20dd82d49e944318b4080 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 17:31:55 +0200 Subject: [PATCH 20/85] Oops, that could've crashed horribly --- components/bsa/bsa_archive.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 3981780bcb..9a325d1605 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -111,7 +111,8 @@ class DirArchive: public Ogre::FileSystemArchive found = m.find(folder); } - current = found->second; + if (found != m.end()) + current = found->second; } mrComparer comp(delimiter, copy.size() - delimiter-1); From 3d5384e2bb350d42f0331fa77f59e3d5c804bb97 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 29 Mar 2012 11:28:33 -0700 Subject: [PATCH 21/85] Remove some unneeded casts --- apps/openmw/mwsound/soundmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index ad9e47f729..f072e084db 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -108,7 +108,7 @@ namespace MWSound max = std::max(min, max); } - return std::string("Sound/")+snd->sound; + return "Sound/"+snd->sound; } @@ -182,7 +182,7 @@ namespace MWSound { // The range values are not tested float basevol = 1.0f; /* TODO: volume settings */ - std::string filePath = std::string("Sound/")+filename; + std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, From c3944d3e1a48220d423849472eb5aebc746185ea Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 21:27:37 +0200 Subject: [PATCH 22/85] Use a normal binary search --- components/bsa/bsa_archive.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 9a325d1605..a68e91df1e 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -116,12 +116,7 @@ class DirArchive: public Ogre::FileSystemArchive } mrComparer comp(delimiter, copy.size() - delimiter-1); - std::vector::iterator found = std::lower_bound(current.begin(), current.end(), copy, comp); - - if (found != current.end() && !(comp(copy, current.front()))) - return true; - - return false; + return std::binary_search(current.begin(), current.end(), copy, comp); } public: From 6acd900577639bc4d83d0a0fcc444f806ab4c2c7 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 21:36:00 +0200 Subject: [PATCH 23/85] Better name --- components/bsa/bsa_archive.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index a68e91df1e..945aae0773 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -45,7 +45,7 @@ struct ciLessBoost : std::binary_function } }; -struct mrComparer +struct pathComparer { private: int m_start, m_size; @@ -63,7 +63,7 @@ private: } public: - mrComparer(int start, int size) : m_start(start), m_size(size) { } + pathComparer(int start, int size) : m_start(start), m_size(size) { } bool operator() (const std::string& first, const std::string& other) { @@ -115,7 +115,7 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - mrComparer comp(delimiter, copy.size() - delimiter-1); + pathComparer comp(delimiter, copy.size() - delimiter-1); return std::binary_search(current.begin(), current.end(), copy, comp); } From 60b95e7992f04db17c2a50871e979e71c2ed29bd Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 22:38:14 +0200 Subject: [PATCH 24/85] Sorting the file lists properly --- components/bsa/bsa_archive.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 945aae0773..64dc979803 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -155,6 +155,7 @@ class DirArchive: public Ogre::FileSystemArchive filesind.push_back(small); } } + std::sort(filesind.begin(), filesind.end(), ciLessBoost()); std::string small; std::string original = d.string(); if(cutoff < original.size()) From 4a9a416d46bb0bd7f2b92615dbde3b81b4975745 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 14:45:32 +0200 Subject: [PATCH 25/85] Can find files even if no .bsa file exists now --- apps/openmw/engine.cpp | 9 +++++++-- components/bsa/bsa_archive.cpp | 20 +++++++++++++++++++- components/files/collections.cpp | 5 +++++ components/files/collections.hpp | 2 ++ components/nifogre/ogre_nif_loader.cpp | 2 +- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5e49ae2f72..9903561911 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -204,13 +204,18 @@ OMW::Engine::~Engine() void OMW::Engine::loadBSA() { const Files::MultiDirCollection& bsa = mFileCollections.getCollection (".bsa"); - std::string dataDirectory; + for (Files::MultiDirCollection::TIter iter(bsa.begin()); iter!=bsa.end(); ++iter) { std::cout << "Adding " << iter->second.string() << std::endl; Bsa::addBSA(iter->second.string()); + } - dataDirectory = iter->second.parent_path().string(); + const Files::PathContainer& dataDirs = mFileCollections.getPaths(); + std::string dataDirectory; + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + dataDirectory = iter->string(); std::cout << "Data dir " << dataDirectory << std::endl; Bsa::addDir(dataDirectory, mFSStrict); } diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 64dc979803..36f4b423ce 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -52,8 +52,15 @@ private: bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const { + return lexicographical_compare(file1.substr(start,size), file2.substr(start,size), boost::algorithm::is_iless()); + for(int i = start; i < start+size; i++) { + if (i >= file1.size()) + return true; + else if (i >= file2.size()) + return false; + char one = tolower(file1.at(i)); char two = tolower(file2.at(i)); if(one != two) @@ -83,7 +90,18 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - copy = filename; + { + String passed = filename; + if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' + || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' + || filename.at(filename.length() - 1) == '|') + { + passed = filename.substr(0, filename.length() - 2); + } + if(filename.at(filename.length() - 2) == '>') + passed = filename.substr(0, filename.length() - 6); + copy = passed; + } std::replace(copy.begin(), copy.end(), '\\', '/'); diff --git a/components/files/collections.cpp b/components/files/collections.cpp index 424b558e60..50340dca4d 100644 --- a/components/files/collections.cpp +++ b/components/files/collections.cpp @@ -30,4 +30,9 @@ namespace Files return iter->second; } + + const Files::PathContainer& Collections::getPaths() const + { + return mDirectories; + } } diff --git a/components/files/collections.hpp b/components/files/collections.hpp index 1ddca9a5b7..70aaec55e3 100644 --- a/components/files/collections.hpp +++ b/components/files/collections.hpp @@ -21,6 +21,8 @@ namespace Files /// leading dot and must be all lower-case. const MultiDirCollection& getCollection(const std::string& extension) const; + const Files::PathContainer& getPaths() const; + private: typedef std::map MultiDirCollectionContainer; Files::PathContainer mDirectories; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index f943231d0d..2ab6ae6211 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1368,7 +1368,7 @@ void NIFLoader::loadResource(Resource *resource) if (!vfs->isFile(resourceName)) { - warn("File not found."); + warn("File "+resourceName+" not found."); return; } From d2f8539a4283bfd10c5a2639a64558d763adfd11 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 14:50:39 +0200 Subject: [PATCH 26/85] Forgot to remove some old code that didn't do anything --- components/bsa/bsa_archive.cpp | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 36f4b423ce..eb392757b8 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -50,31 +50,12 @@ struct pathComparer private: int m_start, m_size; - bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const - { - return lexicographical_compare(file1.substr(start,size), file2.substr(start,size), boost::algorithm::is_iless()); - - for(int i = start; i < start+size; i++) - { - if (i >= file1.size()) - return true; - else if (i >= file2.size()) - return false; - - char one = tolower(file1.at(i)); - char two = tolower(file2.at(i)); - if(one != two) - return (one < two); - } - return false; - } - public: pathComparer(int start, int size) : m_start(start), m_size(size) { } bool operator() (const std::string& first, const std::string& other) { - return comparePortion(first, other, m_start, m_size); + return lexicographical_compare(first.substr(m_start,m_size), other.substr(m_start,m_size), boost::algorithm::is_iless()); } }; From de102cd27465c22f32a6e16277c45a850d13e34d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 06:28:40 -0700 Subject: [PATCH 27/85] Simplify broken OpenAL workaround --- apps/openmw/mwsound/openal_output.cpp | 30 +++++---------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 0417596834..13ea49481d 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -442,33 +442,13 @@ void OpenAL_Output::init(const std::string &devname) { ALCuint maxtotal = std::min(maxmono+maxstereo, 256); if (maxtotal == 0) // workaround for broken implementations - { maxtotal = 256; - bool stop = false; - for(size_t i = 0;i < maxtotal && !stop;i++) // generate source until error returned - { - ALuint src = 0; - alGenSources(1, &src); - ALenum err = alGetError(); - if(err != AL_NO_ERROR) - { - stop = true; - } - else - { - mFreeSources.push_back(src); - } - } - } - else // normal case + for(size_t i = 0;i < maxtotal;i++) { - for(size_t i = 0;i < maxtotal;i++) - { - ALuint src = 0; - alGenSources(1, &src); - throwALerror(); - mFreeSources.push_back(src); - } + ALuint src = 0; + alGenSources(1, &src); + throwALerror(); + mFreeSources.push_back(src); } } catch(std::exception &e) From 71d9d7e94331df0ab05709cb00230b23fb836357 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 06:41:06 -0700 Subject: [PATCH 28/85] Store the current sound position with the Sound object --- apps/openmw/mwsound/sound.hpp | 6 +++++- apps/openmw/mwsound/soundmanager.cpp | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 2cbd48d961..f1b09875cd 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,6 +1,8 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H +#include + namespace MWSound { class Sound @@ -11,6 +13,7 @@ namespace MWSound Sound(const Sound &rhs); protected: + Ogre::Vector3 mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; float mMinDistance; @@ -21,7 +24,8 @@ namespace MWSound virtual bool isPlaying() = 0; virtual void setVolume(float volume) = 0; - Sound() : mVolume(1.0f) + Sound() : mPos(0.0f, 0.0f, 0.0f) + , mVolume(1.0f) , mBaseVolume(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index f072e084db..d2e52043ab 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -187,6 +187,7 @@ namespace MWSound SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, 20.0f, 12750.0f, false); + sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); sound->mBaseVolume = basevol; mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); @@ -241,6 +242,7 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); + sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -330,7 +332,10 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) + { snditer->first->update(pos.pos); + snditer->first->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + } snditer++; } } From 479df78ea185bd56b8fe4f214beb1c1a8bc034cd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:01:37 -0700 Subject: [PATCH 29/85] Update the actual sound position after the listener --- apps/openmw/mwsound/openal_output.cpp | 12 ++++++------ apps/openmw/mwsound/sound.hpp | 2 +- apps/openmw/mwsound/soundmanager.cpp | 9 +++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 13ea49481d..ad0f7603ba 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -90,7 +90,7 @@ public: virtual void stop(); virtual bool isPlaying(); virtual void setVolume(float volume); - virtual void update(const float *pos); + virtual void update(); void play(); bool process(); @@ -262,9 +262,9 @@ void OpenAL_SoundStream::setVolume(float volume) mVolume = volume; } -void OpenAL_SoundStream::update(const float *pos) +void OpenAL_SoundStream::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); @@ -340,7 +340,7 @@ public: virtual void stop(); virtual bool isPlaying(); virtual void setVolume(float volume); - virtual void update(const float *pos); + virtual void update(); }; OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) @@ -379,9 +379,9 @@ void OpenAL_Sound::setVolume(float volume) mVolume = volume; } -void OpenAL_Sound::update(const float *pos) +void OpenAL_Sound::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index f1b09875cd..521d8dbca6 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,7 +7,7 @@ namespace MWSound { class Sound { - virtual void update(const float *pos) = 0; + virtual void update() = 0; Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d2e52043ab..da7cb9d3b3 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -328,14 +328,12 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) - { - snditer->first->update(pos.pos); - snditer->first->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); - } + snditer->first->mPos = objpos; snditer++; } } @@ -428,7 +426,10 @@ namespace MWSound if(!snditer->first->isPlaying()) mActiveSounds.erase(snditer++); else + { + snditer->first->update(); snditer++; + } } } From a4576f043d110479a236f1c91659298582839c11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 30 Mar 2012 16:02:41 +0200 Subject: [PATCH 30/85] fixed the physics debug rendering now activates/deactivates properly --- apps/openmw/mwrender/debugging.cpp | 6 +----- libs/openengine/bullet/physic.cpp | 10 +++++++++- libs/openengine/bullet/physic.hpp | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 60b299acd1..4221852730 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -27,11 +27,7 @@ bool Debugging::toggleRenderMode (int mode){ switch (mode) { case MWWorld::World::Render_CollisionDebug: - - // TODO use a proper function instead of accessing the member variable - // directly. - eng->setDebugRenderingMode (!eng->isDebugCreated); - return eng->isDebugCreated; + return eng->toggleDebugRendering(); } return false; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 8b9f3dfecb..cc1f907a01 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -151,7 +151,8 @@ namespace Physic - PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) : + mDebugActive(0) { // Set up the collision configuration and dispatcher collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -203,6 +204,13 @@ namespace Physic createDebugRendering(); } mDebugDrawer->setDebugMode(mode); + mDebugActive = mode; + } + + bool PhysicEngine::toggleDebugRendering() + { + setDebugRenderingMode(!mDebugActive); + return mDebugActive; } PhysicEngine::~PhysicEngine() diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 57ffe91305..16dac96f4c 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -199,6 +199,8 @@ namespace Physic */ void setDebugRenderingMode(int mode); + bool toggleDebugRendering(); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ @@ -230,6 +232,7 @@ namespace Physic //debug rendering BtOgre::DebugDrawer* mDebugDrawer; bool isDebugCreated; + bool mDebugActive; }; }} From fad27d99e66c6892f6e09d03691246ce833b3346 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:10:34 -0700 Subject: [PATCH 31/85] Update the actual sound volume with the position --- apps/openmw/mwsound/openal_output.cpp | 18 ++---------------- apps/openmw/mwsound/sound.hpp | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ad0f7603ba..d8945cf52b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -89,7 +89,6 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void setVolume(float volume); virtual void update(); void play(); @@ -255,15 +254,9 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } -void OpenAL_SoundStream::setVolume(float volume) -{ - alSourcef(mSource, AL_GAIN, volume*mBaseVolume); - throwALerror(); - mVolume = volume; -} - void OpenAL_SoundStream::update() { + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -339,7 +332,6 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void setVolume(float volume); virtual void update(); }; @@ -372,15 +364,9 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } -void OpenAL_Sound::setVolume(float volume) -{ - alSourcef(mSource, AL_GAIN, volume*mBaseVolume); - throwALerror(); - mVolume = volume; -} - void OpenAL_Sound::update() { + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 521d8dbca6..d2c2d52091 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -22,7 +22,7 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; - virtual void setVolume(float volume) = 0; + void setVolume(float volume) { mVolume = volume; } Sound() : mPos(0.0f, 0.0f, 0.0f) , mVolume(1.0f) From 1ee8b963d06a17f58f4d28fac02ac3fefcab79bb Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:30:17 -0700 Subject: [PATCH 32/85] Store the sound listener position as well --- apps/openmw/mwsound/openal_output.cpp | 3 ++- apps/openmw/mwsound/sound_output.hpp | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index d8945cf52b..8d574b2f2b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -760,8 +760,9 @@ void OpenAL_Output::updateListener(const float *pos, const float *atdir, const f atdir[0], atdir[2], -atdir[1], updir[0], updir[2], -updir[1] }; + mPos = Ogre::Vector3(pos[0], pos[1], pos[2]); - alListener3f(AL_POSITION, pos[0], pos[2], -pos[1]); + alListener3f(AL_POSITION, mPos[0], mPos[2], -mPos[1]); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 794383591b..3c1a3d3127 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "soundmanager.hpp" #include "../mwworld/ptr.hpp" @@ -34,7 +36,13 @@ namespace MWSound Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); - Sound_Output(SoundManager &mgr) : mManager(mgr) { } + protected: + Ogre::Vector3 mPos; + + Sound_Output(SoundManager &mgr) + : mManager(mgr) + , mPos(0.0f, 0.0f, 0.0f) + { } public: virtual ~Sound_Output() { } From 8ac9dd8e70116eb8adf3f1f34a50a4d9b776fd32 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 16:59:19 +0200 Subject: [PATCH 33/85] Always use the same type of slashes --- components/bsa/bsa_archive.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index eb392757b8..e33f0ba49d 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -104,13 +104,10 @@ class DirArchive: public Ogre::FileSystemArchive std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); - if (found == m.end()) - { - std::replace(folder.begin(), folder.end(), '/', '\\'); - found = m.find(folder); - } - if (found != m.end()) + if (found == m.end()) + return false; + else current = found->second; } @@ -134,16 +131,14 @@ class DirArchive: public Ogre::FileSystemArchive //need to cut off first boost::filesystem::directory_iterator dir_iter(d), dir_end; std::vector filesind; - boost::filesystem::path f; for(;dir_iter != dir_end; dir_iter++) { if(boost::filesystem::is_directory(*dir_iter)) populateMap(*dir_iter); else { - - f = *dir_iter; - std::string s = f.string(); + std::string s = dir_iter->path().string(); + std::replace(s.begin(), s.end(), '\\', '/'); std::string small; if(cutoff < s.size()) @@ -155,14 +150,16 @@ class DirArchive: public Ogre::FileSystemArchive } } std::sort(filesind.begin(), filesind.end(), ciLessBoost()); + std::string small; std::string original = d.string(); + std::replace(original.begin(), original.end(), '\\', '/'); if(cutoff < original.size()) small = original.substr(cutoff, original.size() - cutoff); else small = original.substr(cutoff - 1, original.size() - cutoff); - m[small] = filesind; + m[small] = filesind; } bool isCaseSensitive() const { return fsstrict; } From fc4e4dc336feb15584875f651e88f1cd0936a8e6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 19:05:58 +0200 Subject: [PATCH 34/85] Case sensitiviy? --- components/bsa/bsa_archive.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index e33f0ba49d..bd61e03395 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -112,7 +112,13 @@ class DirArchive: public Ogre::FileSystemArchive } pathComparer comp(delimiter, copy.size() - delimiter-1); - return std::binary_search(current.begin(), current.end(), copy, comp); + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); + if (find != current.end() && !comp(copy, current.front())) + { + copy = *find; + return true; + } + return false; } public: From fefc8f86ab05f48cd5253a4939d96e9354c3fbd3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 11:11:07 -0700 Subject: [PATCH 35/85] Remove the unused streamSound3D method --- apps/openmw/mwsound/openal_output.cpp | 44 --------------------------- apps/openmw/mwsound/openal_output.hpp | 3 -- apps/openmw/mwsound/sound_output.hpp | 2 -- 3 files changed, 49 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 8d574b2f2b..da1de3d147 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -709,50 +709,6 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa return sound; } -SoundPtr OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) -{ - throwALerror(); - - boost::shared_ptr sound; - ALuint src; - - if(mFreeSources.empty()) - fail("No free sources"); - src = mFreeSources.front(); - mFreeSources.pop_front(); - - try - { - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); - } - catch(std::exception &e) - { - mFreeSources.push_back(src); - throw; - } - - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); - - sound->play(); - return sound; -} - void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index d288a62f39..f37f72cabd 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -43,10 +43,7 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop); - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); - virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max); virtual void updateListener(const float *pos, const float *atdir, const float *updir); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 3c1a3d3127..e54f9016da 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -28,8 +28,6 @@ namespace MWSound virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) = 0; virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; From fc167dbc83b6312433d1469ec8222f22ac47e10b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 11:42:11 -0700 Subject: [PATCH 36/85] Pass Ogre Vector3s to playSound3D and updateListener --- apps/openmw/mwsound/openal_output.cpp | 16 ++++++++-------- apps/openmw/mwsound/openal_output.hpp | 6 +++--- apps/openmw/mwsound/sound_output.hpp | 6 +++--- apps/openmw/mwsound/soundmanager.cpp | 16 +++++++++------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index da1de3d147..9fda1470c6 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -616,7 +616,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float return sound; } -SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, +SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, bool loop) { throwALerror(); @@ -643,7 +643,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, throw; } - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -710,15 +710,15 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa } -void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) { - float orient[6] = { - atdir[0], atdir[2], -atdir[1], - updir[0], updir[2], -updir[1] + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y }; - mPos = Ogre::Vector3(pos[0], pos[1], pos[2]); + mPos = pos; - alListener3f(AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index f37f72cabd..f4d4e90afd 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -41,11 +41,11 @@ namespace MWSound virtual void deinit(); virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); - virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop); + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, bool loop); virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); - virtual void updateListener(const float *pos, const float *atdir, const float *updir); + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index e54f9016da..ed3dda2f34 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -25,11 +25,11 @@ namespace MWSound virtual void deinit() = 0; virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; - virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) = 0; + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, bool loop) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index da7cb9d3b3..005df3e7ef 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -184,10 +184,11 @@ namespace MWSound float basevol = 1.0f; /* TODO: volume settings */ std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, + SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, 20.0f, 12750.0f, false); - sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + sound->mPos = objpos; sound->mBaseVolume = basevol; mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); @@ -240,9 +241,10 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); - sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); + sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -414,9 +416,9 @@ namespace MWSound // The output handler is expecting vectors oriented like the game // (that is, -Z goes down, +Y goes forward), but that's not what we // get from Ogre's camera, so we have to convert. - float pos[3] = { nPos[0], -nPos[2], nPos[1] }; - float at[3] = { nDir[0], -nDir[2], nDir[1] }; - float up[3] = { nUp[0], -nUp[2], nUp[1] }; + const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); + const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); + const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); mOutput->updateListener(pos, at, up); // Check if any sounds are finished playing, and trash them From fbd626baf69da248f64e0545d635799469a7a09e Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 20:59:44 +0200 Subject: [PATCH 37/85] mwiniimporter --- CMakeLists.txt | 7 +++++- apps/mwiniimporter/CMakeLists.txt | 21 +++++++++++++++++ apps/mwiniimporter/importer.cpp | 6 +++++ apps/mwiniimporter/importer.hpp | 17 ++++++++++++++ apps/mwiniimporter/main.cpp | 39 +++++++++++++++++++++++++++++++ apps/mwiniimporter/main.hpp | 11 +++++++++ 6 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 apps/mwiniimporter/CMakeLists.txt create mode 100644 apps/mwiniimporter/importer.cpp create mode 100644 apps/mwiniimporter/importer.hpp create mode 100644 apps/mwiniimporter/main.cpp create mode 100644 apps/mwiniimporter/main.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f007dbccf..552a6997a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,7 @@ if(DPKG_PROGRAM) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "nvidia-cg-toolkit (>= 2.1), libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -391,6 +391,11 @@ if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +option(BUILD_MWINIIMPORTER "build MWiniImporter inspector" ON) +if (BUILD_MWINIIMPORTER) + add_subdirectory( apps/mwiniimporter ) +endif() + if (WIN32) if (MSVC) if (USE_DEBUG_CONSOLE) diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt new file mode 100644 index 0000000000..1d7b7b624c --- /dev/null +++ b/apps/mwiniimporter/CMakeLists.txt @@ -0,0 +1,21 @@ +set(MWINIIMPORT + main.cpp + importer.cpp +) + +set(MWINIIMPORT_HEADER + main.hpp + importer.hpp +) + +source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) + +add_executable(mwiniimport + ${MWINIIMPORT} +) + +target_link_libraries(mwiniimport + ${Boost_LIBRARIES} + components +) + diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp new file mode 100644 index 0000000000..225b1667c4 --- /dev/null +++ b/apps/mwiniimporter/importer.cpp @@ -0,0 +1,6 @@ +#include "importer.hpp" + +void MwIniImporter::test() { + +} + diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp new file mode 100644 index 0000000000..026ed55bf9 --- /dev/null +++ b/apps/mwiniimporter/importer.hpp @@ -0,0 +1,17 @@ +#ifndef MWINIIMPORTER_IMPORTER +#define MWINIIMPORTER_IMPORTER 1 + +#include + +class MwIniImporter { + + public: + void test(); + + private: + + +}; + + +#endif diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp new file mode 100644 index 0000000000..46cb37e7f4 --- /dev/null +++ b/apps/mwiniimporter/main.cpp @@ -0,0 +1,39 @@ +#include "main.hpp" +#include "importer.hpp" + +int main(int argc, char *argv[]) { + + bpo::options_description desc("Syntax: mwiniimporter \nAllowed options"); + desc.add_options() + ("help,h", "produce help message") + ("in,i", bpo::value()->required(), "morrowind.ini input file") + ("out,o", bpo::value()->required(), "openmw.cfg output file") + ; + + bpo::variables_map vm; + try { + bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + bpo::notify(vm); + + } + catch(std::exception& e) { + std::cout << "Error:" << e.what() << std::endl; + return -1; + } + catch(...) { + std::cout << "Error" << std::endl; + return -1; + } + + if(vm.count("help")) { + std::cout << desc; + return 0; + } + + std::cout << "in:" << vm["in"].as() << std::endl; + + MwIniImporter importer; + importer.test(); + + return 0; +} diff --git a/apps/mwiniimporter/main.hpp b/apps/mwiniimporter/main.hpp new file mode 100644 index 0000000000..f93de7b078 --- /dev/null +++ b/apps/mwiniimporter/main.hpp @@ -0,0 +1,11 @@ +#ifndef MWINIIMPORTER_MAIN +#define MWINIIMPORTER_MAIN 1 + +#include +#include +#include + +namespace bpo = boost::program_options; + + +#endif From c160bc708091039cccdea53bc584704713193852 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 22:58:54 +0200 Subject: [PATCH 38/85] works, sort of --- apps/mwiniimporter/CMakeLists.txt | 1 - apps/mwiniimporter/importer.cpp | 94 ++++++++++++++++++++++++++++++- apps/mwiniimporter/importer.hpp | 18 +++++- apps/mwiniimporter/main.cpp | 58 +++++++++++++++---- apps/mwiniimporter/main.hpp | 11 ---- 5 files changed, 155 insertions(+), 27 deletions(-) delete mode 100644 apps/mwiniimporter/main.hpp diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 1d7b7b624c..2a8c0f5fea 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -4,7 +4,6 @@ set(MWINIIMPORT ) set(MWINIIMPORT_HEADER - main.hpp importer.hpp ) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 225b1667c4..d0cfe4a04e 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,6 +1,96 @@ #include "importer.hpp" +#include +#include +#include -void MwIniImporter::test() { - +void MwIniImporter::setVerbose(bool verbose) { + mVerbose = verbose; } +strmap MwIniImporter::loadIniFile(std::string filename) { + std::cout << "load ini file: " << filename << std::endl; + + std::map map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + // ignore sections for now + if(line.empty() || line[0] == ';' || line[0] == '[') { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + throw IniParseException(); + } + + map.insert(std::pair( + line.substr(0,pos), line.substr(pos+1) + )); + } + + return map; +} + +strmap MwIniImporter::loadCfgFile(std::string filename) { + std::cout << "load cfg file: " << filename << std::endl; + + std::map map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + if(line[0] == '[') { // section + continue; // ignore for now + } + + // we cant say comment by only looking at first char anymore + int comment_pos = line.find("#"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + throw IniParseException(); + } + + map.insert(std::pair( + line.substr(0,pos), line.substr(pos+1) + )); + } + + return map; +} + +void MwIniImporter::merge(strmap &cfg, strmap &ini) { + strmap::iterator ini_it; + for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + ini_it = ini.find(it->first); + + // found a key in both files + if(ini_it != ini.end()) { + cfg.erase(it); + cfg.insert(std::pair( + ini_it->first, ini_it->second + )); + } + } +} + +void MwIniImporter::writeToFile(std::string file, strmap &cfg) { + boost::iostreams::stream out(file); + + for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + out << (it->first) << "=" << (it->second) << std::endl; + } +} + + diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 026ed55bf9..1933830f21 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -2,14 +2,28 @@ #define MWINIIMPORTER_IMPORTER 1 #include +#include +#include + +typedef std::map strmap; + +class IniParseException : public std::exception { + virtual const char* what() const throw() { + return "unexpected end of line"; + } +}; class MwIniImporter { public: - void test(); + void setVerbose(bool verbose); + strmap loadIniFile(std::string filename); + strmap loadCfgFile(std::string filename); + void merge(strmap &cfg, strmap &ini); + void writeToFile(std::string file, strmap &cfg); private: - + bool mVerbose; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 46cb37e7f4..fa7a5c512f 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -1,39 +1,75 @@ -#include "main.hpp" #include "importer.hpp" +#include +#include +#include +#include + +namespace bpo = boost::program_options; + int main(int argc, char *argv[]) { bpo::options_description desc("Syntax: mwiniimporter \nAllowed options"); desc.add_options() ("help,h", "produce help message") - ("in,i", bpo::value()->required(), "morrowind.ini input file") - ("out,o", bpo::value()->required(), "openmw.cfg output file") + ("verbose,v", "verbose output") + ("ini,i", bpo::value()->required(), "morrowind.ini file") + ("cfg,c", bpo::value()->required(), "openmw.cfg file") + ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ; bpo::variables_map vm; try { bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + + // parse help before calling notify because we dont want it to throw an error if help is set + if(vm.count("help")) { + std::cout << desc; + return 0; + } + bpo::notify(vm); } catch(std::exception& e) { - std::cout << "Error:" << e.what() << std::endl; + std::cerr << "Error:" << e.what() << std::endl; return -1; } catch(...) { - std::cout << "Error" << std::endl; - return -1; + std::cerr << "Error" << std::endl; + return -2; } - if(vm.count("help")) { - std::cout << desc; - return 0; + std::string iniFile = vm["ini"].as(); + std::string cfgFile = vm["cfg"].as(); + + // if no output is given, write back to cfg file + std::string outputFile(vm["output"].as()); + if(vm["output"].defaulted()) { + outputFile = vm["cfg"].as(); } - std::cout << "in:" << vm["in"].as() << std::endl; + if(!boost::filesystem::exists(iniFile)) { + std::cerr << "ini file does not exist" << std::endl; + return -3; + } + if(!boost::filesystem::exists(cfgFile)) { + std::cerr << "cfg file does not exist" << std::endl; + return -4; + } MwIniImporter importer; - importer.test(); + importer.setVerbose(vm.count("verbose")); + + std::mapini = importer.loadIniFile(iniFile); + std::mapcfg = importer.loadCfgFile(cfgFile); + + importer.merge(cfg, ini); + + std::cout << "write to: " << outputFile << std::endl; + importer.writeToFile(outputFile, cfg); return 0; } + + diff --git a/apps/mwiniimporter/main.hpp b/apps/mwiniimporter/main.hpp deleted file mode 100644 index f93de7b078..0000000000 --- a/apps/mwiniimporter/main.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MWINIIMPORTER_MAIN -#define MWINIIMPORTER_MAIN 1 - -#include -#include -#include - -namespace bpo = boost::program_options; - - -#endif From 849c3a9bececfdeba8495f09dc057f62236027e8 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 23:12:52 +0200 Subject: [PATCH 39/85] add the section to the ini-keys --- apps/mwiniimporter/importer.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index d0cfe4a04e..f662f42d64 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -10,6 +10,7 @@ void MwIniImporter::setVerbose(bool verbose) { strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; + std::string section(""); std::map map; boost::iostreams::streamfile(filename.c_str()); @@ -17,17 +18,25 @@ strmap MwIniImporter::loadIniFile(std::string filename) { while (std::getline(file, line)) { // ignore sections for now - if(line.empty() || line[0] == ';' || line[0] == '[') { + if(line.empty() || line[0] == ';') { continue; } + if(line[0] == '[') { + if(line.length() > 2) { + section = line.substr(1, line.length()-3); + continue; + } + throw IniParseException(); + } + int pos = line.find("="); if(pos < 1) { throw IniParseException(); } map.insert(std::pair( - line.substr(0,pos), line.substr(pos+1) + section + " " + line.substr(0,pos), line.substr(pos+1) )); } From 8d9100c77b7fd67df4c18a1642beffcf3bc68b72 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 23:29:58 +0200 Subject: [PATCH 40/85] Debug output --- components/bsa/bsa_archive.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index bd61e03395..9d7212a5c4 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -101,12 +101,17 @@ class DirArchive: public Ogre::FileSystemArchive delimiter = lastSlash+1; } + std::cout << "Finding: " << copy; + std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); if (found == m.end()) + { + std::cout << " failed, couldn't find folder." << std::endl; return false; + } else current = found->second; } @@ -115,9 +120,18 @@ class DirArchive: public Ogre::FileSystemArchive std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { + std::cout << " found"; + if (copy != *find) + if (lexicographical_compare(copy, *find, boost::algorithm::is_iless())) + std::cout << " case folded to " << *find << std::endl; + else + std::cout << " as different file " << *find << std::endl; + copy = *find; return true; } + + std::cout << " failed, couldn't find file." << std::endl; return false; } From 8e07b7e05002e246d785b3c6d7b35ace6bdb68b1 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 00:54:49 +0200 Subject: [PATCH 41/85] Better (less) debug output --- components/bsa/bsa_archive.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 9d7212a5c4..6501271597 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -101,37 +101,34 @@ class DirArchive: public Ogre::FileSystemArchive delimiter = lastSlash+1; } - std::cout << "Finding: " << copy; - std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); if (found == m.end()) { - std::cout << " failed, couldn't find folder." << std::endl; return false; } else current = found->second; } + std::cout << "Finding: " << copy; + pathComparer comp(delimiter, copy.size() - delimiter-1); std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { std::cout << " found"; - if (copy != *find) - if (lexicographical_compare(copy, *find, boost::algorithm::is_iless())) - std::cout << " case folded to " << *find << std::endl; - else - std::cout << " as different file " << *find << std::endl; + if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iless())) + std::cout << ", as different file " << *find; + std::cout << "." << std::endl; copy = *find; return true; } - std::cout << " failed, couldn't find file." << std::endl; + std::cout << " failed." << std::endl; return false; } From 5adeee20fd893f1c7b04206365b6b06a8bacaa9c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 00:57:29 -0700 Subject: [PATCH 42/85] Mute sounds that go outside the max distance --- apps/openmw/mwsound/openal_output.cpp | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 9fda1470c6..1b90c62fa1 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -77,6 +77,7 @@ class OpenAL_SoundStream : public Sound ALuint mBufferSize; DecoderPtr mDecoder; + bool mIs3D; volatile bool mIsFinished; @@ -84,7 +85,7 @@ class OpenAL_SoundStream : public Sound OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D); virtual ~OpenAL_SoundStream(); virtual void stop(); @@ -164,8 +165,8 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) - : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D) + : mOutput(output), mSource(src), mDecoder(decoder), mIs3D(is3D), mIsFinished(true) { throwALerror(); @@ -256,7 +257,10 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, 0.0f); + else + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -323,11 +327,13 @@ class OpenAL_Sound : public Sound ALuint mSource; ALuint mBuffer; + bool mIs3D; + OpenAL_Sound(const OpenAL_Sound &rhs); OpenAL_Sound& operator=(const OpenAL_Sound &rhs); public: - OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D); virtual ~OpenAL_Sound(); virtual void stop(); @@ -335,8 +341,8 @@ public: virtual void update(); }; -OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) - : mOutput(output), mSource(src), mBuffer(buf) +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D) + : mOutput(output), mSource(src), mBuffer(buf), mIs3D(is3D) { } OpenAL_Sound::~OpenAL_Sound() @@ -366,7 +372,10 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, 0.0f); + else + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -583,7 +592,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, false)); } catch(std::exception &e) { @@ -632,7 +641,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, true)); } catch(std::exception &e) { @@ -651,7 +660,8 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_MAX_DISTANCE, max); alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? + 0.0f : volume); alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); @@ -682,7 +692,7 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa { DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder, false)); } catch(std::exception &e) { From b01289128bb50e6216799cbb0e6427bb0df369d9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 01:15:27 -0700 Subject: [PATCH 43/85] Split OpenAL_Sound into separate classes for 2D and 3D sounds --- apps/openmw/mwsound/openal_output.cpp | 57 ++++++++++++++++++--------- apps/openmw/mwsound/openal_output.hpp | 1 + 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 1b90c62fa1..3ed86cf2bc 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -77,7 +77,6 @@ class OpenAL_SoundStream : public Sound ALuint mBufferSize; DecoderPtr mDecoder; - bool mIs3D; volatile bool mIsFinished; @@ -85,7 +84,7 @@ class OpenAL_SoundStream : public Sound OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); virtual ~OpenAL_SoundStream(); virtual void stop(); @@ -165,8 +164,8 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D) - : mOutput(output), mSource(src), mDecoder(decoder), mIs3D(is3D), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) + : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -257,10 +256,7 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - alSourcef(mSource, AL_GAIN, 0.0f); - else - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -318,22 +314,22 @@ bool OpenAL_SoundStream::process() } // -// A regular OpenAL sound +// A regular 2D OpenAL sound // class OpenAL_Sound : public Sound { +protected: OpenAL_Output &mOutput; ALuint mSource; ALuint mBuffer; - bool mIs3D; - +private: OpenAL_Sound(const OpenAL_Sound &rhs); OpenAL_Sound& operator=(const OpenAL_Sound &rhs); public: - OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D); + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); virtual ~OpenAL_Sound(); virtual void stop(); @@ -341,8 +337,24 @@ public: virtual void update(); }; -OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D) - : mOutput(output), mSource(src), mBuffer(buf), mIs3D(is3D) +// +// A regular 3D OpenAL sound +// +class OpenAL_Sound3D : public OpenAL_Sound +{ + OpenAL_Sound3D(const OpenAL_Sound &rhs); + OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs); + +public: + OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf) + : OpenAL_Sound(output, src, buf) + { } + + virtual void update(); +}; + +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) + : mOutput(output), mSource(src), mBuffer(buf) { } OpenAL_Sound::~OpenAL_Sound() @@ -372,7 +384,16 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +void OpenAL_Sound3D::update() +{ + if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) alSourcef(mSource, AL_GAIN, 0.0f); else alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); @@ -592,7 +613,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf, false)); + sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { @@ -641,7 +662,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf, true)); + sound.reset(new OpenAL_Sound3D(*this, src, buf)); } catch(std::exception &e) { @@ -692,7 +713,7 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa { DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder, false)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder)); } catch(std::exception &e) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index f4d4e90afd..35966cc29b 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -57,6 +57,7 @@ namespace MWSound std::auto_ptr mStreamThread; friend class OpenAL_Sound; + friend class OpenAL_Sound3D; friend class OpenAL_SoundStream; friend class SoundManager; }; From 4e908aa0954801549c5d0f8c30bad057e65c5c9e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 01:34:26 -0700 Subject: [PATCH 44/85] Add a method to set the sound's position --- apps/openmw/mwsound/sound.hpp | 1 + apps/openmw/mwsound/soundmanager.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index d2c2d52091..e23830cdb9 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -22,6 +22,7 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; + void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } Sound() : mPos(0.0f, 0.0f, 0.0f) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 005df3e7ef..9c26f00846 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -335,7 +335,7 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) - snditer->first->mPos = objpos; + snditer->first->setPosition(objpos); snditer++; } } From 06a34b9e0d9edf3060690094ec8ed8a0c1194c77 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 10:35:08 +0200 Subject: [PATCH 45/85] Strange problems call for strange solutions. --- components/bsa/bsa_archive.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 6501271597..660b24d560 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,6 +71,9 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { + if (filename.find(".tga") != std::string::npos) + return false; + { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -120,8 +123,8 @@ class DirArchive: public Ogre::FileSystemArchive if (find != current.end() && !comp(copy, current.front())) { std::cout << " found"; - if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iless())) - std::cout << ", as different file " << *find; + if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iequal())) + std::cout << ", as different file " << *find; std::cout << "." << std::endl; copy = *find; From 28378c063b0b7601e2846d33963834473f35dad3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 02:22:23 -0700 Subject: [PATCH 46/85] Minor OpenAL_SoundStream cleanups --- apps/openmw/mwsound/openal_output.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3ed86cf2bc..c40b340c56 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -65,7 +65,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 6; - static const ALfloat sBufferLength; + static const ALfloat sBufferLength = 0.125f; OpenAL_Output &mOutput; @@ -95,7 +95,6 @@ public: bool process(); }; -const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; // // A background streaming thread (keeps active streams processed) @@ -186,7 +185,6 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode } catch(std::exception &e) { - mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); throw; From 3b0dc408ae1866b5a0d702b8fce6e453eb697b47 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 11:29:24 +0200 Subject: [PATCH 47/85] Debug-b-gone --- components/bsa/bsa_archive.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 660b24d560..41bff7e40a 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,9 +71,6 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - if (filename.find(".tga") != std::string::npos) - return false; - { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -116,22 +113,14 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - std::cout << "Finding: " << copy; - pathComparer comp(delimiter, copy.size() - delimiter-1); std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { - std::cout << " found"; - if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iequal())) - std::cout << ", as different file " << *find; - - std::cout << "." << std::endl; copy = *find; return true; } - std::cout << " failed." << std::endl; return false; } From 6eb3281c4c504dfc2d6398156ee5c449772325c8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 11:36:51 +0200 Subject: [PATCH 48/85] boost fix --- apps/mwiniimporter/main.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index fa7a5c512f..059703ea89 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -13,23 +13,23 @@ int main(int argc, char *argv[]) { desc.add_options() ("help,h", "produce help message") ("verbose,v", "verbose output") - ("ini,i", bpo::value()->required(), "morrowind.ini file") - ("cfg,c", bpo::value()->required(), "openmw.cfg file") + ("ini,i", bpo::value(), "morrowind.ini file") + ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ; - + bpo::variables_map vm; try { bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); - + // parse help before calling notify because we dont want it to throw an error if help is set if(vm.count("help")) { std::cout << desc; return 0; } - + bpo::notify(vm); - + } catch(std::exception& e) { std::cerr << "Error:" << e.what() << std::endl; @@ -39,16 +39,16 @@ int main(int argc, char *argv[]) { std::cerr << "Error" << std::endl; return -2; } - + std::string iniFile = vm["ini"].as(); std::string cfgFile = vm["cfg"].as(); - + // if no output is given, write back to cfg file std::string outputFile(vm["output"].as()); if(vm["output"].defaulted()) { outputFile = vm["cfg"].as(); } - + if(!boost::filesystem::exists(iniFile)) { std::cerr << "ini file does not exist" << std::endl; return -3; @@ -57,19 +57,17 @@ int main(int argc, char *argv[]) { std::cerr << "cfg file does not exist" << std::endl; return -4; } - + MwIniImporter importer; importer.setVerbose(vm.count("verbose")); - + std::mapini = importer.loadIniFile(iniFile); std::mapcfg = importer.loadCfgFile(cfgFile); - + importer.merge(cfg, ini); - + std::cout << "write to: " << outputFile << std::endl; importer.writeToFile(outputFile, cfg); - + return 0; } - - From 7541e08909ee3be2655831bdf0a680ea45112a82 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 02:48:14 -0700 Subject: [PATCH 49/85] Don't reset the sound Output device if init fails --- apps/openmw/mwsound/soundmanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9c26f00846..d537a2bbd4 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -62,7 +62,6 @@ namespace MWSound catch(std::exception &e) { std::cout <<"Sound init failed: "< Date: Sat, 31 Mar 2012 03:31:41 -0700 Subject: [PATCH 50/85] Finally "fix" --nosound Expect degraded performance with it. Looping sounds are constantly checked to see if they're playing, and try to play it again when it's not. --- apps/openmw/mwsound/openal_output.cpp | 41 ++++++++++++++++----------- apps/openmw/mwsound/soundmanager.cpp | 5 ++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c40b340c56..6a4e3fe570 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -25,14 +25,20 @@ static void throwALCerror(ALCdevice *device) { ALCenum err = alcGetError(device); if(err != ALC_NO_ERROR) - fail(alcGetString(device, err)); + { + const ALCchar *errstring = alcGetString(device, err); + fail(errstring ? errstring : ""); + } } static void throwALerror() { ALenum err = alGetError(); if(err != AL_NO_ERROR) - fail(alGetString(err)); + { + const ALchar *errstring = alGetString(err); + fail(errstring ? errstring : ""); + } } @@ -424,8 +430,7 @@ std::vector OpenAL_Output::enumerate() void OpenAL_Output::init(const std::string &devname) { - if(mDevice || mContext) - fail("Device already open"); + deinit(); mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) @@ -442,7 +447,12 @@ void OpenAL_Output::init(const std::string &devname) mContext = alcCreateContext(mDevice, NULL); if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) + { + if(mContext) + alcDestroyContext(mContext); + mContext = 0; fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); + } alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); throwALerror(); @@ -598,8 +608,6 @@ void OpenAL_Output::bufferFinished(ALuint buf) SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) { - throwALerror(); - boost::shared_ptr sound; ALuint src=0, buf=0; @@ -647,8 +655,6 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, bool loop) { - throwALerror(); - boost::shared_ptr sound; ALuint src=0, buf=0; @@ -697,8 +703,6 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) { - throwALerror(); - boost::shared_ptr sound; ALuint src; @@ -741,15 +745,18 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) { - ALfloat orient[6] = { - atdir.x, atdir.z, -atdir.y, - updir.x, updir.z, -updir.y - }; mPos = pos; - alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); - alListenerfv(AL_ORIENTATION, orient); - throwALerror(); + if(mContext) + { + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y + }; + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); + alListenerfv(AL_ORIENTATION, orient); + throwALerror(); + } } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d537a2bbd4..534b5b6deb 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -41,6 +41,8 @@ namespace MWSound SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment) : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) , mEnvironment(environment) + , mOutput(new DEFAULT_OUTPUT(*this)) + { if(!useSound) return; @@ -50,8 +52,6 @@ namespace MWSound try { - mOutput.reset(new DEFAULT_OUTPUT(*this)); - std::vector names = mOutput->enumerate(); std::cout <<"Enumerated output devices:"<< std::endl; for(size_t i = 0;i < names.size();i++) @@ -62,7 +62,6 @@ namespace MWSound catch(std::exception &e) { std::cout <<"Sound init failed: "< Date: Sat, 31 Mar 2012 14:28:19 +0200 Subject: [PATCH 51/85] ignore syntax errors and empty lines; fixed merge function --- apps/mwiniimporter/importer.cpp | 37 +++++++++++++++------------------ apps/mwiniimporter/importer.hpp | 14 ++++++------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f662f42d64..9a76adeed8 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -25,19 +25,16 @@ strmap MwIniImporter::loadIniFile(std::string filename) { if(line[0] == '[') { if(line.length() > 2) { section = line.substr(1, line.length()-3); - continue; } - throw IniParseException(); + continue; } int pos = line.find("="); if(pos < 1) { - throw IniParseException(); + continue; } - map.insert(std::pair( - section + " " + line.substr(0,pos), line.substr(pos+1) - )); + map.insert(STRPAIR(section + ":" + line.substr(0,pos), line.substr(pos+1))); } return map; @@ -68,32 +65,32 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { int pos = line.find("="); if(pos < 1) { - throw IniParseException(); + continue; } - map.insert(std::pair( - line.substr(0,pos), line.substr(pos+1) - )); + map.insert(STRPAIR(line.substr(0,pos), line.substr(pos+1))); } return map; } void MwIniImporter::merge(strmap &cfg, strmap &ini) { - strmap::iterator ini_it; - for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { - ini_it = ini.find(it->first); - - // found a key in both files - if(ini_it != ini.end()) { - cfg.erase(it); - cfg.insert(std::pair( - ini_it->first, ini_it->second - )); + strmap::iterator cfgIt; + strmap::iterator iniIt; + for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { + if((iniIt = ini.find(it->second)) != ini.end()) { + cfg.erase(it->first); + if(!this->specialMerge(it->first, it->second, cfg, ini)) { + cfg.insert(STRPAIR(it->first, iniIt->second)); + } } } } +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini) { + return false; +} + void MwIniImporter::writeToFile(std::string file, strmap &cfg) { boost::iostreams::stream out(file); diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 1933830f21..3ab1d892ee 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -5,17 +5,16 @@ #include #include -typedef std::map strmap; -class IniParseException : public std::exception { - virtual const char* what() const throw() { - return "unexpected end of line"; - } -}; +typedef std::map strmap; +#define STRPAIR std::make_pair class MwIniImporter { public: + MwIniImporter() { + mMergeMap.insert(STRPAIR("fps", "General:Show FPS")); + }; void setVerbose(bool verbose); strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); @@ -23,8 +22,9 @@ class MwIniImporter { void writeToFile(std::string file, strmap &cfg); private: + bool specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini); bool mVerbose; - + strmap mMergeMap; }; From b7635b3d4a12bacf31a0c4d7aad92bfaacd2b711 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 14:34:00 +0200 Subject: [PATCH 52/85] pass maps by reference --- apps/mwiniimporter/importer.cpp | 2 +- apps/mwiniimporter/importer.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 9a76adeed8..041712b216 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -87,7 +87,7 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } -bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini) { +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 3ab1d892ee..d0034a13d4 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -22,7 +22,7 @@ class MwIniImporter { void writeToFile(std::string file, strmap &cfg); private: - bool specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini); + bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); bool mVerbose; strmap mMergeMap; }; From e48d125a84165489825103ddfeabfb6e1095f74a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 05:57:03 -0700 Subject: [PATCH 53/85] Only allow one instance of a given tracked soundid The untracked flag should probably be broken up and combined with the loop boolean into a set of flags. --- apps/openmw/mwsound/soundmanager.cpp | 42 +++++++++++++++++++++++++--- apps/openmw/mwsound/soundmanager.hpp | 3 ++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 534b5b6deb..9a71ef4210 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -67,6 +67,7 @@ namespace MWSound SoundManager::~SoundManager() { + mSingleSounds.clear(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -231,15 +232,29 @@ namespace MWSound float volume, float pitch, bool loop, bool untracked) { + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; + + if(!untracked) + { + IDSoundMap::iterator inviter = mSingleSounds.find(soundId); + if(inviter != mSingleSounds.end()) + { + if(inviter->second->mPos.squaredDistance(mOutput->mPos) < + objpos.squaredDistance(mOutput->mPos)) + return sound; + inviter->second->stop(); + mSingleSounds.erase(inviter); + } + } + try { // Look up the sound in the ESM data float basevol = 1.0f; /* TODO: volume settings */ float min, max; std::string file = lookup(soundId, basevol, min, max); - const ESM::Position &pos = ptr.getCellRef().pos; - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); sound->mPos = objpos; @@ -248,8 +263,13 @@ namespace MWSound sound->mMinDistance = min; sound->mMaxDistance = max; - mActiveSounds[sound] = (!untracked ? std::make_pair(ptr, soundId) : - std::make_pair(MWWorld::Ptr(), soundId)); + if(untracked) + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + else + { + mActiveSounds[sound] = std::make_pair(ptr, soundId); + mSingleSounds[soundId] = sound; + } } catch(std::exception &e) { @@ -265,6 +285,9 @@ namespace MWSound { if(snditer->second.first == ptr && snditer->second.second == soundId) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -280,6 +303,9 @@ namespace MWSound { if(snditer->second.first == ptr) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -296,6 +322,9 @@ namespace MWSound if(snditer->second.first != MWWorld::Ptr() && snditer->second.first.getCell() == cell) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -424,7 +453,12 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(!snditer->first->isPlaying()) + { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); mActiveSounds.erase(snditer++); + } else { snditer->first->update(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 3ab1e881c3..3ab2b569ea 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -45,6 +45,9 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; + typedef std::map IDSoundMap; + IDSoundMap mSingleSounds; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); From ae308b9b5fc4c73859167d4390cb61a2b264bc2f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 07:31:55 -0700 Subject: [PATCH 54/85] Use a set of flags instead of separate booleans --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 4 ++-- apps/openmw/mwclass/door.cpp | 6 +++--- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 4 ++-- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwscript/soundextensions.cpp | 4 ++-- apps/openmw/mwsound/soundmanager.cpp | 13 ++++++------- apps/openmw/mwsound/soundmanager.hpp | 21 ++++++++++++++++++--- 17 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e95fb572f3..d27d0bc71d 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index e1c2734f0e..9956a56fb5 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -60,7 +60,7 @@ namespace MWClass boost::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0a81ebafb7..76370dc5c0 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -58,7 +58,7 @@ namespace MWClass { // TODO implement reading - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 4fe19ada40..2357851d75 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index c58a25c03e..29b3331ba9 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -85,7 +85,7 @@ namespace MWClass { // TODO check for key std::cout << "Locked container" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } else @@ -100,7 +100,7 @@ namespace MWClass { // Trap activation goes here std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5654dff698..9d6c6a78dc 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -73,7 +73,7 @@ namespace MWClass // TODO check for key // TODO report failure to player (message, sound?). Look up behaviour of original MW. std::cout << "Locked!" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } @@ -81,7 +81,7 @@ namespace MWClass { // Trap activation std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } @@ -110,7 +110,7 @@ namespace MWClass // TODO return action for rotating the door // This is a little pointless, but helps with testing - environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 1a7edf6325..cbe153ba3a 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -54,7 +54,7 @@ namespace MWClass boost::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index e2e63a89bb..71e4775916 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -59,7 +59,7 @@ namespace MWClass if (!ref->base->sound.empty()) { - environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, true); + environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); } } @@ -83,7 +83,7 @@ namespace MWClass if (!(ref->base->data.flags & ESM::Light::Carry)) return boost::shared_ptr (new MWWorld::NullAction); - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 3dda2f4af0..1eef0db8ba 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -58,7 +58,7 @@ namespace MWClass boost::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 864fc1e382..def1a90a86 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4ab3745900..ed1733e2d2 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4b4d79a73e..8013e2e80f 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 758bf40797..d49979861b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 20db0cf38f..e36e9202fa 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index d5cc41b76f..7ae109075d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); } }; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9a71ef4210..2d743d9cbf 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -204,7 +204,7 @@ namespace MWSound } - SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, int mode) { SoundPtr sound; try @@ -213,7 +213,7 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound(file, volume*basevol, pitch, loop); + sound = mOutput->playSound(file, volume*basevol, pitch, mode&Play_Loop); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -229,14 +229,13 @@ namespace MWSound } SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, bool loop, - bool untracked) + float volume, float pitch, int mode) { const ESM::Position &pos = ptr.getCellRef().pos; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; - if(!untracked) + if((mode&Play_Single)) { IDSoundMap::iterator inviter = mSingleSounds.find(soundId); if(inviter != mSingleSounds.end()) @@ -256,14 +255,14 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; - if(untracked) + if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else { diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 3ab2b569ea..03cacca237 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -30,6 +30,22 @@ namespace MWSound typedef boost::shared_ptr DecoderPtr; typedef boost::shared_ptr SoundPtr; + enum PlayMode { + Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ + Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ + Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ + Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position + * but do not keep it updated (the sound will not move with + * the object and will not stop when the object is deleted. */ + Play_Single = 1<<3, /* (3D only) Play only a single instance of the given sound id. + * Sounds not marked as Single will not count, and all but the + * closest to the listener's position will be stopped. */ + }; + static inline int operator|(const PlayMode &a, const PlayMode &b) + { return (int)a | (int)b; } + static inline int operator&(const PlayMode &a, const PlayMode &b) + { return (int)a & (int)b; } + class SoundManager { Ogre::ResourceGroupManager& mResourceMgr; @@ -90,12 +106,11 @@ namespace MWSound bool sayDone(MWWorld::Ptr reference) const; ///< Is actor not speaking? - SoundPtr playSound(const std::string& soundId, float volume, float pitch, bool loop=false); + SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); ///< Play a sound, independently of 3D-position SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, bool loop, - bool untracked=false); + float volume, float pitch, int mode=Play_Normal); ///< Play a sound from an object void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); From 977e7ac9a3c49a4771c2f8ede2193429399a5ee0 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 07:41:26 -0700 Subject: [PATCH 55/85] Remove the Play_Single flag. It's not correct. --- apps/openmw/mwscript/soundextensions.cpp | 4 +-- apps/openmw/mwsound/soundmanager.cpp | 36 ++---------------------- apps/openmw/mwsound/soundmanager.hpp | 6 ---- 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 7ae109075d..b4386a8a0d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWSound::Play_Loop : 0); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop ? MWSound::Play_Loop : 0); } }; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 2d743d9cbf..6249c4e4df 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -67,7 +67,6 @@ namespace MWSound SoundManager::~SoundManager() { - mSingleSounds.clear(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -231,29 +230,15 @@ namespace MWSound SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, float volume, float pitch, int mode) { - const ESM::Position &pos = ptr.getCellRef().pos; - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; - - if((mode&Play_Single)) - { - IDSoundMap::iterator inviter = mSingleSounds.find(soundId); - if(inviter != mSingleSounds.end()) - { - if(inviter->second->mPos.squaredDistance(mOutput->mPos) < - objpos.squaredDistance(mOutput->mPos)) - return sound; - inviter->second->stop(); - mSingleSounds.erase(inviter); - } - } - try { // Look up the sound in the ESM data float basevol = 1.0f; /* TODO: volume settings */ float min, max; std::string file = lookup(soundId, basevol, min, max); + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); sound->mPos = objpos; @@ -265,10 +250,7 @@ namespace MWSound if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else - { mActiveSounds[sound] = std::make_pair(ptr, soundId); - mSingleSounds[soundId] = sound; - } } catch(std::exception &e) { @@ -284,9 +266,6 @@ namespace MWSound { if(snditer->second.first == ptr && snditer->second.second == soundId) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -302,9 +281,6 @@ namespace MWSound { if(snditer->second.first == ptr) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -321,9 +297,6 @@ namespace MWSound if(snditer->second.first != MWWorld::Ptr() && snditer->second.first.getCell() == cell) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -452,12 +425,7 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(!snditer->first->isPlaying()) - { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); mActiveSounds.erase(snditer++); - } else { snditer->first->update(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 03cacca237..de5cca839c 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -37,9 +37,6 @@ namespace MWSound Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ - Play_Single = 1<<3, /* (3D only) Play only a single instance of the given sound id. - * Sounds not marked as Single will not count, and all but the - * closest to the listener's position will be stopped. */ }; static inline int operator|(const PlayMode &a, const PlayMode &b) { return (int)a | (int)b; } @@ -61,9 +58,6 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; - typedef std::map IDSoundMap; - IDSoundMap mSingleSounds; - std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); From ceedae4a1af3c155f31221376ac9ef66c3e9dca8 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 16:54:53 +0200 Subject: [PATCH 56/85] technical corrections --- apps/mwiniimporter/importer.cpp | 18 +++++++++++++++--- apps/mwiniimporter/importer.hpp | 5 +---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 041712b216..09088774b4 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,6 +3,18 @@ #include #include +MwIniImporter::MwIniImporter() { + const char *map[][2] = + { + { "fps", "General:Show FPS" }, + { 0, 0 } + }; + + for(int i=0; map[i][0]; i++) { + mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); + } +} + void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } @@ -34,7 +46,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } - map.insert(STRPAIR(section + ":" + line.substr(0,pos), line.substr(pos+1))); + map.insert(std::make_pair(section + ":" + line.substr(0,pos), line.substr(pos+1))); } return map; @@ -68,7 +80,7 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { continue; } - map.insert(STRPAIR(line.substr(0,pos), line.substr(pos+1))); + map.insert(std::make_pair(line.substr(0,pos), line.substr(pos+1))); } return map; @@ -81,7 +93,7 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { if((iniIt = ini.find(it->second)) != ini.end()) { cfg.erase(it->first); if(!this->specialMerge(it->first, it->second, cfg, ini)) { - cfg.insert(STRPAIR(it->first, iniIt->second)); + cfg.insert(std::make_pair(it->first, iniIt->second)); } } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index d0034a13d4..ad5aaacde3 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -7,14 +7,11 @@ typedef std::map strmap; -#define STRPAIR std::make_pair class MwIniImporter { public: - MwIniImporter() { - mMergeMap.insert(STRPAIR("fps", "General:Show FPS")); - }; + MwIniImporter(); void setVerbose(bool verbose); strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); From 575474ff69459e52e0d7bba14fadc86da4fd8f84 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 08:14:39 -0700 Subject: [PATCH 57/85] Pass the mode flags to the sound output play methods --- apps/openmw/mwsound/openal_output.cpp | 8 ++++---- apps/openmw/mwsound/openal_output.hpp | 4 ++-- apps/openmw/mwsound/sound.hpp | 2 ++ apps/openmw/mwsound/sound_output.hpp | 4 ++-- apps/openmw/mwsound/soundmanager.cpp | 8 +++++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6a4e3fe570..ddf4df7057 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -606,7 +606,7 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) +SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -642,7 +642,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); @@ -653,7 +653,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float } SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, - float min, float max, bool loop) + float min, float max, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -690,7 +690,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 35966cc29b..a709576bae 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -40,9 +40,9 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, bool loop); + float volume, float pitch, float min, float max, int flags); virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index e23830cdb9..ca12ec5571 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -18,6 +18,7 @@ namespace MWSound float mBaseVolume; float mMinDistance; float mMaxDistance; + int mFlags; public: virtual void stop() = 0; @@ -30,6 +31,7 @@ namespace MWSound , mBaseVolume(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ + , mFlags(Play_Normal) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index ed3dda2f34..1507e18472 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,9 +24,9 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, bool loop) = 0; + float volume, float pitch, float min, float max, int flags) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 6249c4e4df..2c2e6e9f91 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -185,7 +185,7 @@ namespace MWSound const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, - 20.0f, 12750.0f, false); + 20.0f, 12750.0f, Play_Normal); sound->mPos = objpos; sound->mBaseVolume = basevol; @@ -212,11 +212,12 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound(file, volume*basevol, pitch, mode&Play_Loop); + sound = mOutput->playSound(file, volume*basevol, pitch, mode); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; + sound->mFlags = mode; mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } @@ -240,12 +241,13 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; + sound->mFlags = mode; if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); From 2a7885b514ab183edc3bfd8f1654cbfcaa501c3f Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 31 Mar 2012 19:36:07 +0400 Subject: [PATCH 58/85] Synchronized OS X icon with our current app icon --- files/mac/openmw.icns | Bin 153405 -> 134980 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/mac/openmw.icns b/files/mac/openmw.icns index dfea24660669401eb1a2ae9fb81c142891c7be03..3ff899a799e17fa69258323be59c470633cfec68 100644 GIT binary patch literal 134980 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 literal 153405 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�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 DFXfI0 From 08f8701b33d1a7cc5dc9d92e1c4b9a77cb35f7ab Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 17:52:58 +0200 Subject: [PATCH 59/85] workaround for tga problem --- components/bsa/bsa_archive.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 41bff7e40a..f4f4b898cf 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,6 +71,9 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { + if (filename.find(".tga") != std::string::npos) + return false; + { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -179,7 +182,7 @@ class DirArchive: public Ogre::FileSystemArchive bool exists(const String& filename) { std::string copy; - + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); @@ -192,7 +195,7 @@ class DirArchive: public Ogre::FileSystemArchive if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - + DataStreamPtr p; return p; } @@ -243,7 +246,7 @@ bool exists(const String& filename) { } // Check if the file exists. - bool cexists(const String& filename) const { + bool cexists(const String& filename) const { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' @@ -254,7 +257,7 @@ bool exists(const String& filename) { if(filename.at(filename.length() - 2) == '>') passed = filename.substr(0, filename.length() - 6); -return arc.exists(passed.c_str()); +return arc.exists(passed.c_str()); } time_t getModifiedTime(const String&) { return 0; } From 653fbdd10cf83a3d361b7017b6e491d6df7aa67a Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 18:24:43 +0200 Subject: [PATCH 60/85] master/plugin support; needs multimap instead of map --- apps/mwiniimporter/importer.cpp | 58 +++++++++++++++++++++++++++++++++ apps/mwiniimporter/importer.hpp | 1 + apps/mwiniimporter/main.cpp | 5 +++ 3 files changed, 64 insertions(+) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 09088774b4..7532bf1dae 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -99,6 +102,61 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } +void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini) { + std::vector esmFiles; + std::string baseEsm("Game Files:GameFile"); + std::string esmFile(""); + + strmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + esmFile = baseEsm; + esmFile.append(1,i+'0'); + + it = ini.find(esmFile); + if(it == ini.end()) { + break; + } + + std::cout << "found EMS file: " << it->second << std::endl; + esmFiles.push_back(it->second); + esmFile = ""; + } + + + std::vector bsaFiles; + std::string baseBsa("Archives:Archive "); + std::string bsaFile(""); + + it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + bsaFile = baseBsa; + bsaFile.append(1,i+'0'); + + it = ini.find(bsaFile); + if(it == ini.end()) { + break; + } + + std::cout << "found BSA file: " << it->second << std::endl; + bsaFiles.push_back(it->second); + bsaFile = ""; + } + + if(!esmFiles.empty()) { + cfg.erase("master"); + for(std::vector::iterator it = esmFiles.begin(); it != esmFiles.end(); it++) { + cfg.insert(std::make_pair("master", *it)); + } + } + + if(!bsaFile.empty()) { + cfg.erase("plugin"); + for(std::vector::iterator it = bsaFiles.begin(); it != bsaFiles.end(); it++) { + cfg.insert(std::make_pair("plugin", *it)); + } + } +} + bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index ad5aaacde3..13cb02ee69 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -16,6 +16,7 @@ class MwIniImporter { strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); void merge(strmap &cfg, strmap &ini); + void importGameFiles(strmap &cfg, strmap &ini); void writeToFile(std::string file, strmap &cfg); private: diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 059703ea89..87225432d1 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -16,6 +16,7 @@ int main(int argc, char *argv[]) { ("ini,i", bpo::value(), "morrowind.ini file") ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") + ("game-files,g", "import esm and esp files") ; bpo::variables_map vm; @@ -65,6 +66,10 @@ int main(int argc, char *argv[]) { std::mapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); + + if(vm.count("game-files")) { + importer.importGameFiles(cfg, ini); + } std::cout << "write to: " << outputFile << std::endl; importer.writeToFile(outputFile, cfg); From 092de45924ec2c263049e9fe46757892fb93c039 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 18:28:48 +0200 Subject: [PATCH 61/85] std::map to std::multimap --- apps/mwiniimporter/importer.cpp | 4 ++-- apps/mwiniimporter/importer.hpp | 2 +- apps/mwiniimporter/main.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 7532bf1dae..a92eee725c 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -26,7 +26,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::map map; + std::multimap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -58,7 +58,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { strmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::map map; + std::multimap map; boost::iostreams::streamfile(filename.c_str()); std::string line; diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 13cb02ee69..3c85fd25a2 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -6,7 +6,7 @@ #include -typedef std::map strmap; +typedef std::multimap strmap; class MwIniImporter { diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 87225432d1..5eba95961a 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -62,8 +62,8 @@ int main(int argc, char *argv[]) { MwIniImporter importer; importer.setVerbose(vm.count("verbose")); - std::mapini = importer.loadIniFile(iniFile); - std::mapcfg = importer.loadCfgFile(cfgFile); + std::multimapini = importer.loadIniFile(iniFile); + std::multimapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); From cbf6c0404a7f0177e5eea47da04c77beb0c8a7f3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:06:12 -0700 Subject: [PATCH 62/85] Implement a basic underwater sound environment --- apps/openmw/mwsound/openal_output.cpp | 60 +++++++++++++++++++++++---- apps/openmw/mwsound/openal_output.hpp | 6 ++- apps/openmw/mwsound/sound_output.hpp | 4 +- apps/openmw/mwsound/soundmanager.cpp | 11 ++++- apps/openmw/mwsound/soundmanager.hpp | 5 +++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ddf4df7057..6c7fff973a 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -260,7 +260,16 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -388,7 +397,16 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -397,10 +415,18 @@ void OpenAL_Sound::update() void OpenAL_Sound3D::update() { + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - alSourcef(mSource, AL_GAIN, 0.0f); - else - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + gain = 0.0f; + else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -638,6 +664,11 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); @@ -685,6 +716,11 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_MAX_DISTANCE, max); alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? 0.0f : volume); alSourcef(src, AL_PITCH, pitch); @@ -701,7 +737,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector } -SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) +SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src; @@ -713,6 +749,8 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa try { + if((flags&Play_Loop)) + std::cout <<"Warning: cannot loop stream "<open(fname); sound.reset(new OpenAL_SoundStream(*this, src, decoder)); @@ -731,6 +769,11 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); @@ -743,9 +786,10 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa } -void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) { mPos = pos; + mLastEnvironment = env; if(mContext) { @@ -762,7 +806,7 @@ void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), - mStreamThread(new StreamThread) + mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index a709576bae..d62d20286a 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -36,6 +36,8 @@ namespace MWSound ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); + Environment mLastEnvironment; + virtual std::vector enumerate(); virtual void init(const std::string &devname=""); virtual void deinit(); @@ -43,9 +45,9 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, int flags); - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 1507e18472..774e42efa3 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -27,9 +27,9 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, int flags) = 0; - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 2c2e6e9f91..aaeef80ff5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -137,8 +137,9 @@ namespace MWSound { if(mMusic) mMusic->stop(); - mMusic = mOutput->streamSound(filename, 0.4f, 1.0f); + mMusic = mOutput->streamSound(filename, 0.4f, 1.0f, Play_NoEnv); mMusic->mBaseVolume = 0.4f; + mMusic->mFlags = Play_NoEnv; } catch(std::exception &e) { @@ -408,19 +409,25 @@ namespace MWSound if(!isMusicPlaying()) startRandomTitle(); + MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); Ogre::Vector3 nPos, nDir, nUp; nPos = cam->getRealPosition(); nDir = cam->getRealDirection(); nUp = cam->getRealUp(); + Environment env = Env_Normal; + if(nPos.y < current->cell->water) + env = Env_Underwater; + // The output handler is expecting vectors oriented like the game // (that is, -Z goes down, +Y goes forward), but that's not what we // get from Ogre's camera, so we have to convert. const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); - mOutput->updateListener(pos, at, up); + + mOutput->updateListener(pos, at, up, env); // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index de5cca839c..cad5f61871 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -43,6 +43,11 @@ namespace MWSound static inline int operator&(const PlayMode &a, const PlayMode &b) { return (int)a & (int)b; } + enum Environment { + Env_Normal, + Env_Underwater, + }; + class SoundManager { Ogre::ResourceGroupManager& mResourceMgr; From f8d45eae52413bba78fea6715046ac7ac7eb2182 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:08:05 +0200 Subject: [PATCH 63/85] fix #1 --- apps/openmw/mwrender/occlusionquery.cpp | 7 +++++-- apps/openmw/mwrender/occlusionquery.hpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 228d8a4990..b571e3c3ad 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -55,20 +55,21 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); mBBQueryTotal->setRenderQueueGroup(queue); - mBBNode->attachObject(mBBQueryTotal); + mBBNodeReal->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); mBBQueryVisible->setRenderQueueGroup(queue); - mBBNode->attachObject(mBBQueryVisible); + mBBNodeReal->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); /// \todo ideally this should occupy exactly 1 pixel on the screen @@ -178,6 +179,8 @@ void OcclusionQuery::update(float duration) dist /= 1000.f; mBBNode->setPosition(mSunNode->getPosition() * dist); mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index e81358eb6c..102b18bee3 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -65,6 +65,7 @@ namespace MWRender Ogre::SceneNode* mSunNode; Ogre::SceneNode* mBBNode; + Ogre::SceneNode* mBBNodeReal; float mSunVisibility; Ogre::SceneNode* mObjectNode; From 909abb480d8479f7f70268f717556cb2f82b9102 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:09:36 +0200 Subject: [PATCH 64/85] fix 2 --- apps/openmw/mwrender/occlusionquery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index b571e3c3ad..9baea23b51 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -217,8 +217,6 @@ void OcclusionQuery::update(float duration) //std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); - mBBQuerySingleObject->setVisible(false); - mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; } From c08a2b294224da72d32d409c7229aa999c1c4f82 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:12:02 +0200 Subject: [PATCH 65/85] fix 3 --- apps/openmw/mwrender/occlusionquery.cpp | 10 +++++++--- apps/openmw/mwrender/occlusionquery.hpp | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 9baea23b51..de66df6d97 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false) { mRendering = renderer; mSunNode = sunNode; @@ -82,6 +82,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mRendering->getScene()->addRenderObjectListener(this); mRendering->getScene()->addRenderQueueListener(this); mDoQuery = true; + mDoQuery2 = true; } OcclusionQuery::~OcclusionQuery() @@ -125,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (mDoQuery2 == true && rend == mBBQuerySingleObject && mQuerySingleObjectRequested) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -154,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mQuerySingleObjectRequested) + if (mObjectWasVisible == false && mDoQuery2 && mQuerySingleObjectRequested) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -185,6 +186,7 @@ void OcclusionQuery::update(float duration) // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) mDoQuery = false; + mDoQuery2 = false; if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) @@ -219,6 +221,8 @@ void OcclusionQuery::update(float duration) mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; + + mDoQuery2 = true; } } diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 102b18bee3..ebdc51311d 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -77,6 +77,7 @@ namespace MWRender bool mSupported; bool mDoQuery; + bool mDoQuery2; bool mQuerySingleObjectRequested; bool mQuerySingleObjectStarted; From c9067249dd0a3e4eef6d5db37480bc74426721c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:16:14 +0200 Subject: [PATCH 66/85] fix 4 --- apps/openmw/mwrender/occlusionquery.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index de66df6d97..2bcf6bd4b4 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -126,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (mDoQuery2 == true && rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (mDoQuery2 == true && rend == mBBQuerySingleObject) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -155,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mDoQuery2 && mQuerySingleObjectRequested) + if (mObjectWasVisible == false && mDoQuery2) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -210,13 +210,13 @@ void OcclusionQuery::update(float duration) mDoQuery = true; } - if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) + if (!mSingleObjectQuery->isStillOutstanding()) { unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); - //std::cout << "Single object query result: " << result << " pixels " << std::endl; + std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); mQuerySingleObjectStarted = false; From 4944a29b21b515f33686ab54dc0ecfd71e8b6cbd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:41:12 -0700 Subject: [PATCH 67/85] Keep track of the sound pitch --- apps/openmw/mwsound/openal_output.cpp | 6 +++--- apps/openmw/mwsound/sound.hpp | 2 ++ apps/openmw/mwsound/soundmanager.cpp | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6c7fff973a..41411a6216 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -261,7 +261,7 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; @@ -398,7 +398,7 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; @@ -416,7 +416,7 @@ void OpenAL_Sound::update() void OpenAL_Sound3D::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) gain = 0.0f; else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index ca12ec5571..a33892548c 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -16,6 +16,7 @@ namespace MWSound Ogre::Vector3 mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; + float mPitch; float mMinDistance; float mMaxDistance; int mFlags; @@ -29,6 +30,7 @@ namespace MWSound Sound() : mPos(0.0f, 0.0f, 0.0f) , mVolume(1.0f) , mBaseVolume(1.0f) + , mPitch(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ , mFlags(Play_Normal) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index aaeef80ff5..a00b7dc661 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -216,6 +216,7 @@ namespace MWSound sound = mOutput->playSound(file, volume*basevol, pitch, mode); sound->mVolume = volume; sound->mBaseVolume = basevol; + sound->mPitch = pitch; sound->mMinDistance = min; sound->mMaxDistance = max; sound->mFlags = mode; @@ -246,6 +247,7 @@ namespace MWSound sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; + sound->mPitch = pitch; sound->mMinDistance = min; sound->mMaxDistance = max; sound->mFlags = mode; From 3a57746ee47f8504f6c531fd43166316ba973fd9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:43:55 -0700 Subject: [PATCH 68/85] Remove an unneeded volume special-case --- apps/openmw/mwsound/soundmanager.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a00b7dc661..1aa2bf2bf7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -88,10 +88,7 @@ namespace MWSound if(snd == NULL) throw std::runtime_error(std::string("Failed to lookup sound ")+soundId); - if(snd->data.volume == 0) - volume = 0.0f; - else - volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); + volume *= pow(10.0, (snd->data.volume/255.0*3348.0 - 3348.0) / 2000.0); if(snd->data.minRange == 0 && snd->data.maxRange == 0) { From 3cc81d74bc3395198f9fa995191b410842de31e0 Mon Sep 17 00:00:00 2001 From: Eli2 Date: Sat, 31 Mar 2012 19:50:21 +0200 Subject: [PATCH 69/85] Cleanup, replaced if with switch --- apps/openmw/mwgui/window_manager.cpp | 110 ++++++++++++--------------- 1 file changed, 48 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 4654674695..1afca1d41a 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -180,71 +180,57 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - // If in game mode, don't show anything. - if(mode == GM_Game) //Use a switch/case structure - { - return; - } + int eff; + switch(mode) { + case GM_Game: + // If in game mode, don't show anything. + break; + case GM_MainMenu: + menu->setVisible(true); + break; + case GM_Console: + console->enable(); + break; + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + case GM_Inventory: + // First, compute the effective set of windows to show. + // This is controlled both by what windows the + // user has opened/closed (the 'shown' variable) and by what + // windows we are allowed to show (the 'allowed' var.) + eff = shown & allowed; - if(mode == GM_MainMenu) - { - // Enable the main menu - menu->setVisible(true); - return; - } - - if(mode == GM_Console) - { - console->enable(); - return; - } - - //There must be a more elegant solution - if (mode == GM_Name || mode == GM_Race || mode == GM_Class || mode == GM_ClassPick || mode == GM_ClassCreate || mode == GM_Birth || mode == GM_ClassGenerate || mode == GM_Review) - { - mCharGen->spawnDialog(mode); - return; - } - - if(mode == GM_Inventory) - { - // Ah, inventory mode. First, compute the effective set of - // windows to show. This is controlled both by what windows the - // user has opened/closed (the 'shown' variable) and by what - // windows we are allowed to show (the 'allowed' var.) - int eff = shown & allowed; - - // Show the windows we want - map -> setVisible( (eff & GW_Map) != 0 ); - stats -> setVisible( (eff & GW_Stats) != 0 ); - return; - } - - if (mode == GM_Dialogue) - { - dialogueWindow->open(); - return; - } - - if(mode == GM_InterMessageBox) - { - if(!mMessageBoxManager->isInteractiveMessageBox()) { + // Show the windows we want + map -> setVisible( (eff & GW_Map) != 0 ); + stats -> setVisible( (eff & GW_Stats) != 0 ); + break; + case GM_Dialogue: + dialogueWindow->open(); + break; + case GM_InterMessageBox: + if(!mMessageBoxManager->isInteractiveMessageBox()) { + setGuiMode(GM_Game); + } + break; + case GM_Journal: + mJournal->setVisible(true); + mJournal->open(); + break; + default: + // Unsupported mode, switch back to game + // Note: The call will eventually end up this method again but + // will stop at the check if mode is GM_Game. setGuiMode(GM_Game); - } - return; + break; } - - if(mode == GM_Journal) - { - mJournal->setVisible(true); - mJournal->open(); - return; - } - - // Unsupported mode, switch back to game - // Note: The call will eventually end up this method again but - // will stop at the check if(mode == GM_Game) above. - setGuiMode(GM_Game); } void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) From e8e8d3fb1b4ef1fb917e04278fd6493c310d34ca Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:59:29 -0700 Subject: [PATCH 70/85] Fully reset the music before starting the next track --- apps/openmw/mwsound/soundmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 1aa2bf2bf7..a96aac6c5c 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -132,8 +132,7 @@ namespace MWSound std::cout <<"Playing "<stop(); + stopMusic(); mMusic = mOutput->streamSound(filename, 0.4f, 1.0f, Play_NoEnv); mMusic->mBaseVolume = 0.4f; mMusic->mFlags = Play_NoEnv; From b018d00fd3feff313ec9c099ea5c73be89f24e5d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:05:23 +0200 Subject: [PATCH 71/85] fix 5 --- apps/openmw/mwrender/occlusionquery.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 2bcf6bd4b4..f4eb08acdc 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -126,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (mDoQuery2 == true && rend == mBBQuerySingleObject) + if (mDoQuery == true && rend == mBBQuerySingleObject) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -155,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mDoQuery2) + if (mObjectWasVisible == false && mDoQuery) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -189,7 +189,8 @@ void OcclusionQuery::update(float duration) mDoQuery2 = false; if (!mSunTotalAreaQuery->isStillOutstanding() - && !mSunVisibleAreaQuery->isStillOutstanding()) + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -208,10 +209,6 @@ void OcclusionQuery::update(float duration) if (mSunVisibility > 1) mSunVisibility = 1; } - mDoQuery = true; - } - if (!mSingleObjectQuery->isStillOutstanding()) - { unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); @@ -222,7 +219,7 @@ void OcclusionQuery::update(float duration) mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; - mDoQuery2 = true; + mDoQuery = true; } } From 5f78f6c72325de90c7efbc55fc191dc3c27dee44 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:10:34 +0200 Subject: [PATCH 72/85] change queue --- apps/openmw/mwrender/occlusionquery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index f4eb08acdc..ade0f976e0 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -61,14 +61,14 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); - mBBQueryTotal->setRenderQueueGroup(queue); + mBBQueryTotal->setRenderQueueGroup(queue+1); mBBNodeReal->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); - mBBQueryVisible->setRenderQueueGroup(queue); + mBBQueryVisible->setRenderQueueGroup(queue+1); mBBNodeReal->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); From cfb194f1d13c1836022b0845d7ffdc5d896a768b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:18:20 +0200 Subject: [PATCH 73/85] fix 6 --- apps/openmw/mwrender/occlusionquery.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index ade0f976e0..bfa3b73aef 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -140,6 +140,11 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) { + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } /** * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called From 13efe68fc3a628abaa8ca65181390030983c4e22 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:28:07 +0200 Subject: [PATCH 74/85] disabled some debug output --- apps/openmw/mwrender/occlusionquery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index bfa3b73aef..cc3464c646 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -95,7 +95,6 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; return mSupported; } @@ -218,7 +217,6 @@ void OcclusionQuery::update(float duration) mSingleObjectQuery->pullOcclusionQuery(&result); - std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); mQuerySingleObjectStarted = false; From 4ef921c43fd82f09e61677765c4394de2c32f251 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 20:50:22 +0200 Subject: [PATCH 75/85] Prevent internal classes from being marked dllimport --- apps/openmw/mwrender/terrainmaterial.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 798821d616..3cb3163475 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -50,7 +50,7 @@ namespace Ogre terrain. @note Requires the Cg plugin to render correctly */ - class _OgreTerrainExport TerrainMaterialGeneratorB : public TerrainMaterialGenerator + class TerrainMaterialGeneratorB : public TerrainMaterialGenerator { public: TerrainMaterialGeneratorB(); @@ -58,7 +58,7 @@ namespace Ogre /** Shader model 2 profile target. */ - class _OgreTerrainExport SM2Profile : public TerrainMaterialGenerator::Profile + class SM2Profile : public TerrainMaterialGenerator::Profile { public: SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); @@ -161,7 +161,7 @@ namespace Ogre void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); /// Interface definition for helper class to generate shaders - class _OgreTerrainExport ShaderHelper : public TerrainAlloc + class ShaderHelper : public TerrainAlloc { public: ShaderHelper() {} @@ -194,7 +194,7 @@ namespace Ogre }; /// Utility class to help with generating shaders for Cg / HLSL. - class _OgreTerrainExport ShaderHelperCg : public ShaderHelper + class ShaderHelperCg : public ShaderHelper { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); @@ -212,7 +212,7 @@ namespace Ogre void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); }; - class _OgreTerrainExport ShaderHelperHLSL : public ShaderHelperCg + class ShaderHelperHLSL : public ShaderHelperCg { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); @@ -220,7 +220,7 @@ namespace Ogre }; /// Utility class to help with generating shaders for GLSL. - class _OgreTerrainExport ShaderHelperGLSL : public ShaderHelper + class ShaderHelperGLSL : public ShaderHelper { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); From 64f792c01d4e3595f5ba0116773af23b23c7f9ab Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 21:05:33 +0200 Subject: [PATCH 76/85] Fix a value defined at the wrong place --- apps/openmw/mwsound/openal_output.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ddf4df7057..e57c1a7094 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -71,7 +71,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 6; - static const ALfloat sBufferLength = 0.125f; + static const ALfloat sBufferLength; OpenAL_Output &mOutput; @@ -101,6 +101,7 @@ public: bool process(); }; +const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; // // A background streaming thread (keeps active streams processed) From 6d875dfd54105505c62f831717632e6c9eeaad12 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 21:06:48 +0200 Subject: [PATCH 77/85] handle master/plugin properly --- apps/mwiniimporter/importer.cpp | 120 ++++++++++++++------------------ apps/mwiniimporter/importer.hpp | 10 ++- apps/mwiniimporter/main.cpp | 14 ++-- 3 files changed, 69 insertions(+), 75 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index a92eee725c..937073632d 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -26,17 +27,12 @@ strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::multimap map; + std::map map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - // ignore sections for now - if(line.empty() || line[0] == ';') { - continue; - } - if(line[0] == '[') { if(line.length() > 2) { section = line.substr(1, line.length()-3); @@ -44,6 +40,15 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } + int comment_pos = line.find(";"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + int pos = line.find("="); if(pos < 1) { continue; @@ -58,16 +63,12 @@ strmap MwIniImporter::loadIniFile(std::string filename) { strmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::multimap map; + std::map map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - if(line[0] == '[') { // section - continue; // ignore for now - } - // we cant say comment by only looking at first char anymore int comment_pos = line.find("#"); if(comment_pos > 0) { @@ -102,67 +103,50 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } -void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini) { - std::vector esmFiles; - std::string baseEsm("Game Files:GameFile"); - std::string esmFile(""); - - strmap::iterator it = ini.begin(); - for(int i=0; it != ini.end(); i++) { - esmFile = baseEsm; - esmFile.append(1,i+'0'); - - it = ini.find(esmFile); - if(it == ini.end()) { - break; - } - - std::cout << "found EMS file: " << it->second << std::endl; - esmFiles.push_back(it->second); - esmFile = ""; - } - - - std::vector bsaFiles; - std::string baseBsa("Archives:Archive "); - std::string bsaFile(""); - - it = ini.begin(); - for(int i=0; it != ini.end(); i++) { - bsaFile = baseBsa; - bsaFile.append(1,i+'0'); - - it = ini.find(bsaFile); - if(it == ini.end()) { - break; - } - - std::cout << "found BSA file: " << it->second << std::endl; - bsaFiles.push_back(it->second); - bsaFile = ""; - } - - if(!esmFiles.empty()) { - cfg.erase("master"); - for(std::vector::iterator it = esmFiles.begin(); it != esmFiles.end(); it++) { - cfg.insert(std::make_pair("master", *it)); - } - } - - if(!bsaFile.empty()) { - cfg.erase("plugin"); - for(std::vector::iterator it = bsaFiles.begin(); it != bsaFiles.end(); it++) { - cfg.insert(std::make_pair("plugin", *it)); - } - } -} - bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } -void MwIniImporter::writeToFile(std::string file, strmap &cfg) { - boost::iostreams::stream out(file); +void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles) { + std::string baseGameFile("Game Files:GameFile"); + std::string gameFile(""); + + strmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + gameFile = baseGameFile; + gameFile.append(1,i+'0'); + + it = ini.find(gameFile); + if(it == ini.end()) { + break; + } + + std::string filetype(it->second.substr(it->second.length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(it->second); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(it->second); + } + + gameFile = ""; + } +} + +void MwIniImporter::writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles) { + for(std::vector::iterator it=esmFiles.begin(); it != esmFiles.end(); it++) { + out << "master=" << *it << std::endl; + } + for(std::vector::iterator it=espFiles.begin(); it != espFiles.end(); it++) { + out << "plugin=" << *it << std::endl; + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, strmap &cfg) { + cfg.erase("master"); + cfg.erase("plugin"); for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { out << (it->first) << "=" << (it->second) << std::endl; diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 3c85fd25a2..d7250f5e25 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -1,12 +1,15 @@ #ifndef MWINIIMPORTER_IMPORTER #define MWINIIMPORTER_IMPORTER 1 +#include +#include #include #include +#include #include -typedef std::multimap strmap; +typedef std::map strmap; class MwIniImporter { @@ -16,8 +19,9 @@ class MwIniImporter { strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); void merge(strmap &cfg, strmap &ini); - void importGameFiles(strmap &cfg, strmap &ini); - void writeToFile(std::string file, strmap &cfg); + void importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles); + void writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles); + void writeToFile(boost::iostreams::stream &out, strmap &cfg); private: bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 5eba95961a..7426e71ea0 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -58,21 +58,27 @@ int main(int argc, char *argv[]) { std::cerr << "cfg file does not exist" << std::endl; return -4; } + MwIniImporter importer; importer.setVerbose(vm.count("verbose")); + boost::iostreams::stream file(outputFile); - std::multimapini = importer.loadIniFile(iniFile); - std::multimapcfg = importer.loadCfgFile(cfgFile); + std::mapini = importer.loadIniFile(iniFile); + std::mapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); if(vm.count("game-files")) { - importer.importGameFiles(cfg, ini); + std::vector esmFiles; + std::vector espFiles; + + importer.importGameFiles(cfg, ini, esmFiles, espFiles); + importer.writeGameFiles(file, esmFiles, espFiles); } std::cout << "write to: " << outputFile << std::endl; - importer.writeToFile(outputFile, cfg); + importer.writeToFile(file, cfg); return 0; } From ca4ad741517b6d975ed1d6e0df151560582d30d5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 21:29:46 +0200 Subject: [PATCH 78/85] more cleanup --- apps/openmw/mwgui/window_manager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 1afca1d41a..49b6e644d3 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -180,7 +180,6 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - int eff; switch(mode) { case GM_Game: // If in game mode, don't show anything. @@ -202,16 +201,18 @@ void WindowManager::updateVisible() mCharGen->spawnDialog(mode); break; case GM_Inventory: + { // First, compute the effective set of windows to show. // This is controlled both by what windows the // user has opened/closed (the 'shown' variable) and by what // windows we are allowed to show (the 'allowed' var.) - eff = shown & allowed; + int eff = shown & allowed; // Show the windows we want map -> setVisible( (eff & GW_Map) != 0 ); stats -> setVisible( (eff & GW_Stats) != 0 ); break; + } case GM_Dialogue: dialogueWindow->open(); break; From bdc4c79b4e06450bb2735dbfeaa3b8a347248308 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 31 Mar 2012 21:34:40 +0200 Subject: [PATCH 79/85] Fix for segfault when doing 'coc "seyda neen"'. This is a fix for segfault: ==8683== Process terminating with default action of signal 11 (SIGSEGV) ==8683== Access not within mapped region at address 0x0 ==8683== at 0x59DFE4: MWRender::Animation::handleShapes(std::vector >*, Ogre::Entity*, Ogre::SkeletonInstance*) (animation.cpp:503) ==8683== by 0x5A4ECE: MWRender::Actors::update(float) (actors.cpp:134) ==8683== by 0x5937A9: MWRender::RenderingManager::update(float) (renderingmanager.cpp:168) ==8683== by 0x629AD6: MWWorld::World::update(float) (world.cpp:705) ==8683== by 0x68B022: OMW::Engine::frameRenderingQueued(Ogre::FrameEvent const&) (engine.cpp:157) ==8683== by 0x51F9574: Ogre::Root::_fireFrameRenderingQueued(Ogre::FrameEvent&) (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F964F: Ogre::Root::_fireFrameRenderingQueued() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F9681: Ogre::Root::_updateAllRenderTargets() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F98CF: Ogre::Root::renderOneFrame() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F990C: Ogre::Root::startRendering() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x68A669: OMW::Engine::go() (engine.cpp:408) ==8683== by 0x51CECB: main (main.cpp:254) ==8683== If you believe this happened as a result of a stack ==8683== overflow in your program's main thread (unlikely but ==8683== possible), you can try to increase the size of the ==8683== main thread stack using the --main-stacksize= flag. ==8683== The main thread stack size used in this run was 8388608. when doing 'coc "seyda neen"' when animations are enabled (Animation::animate member variable is set to 1). --- apps/openmw/mwrender/animation.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f3a8f64d55..fb710443b6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -126,6 +126,11 @@ namespace MWRender{ void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ shapeNumber = 0; + if (allshapes == NULL || creaturemodel == NULL || skel == NULL) + { + return; + } + std::vector::iterator allshapesiter; for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) From 1d596d6c722ac579e527b785ff6b393d8375f3b6 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 22:48:50 +0200 Subject: [PATCH 80/85] use std::map > instead of std::map --- apps/mwiniimporter/importer.cpp | 103 ++++++++++++++++++++------------ apps/mwiniimporter/importer.hpp | 20 +++---- apps/mwiniimporter/main.cpp | 11 +--- 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 937073632d..f7ddb2bf07 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -23,11 +23,11 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } -strmap MwIniImporter::loadIniFile(std::string filename) { +MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::map map; + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -54,16 +54,23 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } - map.insert(std::make_pair(section + ":" + line.substr(0,pos), line.substr(pos+1))); + std::string key(section + ":" + line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); } return map; } -strmap MwIniImporter::loadCfgFile(std::string filename) { +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::map map; + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -84,34 +91,43 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { continue; } - map.insert(std::make_pair(line.substr(0,pos), line.substr(pos+1))); + std::string key(line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); } return map; } -void MwIniImporter::merge(strmap &cfg, strmap &ini) { - strmap::iterator cfgIt; - strmap::iterator iniIt; +void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) { + multistrmap::iterator cfgIt; + multistrmap::iterator iniIt; for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { if((iniIt = ini.find(it->second)) != ini.end()) { cfg.erase(it->first); if(!this->specialMerge(it->first, it->second, cfg, ini)) { - cfg.insert(std::make_pair(it->first, iniIt->second)); + cfg.insert(std::make_pair >(it->first, iniIt->second)); } } } } -bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini) { return false; } -void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles) { +void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { + std::vector esmFiles; + std::vector espFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); - strmap::iterator it = ini.begin(); + multistrmap::iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; gameFile.append(1,i+'0'); @@ -121,35 +137,48 @@ void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vectorsecond.substr(it->second.length()-4, 3)); - std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); - - if(filetype.compare("esm") == 0) { - esmFiles.push_back(it->second); - } - else if(filetype.compare("esp") == 0) { - espFiles.push_back(it->second); + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { + std::string filetype(entry->substr(entry->length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(*entry); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(*entry); + } } gameFile = ""; } -} - -void MwIniImporter::writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles) { - for(std::vector::iterator it=esmFiles.begin(); it != esmFiles.end(); it++) { - out << "master=" << *it << std::endl; - } - for(std::vector::iterator it=espFiles.begin(); it != espFiles.end(); it++) { - out << "plugin=" << *it << std::endl; - } -} - -void MwIniImporter::writeToFile(boost::iostreams::stream &out, strmap &cfg) { - cfg.erase("master"); - cfg.erase("plugin"); - for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { - out << (it->first) << "=" << (it->second) << std::endl; + if(!esmFiles.empty()) { + multistrmap::iterator it; + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); + } + } + + if(!espFiles.empty()) { + multistrmap::iterator it; + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); + } + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { + + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { + out << (it->first) << "=" << (*entry) << std::endl; + } } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index d7250f5e25..454dc209af 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -8,23 +8,21 @@ #include #include - -typedef std::map strmap; - class MwIniImporter { - public: + typedef std::map strmap; + typedef std::map > multistrmap; + MwIniImporter(); void setVerbose(bool verbose); - strmap loadIniFile(std::string filename); - strmap loadCfgFile(std::string filename); - void merge(strmap &cfg, strmap &ini); - void importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles); - void writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles); - void writeToFile(boost::iostreams::stream &out, strmap &cfg); + multistrmap loadIniFile(std::string filename); + multistrmap loadCfgFile(std::string filename); + void merge(multistrmap &cfg, multistrmap &ini); + void importGameFiles(multistrmap &cfg, multistrmap &ini); + void writeToFile(boost::iostreams::stream &out, multistrmap &cfg); private: - bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); + bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini); bool mVerbose; strmap mMergeMap; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 7426e71ea0..9a6e61645d 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -59,22 +59,17 @@ int main(int argc, char *argv[]) { return -4; } - MwIniImporter importer; importer.setVerbose(vm.count("verbose")); boost::iostreams::stream file(outputFile); - std::mapini = importer.loadIniFile(iniFile); - std::mapcfg = importer.loadCfgFile(cfgFile); + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); if(vm.count("game-files")) { - std::vector esmFiles; - std::vector espFiles; - - importer.importGameFiles(cfg, ini, esmFiles, espFiles); - importer.writeGameFiles(file, esmFiles, espFiles); + importer.importGameFiles(cfg, ini); } std::cout << "write to: " << outputFile << std::endl; From a2a7539fd55b2a4480c36575f0814d4180215146 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 23:15:33 +0200 Subject: [PATCH 81/85] fix for more than 10 game files; delete both master and plugin settings if called with --game-files --- apps/mwiniimporter/importer.cpp | 36 +++++++++++++++++---------------- apps/mwiniimporter/importer.hpp | 1 + 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f7ddb2bf07..a82240a8aa 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -23,6 +24,12 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } +std::string MwIniImporter::numberToString(int n) { + std::stringstream str; + str << n; + return str.str(); +} + MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; @@ -130,7 +137,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { multistrmap::iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; - gameFile.append(1,i+'0'); + gameFile.append(this->numberToString(i)); it = ini.find(gameFile); if(it == ini.end()) { @@ -152,24 +159,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { gameFile = ""; } - if(!esmFiles.empty()) { - multistrmap::iterator it; - cfg.erase("master"); - cfg.insert( std::make_pair > ("master", std::vector() ) ); - - for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { - cfg["master"].push_back(*it); - } + multistrmap::iterator it; + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); } - if(!espFiles.empty()) { - multistrmap::iterator it; - cfg.erase("plugin"); - cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - - for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { - cfg["plugin"].push_back(*it); - } + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 454dc209af..988f10255e 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -23,6 +23,7 @@ class MwIniImporter { private: bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini); + std::string numberToString(int n); bool mVerbose; strmap mMergeMap; }; From 8aa4001937834851b83146177cc591ebbc6f0c47 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 1 Apr 2012 10:34:51 +0200 Subject: [PATCH 82/85] compile fix --- apps/mwiniimporter/importer.cpp | 51 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index a82240a8aa..08b05f417e 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -14,7 +14,7 @@ MwIniImporter::MwIniImporter() { { "fps", "General:Show FPS" }, { 0, 0 } }; - + for(int i=0; map[i][0]; i++) { mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); } @@ -32,7 +32,7 @@ std::string MwIniImporter::numberToString(int n) { MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; - + std::string section(""); MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); @@ -46,68 +46,68 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { } continue; } - + int comment_pos = line.find(";"); if(comment_pos > 0) { line = line.substr(0,comment_pos); } - + if(line.empty()) { continue; } - + int pos = line.find("="); if(pos < 1) { continue; } - + std::string key(section + ":" + line.substr(0,pos)); std::string value(line.substr(pos+1)); - + multistrmap::iterator it; if((it = map.find(key)) == map.end()) { map.insert( std::make_pair > (key, std::vector() ) ); } map[key].push_back(value); } - + return map; } MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - + // we cant say comment by only looking at first char anymore int comment_pos = line.find("#"); if(comment_pos > 0) { line = line.substr(0,comment_pos); } - + if(line.empty()) { continue; } - + int pos = line.find("="); if(pos < 1) { continue; } - + std::string key(line.substr(0,pos)); std::string value(line.substr(pos+1)); - + multistrmap::iterator it; if((it = map.find(key)) == map.end()) { map.insert( std::make_pair > (key, std::vector() ) ); } map[key].push_back(value); } - + return map; } @@ -138,16 +138,16 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; gameFile.append(this->numberToString(i)); - + it = ini.find(gameFile); if(it == ini.end()) { break; } - + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { std::string filetype(entry->substr(entry->length()-4, 3)); std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); - + if(filetype.compare("esm") == 0) { esmFiles.push_back(*entry); } @@ -155,33 +155,30 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { espFiles.push_back(*entry); } } - + gameFile = ""; } - - multistrmap::iterator it; + cfg.erase("master"); cfg.insert( std::make_pair > ("master", std::vector() ) ); - + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { cfg["master"].push_back(*it); } - + cfg.erase("plugin"); cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { cfg["plugin"].push_back(*it); } } void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { - + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { out << (it->first) << "=" << (*entry) << std::endl; } } } - - From ee754eda6c5e2377956092ad6a04f02c3367fd1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 1 Apr 2012 15:07:41 +0200 Subject: [PATCH 83/85] don't create sky until entering an exterior cell --- apps/openmw/mwrender/occlusionquery.cpp | 24 +++++++++++++++++------ apps/openmw/mwrender/occlusionquery.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 2 ++ apps/openmw/mwrender/sky.cpp | 18 ++++++++++++++++- apps/openmw/mwrender/sky.hpp | 7 ++++++- apps/openmw/mwworld/world.cpp | 4 ++-- 6 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index cc3464c646..29cfe33fe3 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,8 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false), + mBBNode(0) { mRendering = renderer; mSunNode = sunNode; @@ -52,7 +53,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setCullingMode(CULL_NONE); matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); - mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + if (sunNode) + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); @@ -182,10 +184,13 @@ void OcclusionQuery::update(float duration) if (dist==0) dist = 10000000; dist -= 1000; // bias dist /= 1000.f; - mBBNode->setPosition(mSunNode->getPosition() * dist); - mBBNode->setScale(dist, dist, dist); - mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); - mBBNodeReal->setScale(mBBNode->getScale()); + if (mBBNode) + { + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); + } // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) @@ -245,6 +250,13 @@ bool OcclusionQuery::occlusionTestPending() return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); } +void OcclusionQuery::setSunNode(Ogre::SceneNode* node) +{ + mSunNode = node; + if (!mBBNode) + mBBNode = node->getParentSceneNode()->createChildSceneNode(); +} + bool OcclusionQuery::getTestResult() { assert( !occlusionTestPending() diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index ebdc51311d..b655c8e46b 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -53,6 +53,8 @@ namespace MWRender float getSunVisibility() const {return mSunVisibility;}; + void setSunNode(Ogre::SceneNode* node); + private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3d715b3d32..bbddd325a1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -201,6 +201,8 @@ void RenderingManager::skyEnable () { if(mSkyManager) mSkyManager->enable(); + + mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); } void RenderingManager::skyDisable () diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 265008e346..23b44d1f3c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -325,14 +325,17 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen , mSunEnabled(true) , mMasserEnabled(true) , mSecundaEnabled(true) + , mCreated(false) { - mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); +} +void SkyManager::create() +{ /// \todo preload all the textures and meshes that are used for sky rendering // Create overlay used for thunderstorm @@ -562,6 +565,8 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + + mCreated = true; } SkyManager::~SkyManager() @@ -574,11 +579,13 @@ SkyManager::~SkyManager() int SkyManager::getMasserPhase() const { + if (!mCreated) return 0; return mMasser->getPhaseInt(); } int SkyManager::getSecundaPhase() const { + if (!mCreated) return 0; return mSecunda->getPhaseInt(); } @@ -631,6 +638,9 @@ void SkyManager::update(float duration) void SkyManager::enable() { + if (!mCreated) + create(); + mRootNode->setVisible(true); mEnabled = true; } @@ -654,6 +664,7 @@ void SkyManager::setCloudsOpacity(float opacity) void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { + if (!mCreated) return; if (mClouds != weather.mCloudTexture) { mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); @@ -750,17 +761,20 @@ void SkyManager::sunDisable() void SkyManager::setSunDirection(const Vector3& direction) { + if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); } void SkyManager::setMasserDirection(const Vector3& direction) { + if (!mCreated) return; mMasser->setPosition(direction); } void SkyManager::setSecundaDirection(const Vector3& direction) { + if (!mCreated) return; mSecunda->setPosition(direction); } @@ -786,6 +800,7 @@ void SkyManager::secundaDisable() void SkyManager::setThunder(const float factor) { + if (!mCreated) return; if (factor > 0.f) { mThunderOverlay->show(); @@ -818,5 +833,6 @@ void SkyManager::setDate(int day, int month) Ogre::SceneNode* SkyManager::getSunNode() { + if (!mCreated) return 0; return mSun->getNode(); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 508af76732..baf5933cbc 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -112,6 +112,9 @@ namespace MWRender void update(float duration); + void create(); + ///< no need to call this, automatically done on first enable() + void enable(); void disable(); @@ -164,8 +167,10 @@ namespace MWRender void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); - + private: + bool mCreated; + MWWorld::Environment* mEnvironment; float mHour; int mDay; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 6f03fa37f5..a48cc7e722 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -146,10 +146,10 @@ namespace MWWorld mRendering->skySetDate (mGlobalVariables->getInt ("day"), mGlobalVariables->getInt ("month")); - mRendering->getSkyManager()->enable(); + mRendering->skyEnable(); } else - mRendering->getSkyManager()->disable(); + mRendering->skyDisable(); } World::World (OEngine::Render::OgreRenderer& renderer, From 9a261a02aa78e1f9edfbb7aac8874d653b814d78 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 1 Apr 2012 15:14:43 +0200 Subject: [PATCH 84/85] changed the cloud movement direction like suggested on the forum --- apps/openmw/mwrender/sky.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 23b44d1f3c..2fdf9b2cd6 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -536,7 +536,7 @@ void SkyManager::create() " uniform float4 emissive \n" ") \n" "{ \n" - " uv += float2(1,0) * time * speed * 0.003; \n" // Scroll in x direction + " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n" "}"; From 54ce95cfafb2c525818787f1a52463185eb8278a Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sun, 1 Apr 2012 17:25:03 +0200 Subject: [PATCH 85/85] Make sure it doesn't find the wrong file --- components/bsa/bsa_archive.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index f4f4b898cf..0e3563b261 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -41,21 +41,21 @@ struct ciLessBoost : std::binary_function { bool operator() (const std::string & s1, const std::string & s2) const { //case insensitive version of is_less - return lexicographical_compare(s1, s2, boost::algorithm::is_iless()); + return boost::ilexicographical_compare(s1, s2); } }; struct pathComparer { private: - int m_start, m_size; + std::string find; public: - pathComparer(int start, int size) : m_start(start), m_size(size) { } + pathComparer(const std::string& toFind) : find(toFind) { } - bool operator() (const std::string& first, const std::string& other) + bool operator() (const std::string& other) { - return lexicographical_compare(first.substr(m_start,m_size), other.substr(m_start,m_size), boost::algorithm::is_iless()); + return boost::iequals(find, other); } }; @@ -71,9 +71,6 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - if (filename.find(".tga") != std::string::npos) - return false; - { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -116,10 +113,13 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - pathComparer comp(delimiter, copy.size() - delimiter-1); - std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); - if (find != current.end() && !comp(copy, current.front())) + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, ciLessBoost()); + if (find != current.end() && !ciLessBoost()(copy, current.front())) { + if (!boost::iequals(copy, *find)) + if ((find = std::find_if(current.begin(), current.end(), pathComparer(copy))) == current.end()) //\todo Check if this line is actually needed + return false; + copy = *find; return true; }