diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e0042a93..f55e124e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,9 +17,9 @@ Debian: # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - - curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - - curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb stage: build script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7e6db78..c6013ce55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ 0.46.0 ------ + Bug #2969: Scripted items can stack Bug #2987: Editor: some chance and AI data fields can overflow Bug #3623: Fix HiDPI on Windows Bug #3733: Normal maps are inverted on mirrored UVs + Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable Bug #4329: Removed birthsign abilities are restored after reloading the save Bug #4383: Bow model obscures crosshair when arrow is drawn Bug #4411: Reloading a saved game while falling prevents damage in some cases @@ -15,23 +17,31 @@ Bug #4723: ResetActors command works incorrectly Bug #4745: Editor: Interior cell lighting field values are not displayed as colors Bug #4746: Non-solid player can't run or sneak + Bug #4747: Bones are not read from X.NIF file for NPC animation Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4803: Stray special characters before begin statement break script compilation Bug #4804: Particle system with the "Has Sizes = false" causes an exception Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds + Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal Bug #4820: Spell absorption is broken Bug #4827: NiUVController is handled incorrectly Bug #4828: Potion looping effects VFX are not shown for NPCs + Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded + Bug #4860: Actors outside of processing range visible for one frame after spawning Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis + Feature #3980: In-game option to disable controller + Feature #4209: Editor: Faction rank sub-table Feature #4673: Weapon sheathing Feature #4730: Native animated containers support Feature #4812: Support NiSwitchNode + Feature #4836: Daytime node switch Task #4686: Upgrade media decoder to a more current FFmpeg API 0.45.0 diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index e411a1d9b..895b07e1e 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -2,7 +2,7 @@ brew update brew unlink cmake || true -brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/a3b64391ebace30b84de8e7997665a1621c0b2c0/Formula/cmake.rb +brew install https://gist.githubusercontent.com/nikolaykasyanov/f36da224bdef42025e480f99fa21a82d/raw/7dd8b5ed2750198757f81c6bc6456e03541999bd/cmake.rb brew switch cmake 3.12.4 brew outdated pkgconfig || brew upgrade pkgconfig brew install qt diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b5bf0b632..4a114490c 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -608,9 +608,9 @@ printf "OSG 3.4.1-scrawl... " SUFFIX="" fi add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \ - "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll + "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll echo Done. } cd $DEPS diff --git a/CMakeLists.txt b/CMakeLists.txt index 18ade201c..b6e646c13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,7 +266,7 @@ endif() IF(BUILD_OPENMW OR BUILD_OPENCS) - find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX) + find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 86501b3cb..1a91b7cea 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -84,6 +84,8 @@ namespace ESSImport mGlobalMapState.mBounds.mMaxX = 0; mGlobalMapState.mBounds.mMinY = 0; mGlobalMapState.mBounds.mMaxY = 0; + + mPlayerBase.blank(); } int generateActorId() diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 2c827373d..0e9b0745c 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -530,6 +530,10 @@ namespace CSMWorld RaceDataPtr raceData = getRaceData(npc.mRace); data->reset_data(id, "", false, !npc.isMale(), raceData); + // Add head and hair + data->setPart(ESM::PRT_Head, npc.mHead, 0); + data->setPart(ESM::PRT_Hair, npc.mHair, 0); + // Add inventory items for (auto& item : npc.mInventory.mList) { @@ -537,10 +541,6 @@ namespace CSMWorld std::string itemId = item.mItem.toString(); addNpcItem(itemId, data); } - - // Add head and hair - data->setPart(ESM::PRT_Head, npc.mHead, 0); - data->setPart(ESM::PRT_Hair, npc.mHair, 0); } void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) @@ -574,6 +574,10 @@ namespace CSMWorld partId = part.mMale; data->setPart(partType, partId, priority); + + // An another vanilla quirk: hide hairs if an item replaces Head part + if (partType == ESM::PRT_Head) + data->setPart(ESM::PRT_Hair, "", priority); } }; diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 1f16c9695..cf333c1b1 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -30,6 +30,7 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_Skill, Display_Class, Display_Faction, + Display_Rank, Display_Race, Display_Sound, Display_Region, diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index df37afe60..94ebbef55 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -46,6 +46,7 @@ namespace CSMWorld Display_Skill, Display_Class, Display_Faction, + Display_Rank, Display_Race, Display_Sound, Display_Region, diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 109708ab0..a04281ea6 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -234,9 +234,17 @@ namespace CSMWorld { ColumnId_SoundChance, "Chance" }, { ColumnId_FactionReactions, "Reactions" }, + { ColumnId_FactionRanks, "Ranks" }, //{ ColumnId_FactionID, "Faction ID" }, { ColumnId_FactionReaction, "Reaction" }, + { ColumnId_FactionAttrib1, "Attrib 1" }, + { ColumnId_FactionAttrib2, "Attrib 2" }, + { ColumnId_FactionPrimSkill, "Prim Skill" }, + { ColumnId_FactionFavSkill, "Fav Skill" }, + { ColumnId_FactionRep, "Fact Rep" }, + { ColumnId_RankName, "Rank Name" }, + { ColumnId_EffectList, "Effects" }, { ColumnId_EffectId, "Effect" }, //{ ColumnId_EffectAttribute, "Attrib" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index f9ba5725a..528d210a4 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -338,6 +338,14 @@ namespace CSMWorld ColumnId_LandColoursIndex = 304, ColumnId_LandTexturesIndex = 305, + ColumnId_RankName = 306, + ColumnId_FactionRanks = 307, + ColumnId_FactionPrimSkill = 308, + ColumnId_FactionFavSkill = 309, + ColumnId_FactionRep = 310, + ColumnId_FactionAttrib1 = 311, + ColumnId_FactionAttrib2 = 312, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9e9447c5a..da46ea876 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -132,6 +132,23 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionReaction, ColumnBase::Display_Integer)); + // Faction Ranks + mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionRanks)); + index = mFactions.getColumns()-1; + mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionRanksAdapter ())); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_RankName, ColumnBase::Display_Rank)); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_FactionAttrib1, ColumnBase::Display_Integer)); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_FactionAttrib2, ColumnBase::Display_Integer)); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_FactionPrimSkill, ColumnBase::Display_Integer)); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_FactionFavSkill, ColumnBase::Display_Integer)); + mFactions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_FactionRep, ColumnBase::Display_Integer)); + mRaces.addColumn (new StringIdColumn); mRaces.addColumn (new RecordStateColumn); mRaces.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Race)); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 464757cd4..5ae0dabaf 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1090,4 +1090,83 @@ namespace CSMWorld { return 10; } + + FactionRanksAdapter::FactionRanksAdapter () {} + + void FactionRanksAdapter::addRow(Record& record, int position) const + { + throw std::logic_error ("cannot add a row to a fixed table"); + } + + void FactionRanksAdapter::removeRow(Record& record, int rowToRemove) const + { + throw std::logic_error ("cannot remove a row from a fixed table"); + } + + void FactionRanksAdapter::setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const + { + throw std::logic_error ("table operation not supported"); + } + + NestedTableWrapperBase* FactionRanksAdapter::table(const Record& record) const + { + throw std::logic_error ("table operation not supported"); + } + + QVariant FactionRanksAdapter::getData(const Record& record, + int subRowIndex, int subColIndex) const + { + ESM::Faction faction = record.get(); + + if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) + throw std::runtime_error ("index out of range"); + + auto& rankData = faction.mData.mRankData[subRowIndex]; + + switch (subColIndex) + { + case 0: return QString(faction.mRanks[subRowIndex].c_str()); + case 1: return rankData.mAttribute1; + case 2: return rankData.mAttribute2; + case 3: return rankData.mSkill1; + case 4: return rankData.mSkill2; + case 5: return rankData.mFactReaction; + default: throw std::runtime_error("Rank subcolumn index out of range"); + } + } + + void FactionRanksAdapter::setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const + { + ESM::Faction faction = record.get(); + + if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) + throw std::runtime_error ("index out of range"); + + auto& rankData = faction.mData.mRankData[subRowIndex]; + + switch (subColIndex) + { + case 0: faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); break; + case 1: rankData.mAttribute1 = value.toInt(); break; + case 2: rankData.mAttribute2 = value.toInt(); break; + case 3: rankData.mSkill1 = value.toInt(); break; + case 4: rankData.mSkill2 = value.toInt(); break; + case 5: rankData.mFactReaction = value.toInt(); break; + default: throw std::runtime_error("Rank index out of range"); + } + + record.setModified (faction); + } + + int FactionRanksAdapter::getColumnsCount(const Record& record) const + { + return 6; + } + + int FactionRanksAdapter::getRowsCount(const Record& record) const + { + return 10; + } } diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 131f547a5..60d983098 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -100,6 +100,31 @@ namespace CSMWorld virtual int getRowsCount(const Record& record) const; }; + class FactionRanksAdapter : public NestedColumnAdapter + { + public: + FactionRanksAdapter (); + + virtual void addRow(Record& record, int position) const; + + virtual void removeRow(Record& record, int rowToRemove) const; + + virtual void setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* table(const Record& record) const; + + virtual QVariant getData(const Record& record, + int subRowIndex, int subColIndex) const; + + virtual void setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getColumnsCount(const Record& record) const; + + virtual int getRowsCount(const Record& record) const; + }; + class RegionSoundListAdapter : public NestedColumnAdapter { public: diff --git a/apps/opencs/view/render/lighting.cpp b/apps/opencs/view/render/lighting.cpp index 8e068168f..f62e86148 100644 --- a/apps/opencs/view/render/lighting.cpp +++ b/apps/opencs/view/render/lighting.cpp @@ -1,5 +1,38 @@ #include "lighting.hpp" #include +#include +#include + +#include + +class DayNightSwitchVisitor : public osg::NodeVisitor +{ +public: + DayNightSwitchVisitor(int index) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mIndex(index) + { } + + virtual void apply(osg::Switch &switchNode) + { + if (switchNode.getName() == Constants::NightDayLabel) + switchNode.setSingleChildOn(mIndex); + + traverse(switchNode); + } + +private: + int mIndex; +}; CSVRender::Lighting::~Lighting() {} + +void CSVRender::Lighting::updateDayNightMode(int index) +{ + if (mRootNode == nullptr) + return; + + DayNightSwitchVisitor visitor(index); + mRootNode->accept(visitor); +} diff --git a/apps/opencs/view/render/lighting.hpp b/apps/opencs/view/render/lighting.hpp index a4315d02f..66b0eec00 100644 --- a/apps/opencs/view/render/lighting.hpp +++ b/apps/opencs/view/render/lighting.hpp @@ -19,7 +19,7 @@ namespace CSVRender Lighting() : mRootNode(0) {} virtual ~Lighting(); - virtual void activate (osg::Group* rootNode) = 0; + virtual void activate (osg::Group* rootNode, bool isExterior) = 0; virtual void deactivate() = 0; @@ -27,6 +27,8 @@ namespace CSVRender protected: + void updateDayNightMode(int index); + osg::ref_ptr mLightSource; osg::Group* mRootNode; }; diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index 07900d6b0..d76823fb3 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -4,7 +4,7 @@ CSVRender::LightingBright::LightingBright() {} -void CSVRender::LightingBright::activate (osg::Group* rootNode) +void CSVRender::LightingBright::activate (osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; @@ -20,6 +20,8 @@ void CSVRender::LightingBright::activate (osg::Group* rootNode) mLightSource->setLight(light); mRootNode->addChild(mLightSource); + + updateDayNightMode(0); } void CSVRender::LightingBright::deactivate() diff --git a/apps/opencs/view/render/lightingbright.hpp b/apps/opencs/view/render/lightingbright.hpp index bc6422814..7bdebfd3d 100644 --- a/apps/opencs/view/render/lightingbright.hpp +++ b/apps/opencs/view/render/lightingbright.hpp @@ -17,7 +17,7 @@ namespace CSVRender LightingBright(); - virtual void activate (osg::Group* rootNode); + virtual void activate (osg::Group* rootNode, bool /*isExterior*/); virtual void deactivate(); diff --git a/apps/opencs/view/render/lightingday.cpp b/apps/opencs/view/render/lightingday.cpp index a841edc63..dc4592b21 100644 --- a/apps/opencs/view/render/lightingday.cpp +++ b/apps/opencs/view/render/lightingday.cpp @@ -4,7 +4,7 @@ CSVRender::LightingDay::LightingDay(){} -void CSVRender::LightingDay::activate (osg::Group* rootNode) +void CSVRender::LightingDay::activate (osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; @@ -19,6 +19,8 @@ void CSVRender::LightingDay::activate (osg::Group* rootNode) mLightSource->setLight(light); mRootNode->addChild(mLightSource); + + updateDayNightMode(0); } void CSVRender::LightingDay::deactivate() diff --git a/apps/opencs/view/render/lightingday.hpp b/apps/opencs/view/render/lightingday.hpp index 2dc3c02b6..516dd2bbf 100644 --- a/apps/opencs/view/render/lightingday.hpp +++ b/apps/opencs/view/render/lightingday.hpp @@ -11,7 +11,7 @@ namespace CSVRender LightingDay(); - virtual void activate (osg::Group* rootNode); + virtual void activate (osg::Group* rootNode, bool /*isExterior*/); virtual void deactivate(); diff --git a/apps/opencs/view/render/lightingnight.cpp b/apps/opencs/view/render/lightingnight.cpp index 6f87d020b..fbebb46a1 100644 --- a/apps/opencs/view/render/lightingnight.cpp +++ b/apps/opencs/view/render/lightingnight.cpp @@ -4,7 +4,7 @@ CSVRender::LightingNight::LightingNight() {} -void CSVRender::LightingNight::activate (osg::Group* rootNode) +void CSVRender::LightingNight::activate (osg::Group* rootNode, bool isExterior) { mRootNode = rootNode; @@ -20,6 +20,8 @@ void CSVRender::LightingNight::activate (osg::Group* rootNode) mLightSource->setLight(light); mRootNode->addChild(mLightSource); + + updateDayNightMode(isExterior ? 1 : 0); } void CSVRender::LightingNight::deactivate() diff --git a/apps/opencs/view/render/lightingnight.hpp b/apps/opencs/view/render/lightingnight.hpp index dae2a8fa3..3f03150cd 100644 --- a/apps/opencs/view/render/lightingnight.hpp +++ b/apps/opencs/view/render/lightingnight.hpp @@ -11,7 +11,7 @@ namespace CSVRender LightingNight(); - virtual void activate (osg::Group* rootNode); + virtual void activate (osg::Group* rootNode, bool isExterior); virtual void deactivate(); virtual osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient); diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 972fb556d..522534adb 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -20,6 +20,8 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, connect (&mData, SIGNAL (assetTablesChanged ()), this, SLOT (assetTablesChanged ())); + setExterior(false); + if (!referenceable) { QAbstractItemModel *references = diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 7e1666dc1..8ae9d8a62 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -195,6 +195,7 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste , mResourceSystem(resourceSystem) , mLighting(nullptr) , mHasDefaultAmbient(false) + , mIsExterior(true) , mPrevMouseX(0) , mPrevMouseY(0) , mCamPositionSet(false) @@ -250,7 +251,7 @@ void SceneWidget::setLighting(Lighting *lighting) mLighting->deactivate(); mLighting = lighting; - mLighting->activate (mRootNode); + mLighting->activate (mRootNode, mIsExterior); osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : 0); setAmbient(ambient); @@ -315,6 +316,11 @@ void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); } +void SceneWidget::setExterior (bool isExterior) +{ + mIsExterior = isExterior; +} + void SceneWidget::mouseMoveEvent (QMouseEvent *event) { mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 13a109a9b..c2c354274 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -89,6 +89,8 @@ namespace CSVRender void setDefaultAmbient (const osg::Vec4f& colour); ///< \note The actual ambient colour may differ based on lighting settings. + void setExterior (bool isExterior); + protected: void setLighting (Lighting *lighting); ///< \attention The ownership of \a lighting is not transferred to *this. @@ -104,6 +106,7 @@ namespace CSVRender osg::Vec4f mDefaultAmbient; bool mHasDefaultAmbient; + bool mIsExterior; LightingDay mLightingDay; LightingNight mLightingNight; LightingBright mLightingBright; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 3201f7303..a3f0636be 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -28,6 +28,11 @@ void CSVRender::UnpagedWorldspaceWidget::update() setDefaultAmbient (colour); + bool isInterior = (record.get().mData.mFlags & ESM::Cell::Interior) != 0; + bool behaveLikeExterior = (record.get().mData.mFlags & ESM::Cell::QuasiEx) != 0; + + setExterior(behaveLikeExterior || !isInterior); + /// \todo deal with mSunlight and mFog/mForDensity flagAsModified(); diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp index 82ca2b0c9..e30696458 100644 --- a/apps/opencs/view/widget/coloreditor.cpp +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -82,6 +82,7 @@ void CSVWidget::ColorEditor::setColor(const int colorInt) void CSVWidget::ColorEditor::showPicker() { mColorPicker->showPicker(calculatePopupPosition(), mColor); + emit pickingFinished(); } void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index a38728ef3..206a66727 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -39,7 +39,8 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo // Calling getColor() creates a blocking dialog that will continue execution once the user chooses OK or Cancel QColor color = mColorPicker->getColor(initialColor); - mColorPicker->setCurrentColor(color); + if (color.isValid()) + mColorPicker->setCurrentColor(color); } void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 66c7e023c..89ecc4fdb 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -170,6 +170,7 @@ target_link_libraries(tes3mp ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 416159edd..0b010d7d1 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -369,6 +369,9 @@ OMW::Engine::~Engine() mViewer = nullptr; + delete mEncoder; + mEncoder = nullptr; + if (mWindow) { SDL_DestroyWindow(mWindow); @@ -789,8 +792,7 @@ void OMW::Engine::go() settingspath = loadSettings (settings); // Create encoder - ToUTF8::Utf8Encoder encoder (mEncoding); - mEncoder = &encoder; + mEncoder = new ToUTF8::Utf8Encoder(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index b51359356..46cf78e17 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -58,7 +58,7 @@ namespace MWMechanics namespace DetourNavigator { - class Navigator; + struct Navigator; } namespace MWWorld @@ -332,6 +332,8 @@ namespace MWBase virtual int getCurrentWeather() const = 0; + virtual unsigned int getNightDayMode() const = 0; + virtual int getMasserPhase() const = 0; virtual int getSecundaPhase() const = 0; @@ -458,7 +460,7 @@ namespace MWBase virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; - virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; virtual bool toggleCollisionMode() = 0; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 131a8a0de..c170fde96 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -895,7 +895,7 @@ namespace MWClass MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - if (attacker.isEmpty() || (!attacker.isEmpty() && !(object.isEmpty() && !attacker.getClass().isNpc()))) // Unarmed creature attacks don't affect armor condition + if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition { int armorhealth = armor.getClass().getItemHealth(armor); armorhealth -= std::min(damageDiff, armorhealth); @@ -1120,8 +1120,12 @@ namespace MWClass const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); - bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool swimming = world->isSwimming(ptr); + bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); + bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + sneaking = sneaking && (inair || MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); + bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); @@ -1146,7 +1150,7 @@ namespace MWClass flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } - else if (world->isSwimming(ptr)) + else if (swimming) { float swimSpeed = walkSpeed; if(running) @@ -1156,7 +1160,7 @@ namespace MWClass gmst.fSwimRunAthleticsMult->mValue.getFloat(); moveSpeed = swimSpeed; } - else if (running) + else if (running && !sneaking) moveSpeed = runSpeed; else moveSpeed = walkSpeed; diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 846597886..5f20a8abb 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -41,19 +41,6 @@ namespace MWDialogue void Quest::setIndex (int index) { - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); - - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name) - { - if (iter->mQuestStatus==ESM::DialInfo::QS_Finished) - mFinished = true; - else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart) - mFinished = false; - } - // The index must be set even if no related journal entry was found mIndex = index; } @@ -81,8 +68,18 @@ namespace MWDialogue if (index==-1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); + for (auto &info : dialogue->mInfo) + { + if (info.mData.mJournalIndex == index + && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) + { + mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); + break; + } + } + if (index > mIndex) - setIndex (index); + mIndex = index; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 98980e339..af6567afb 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -9,7 +9,10 @@ #include #include +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 257d3c5e1..46a0dd18e 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -302,7 +302,7 @@ namespace MWGui if (mode == GM_Console) MWBase::Environment::get().getWindowManager()->setConsoleSelectedObject(object); - else if ((mode == GM_Container) || (mode == GM_Inventory)) + else //if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object if (!object.isEmpty()) diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index a2f6ea142..f45d73ca9 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -554,7 +554,7 @@ namespace mQuestMode = true; setVisible (LeftTopicIndex, false); - setVisible (CenterTopicIndex, true); + setVisible (CenterTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, false); setVisible (QuestsList, true); @@ -631,7 +631,7 @@ namespace if (page+2 < book->pageCount()) { - MWBase::Environment::get().getWindowManager()->playSound("book page", true); + MWBase::Environment::get().getWindowManager()->playSound("book page"); page += 2; updateShowingPages (); @@ -649,7 +649,7 @@ namespace if(page >= 2) { - MWBase::Environment::get().getWindowManager()->playSound("book page", true); + MWBase::Environment::get().getWindowManager()->playSound("book page"); page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index f2f1cf892..004a172b3 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -327,11 +327,9 @@ namespace MWGui addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); - SkillList::const_iterator end = skills.end(); - for (SkillList::const_iterator it = skills.begin(); it != end; ++it) + for (const int& skillId : skills) { - int skillId = *it; - if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes + if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 30727a545..c68ec2483 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -4,7 +4,10 @@ #include "windowbase.hpp" #include "referenceinterface.hpp" -#include "../mwworld/esmstore.hpp" +namespace ESM +{ + struct Spell; +} namespace MyGUI { @@ -17,7 +20,6 @@ namespace MWGui class WindowManager; } - namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index c3bc1ec19..c90d54072 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -41,7 +41,7 @@ namespace MWGui else if (skill < ESM::Skill::Length) setSkillId(static_cast(skill)); else - throw new std::runtime_error("Skill number out of range"); + throw std::runtime_error("Skill number out of range"); } void MWSkill::setSkillValue(const SkillValue& value) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 7d1e56a7c..e6e5aedb0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -66,6 +66,7 @@ #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwrender/vismask.hpp" diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index ce70a2691..9e11363d9 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -67,6 +67,7 @@ namespace MWInput , mInvertX (Settings::Manager::getBool("invert x axis", "Input")) , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) , mControlsDisabled(false) + , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) , mPreviewPOVDelay(0.f) @@ -671,6 +672,9 @@ namespace MWInput if (it->first == "Input" && it->second == "grab cursor") mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); + if (it->first == "Input" && it->second == "enable controller") + mJoystickEnabled = Settings::Manager::getBool("enable controller", "Input"); + if (it->first == "Video" && ( it->second == "resolution x" || it->second == "resolution y" @@ -871,6 +875,9 @@ namespace MWInput void InputManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg ) { + if (!mJoystickEnabled) + return; + mJoystickLastUsed = true; bool guiMode = false; @@ -905,6 +912,9 @@ namespace MWInput void InputManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg ) { + if (!mJoystickEnabled) + return; + mJoystickLastUsed = true; if(mInputBinder->detectingBindingState()) mInputBinder->buttonReleased(deviceID, arg); @@ -928,7 +938,7 @@ namespace MWInput void InputManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg ) { - if (!mControlsDisabled) + if (!mControlsDisabled && mJoystickEnabled) mInputBinder->axisMoved(deviceID, arg); } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 361babec4..41b0bd404 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -179,6 +179,7 @@ namespace MWInput bool mInvertY; bool mControlsDisabled; + bool mJoystickEnabled; float mCameraSensitivity; float mCameraYMultiplier; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f31f9fb18..e0f586311 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1,13 +1,11 @@ #include "actors.hpp" -#include -#include #include #include -#include #include #include +#include #include /* @@ -43,6 +41,8 @@ #include "../mwmechanics/aibreathe.hpp" +#include "../mwrender/vismask.hpp" + #include "spellcasting.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" @@ -379,6 +379,33 @@ namespace MWMechanics } } + void Actors::playIdleDialogue(const MWWorld::Ptr& actor) + { + if (!actor.getClass().isActor() || actor == getPlayer() || !MWBase::Environment::get().getSoundManager()->sayDone(actor)) + return; + + const CreatureStats &stats = actor.getClass().getCreatureStats(actor); + if (stats.getAiSetting(CreatureStats::AI_Hello).getModified() == 0) + return; + + const MWMechanics::AiSequence& seq = stats.getAiSequence(); + if (seq.isInCombat() || seq.hasPackage(AiPackage::TypeIdFollow) || seq.hasPackage(AiPackage::TypeIdEscort)) + return; + + const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3()); + const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000) + return; + + // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. + // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. + const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; + static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); + if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // No combat for totally static creatures @@ -550,7 +577,7 @@ namespace MWMechanics MagicEffects now = creatureStats.getSpells().getMagicEffects(); - if (creature.getTypeName()==typeid (ESM::NPC).name()) + if (creature.getClass().isNpc()) { MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); now += store.getMagicEffects(); @@ -1286,8 +1313,46 @@ namespace MWMechanics if (!anim) return; mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); + + CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) - mActors[ptr]->getCharacterController()->update(0); + ctrl->update(0); + + // We should initially hide actors outside of processing range. + // Note: since we update player after other actors, distance will be incorrect during teleportation. + // Do not update visibility if player was teleported, so actors will be visible during teleportation frame. + if (MWBase::Environment::get().getWorld()->getPlayer().wasTeleported()) + return; + + updateVisibility(ptr, ctrl); + } + + void Actors::updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (ptr == player) + return; + + const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); + if (dist > mActorsProcessingRange) + { + ptr.getRefData().getBaseNode()->setNodeMask(0); + return; + } + else + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + + // Fade away actors on large distance (>90% of actor's processing distance) + float visibilityRatio = 1.0; + float fadeStartDistance = mActorsProcessingRange*0.9f; + float fadeEndDistance = mActorsProcessingRange; + float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); + if (fadeRatio > 0) + visibilityRatio -= std::max(0.f, fadeRatio); + + visibilityRatio = std::min(1.f, visibilityRatio); + + ctrl->setVisibility(visibilityRatio); } void Actors::removeActor (const MWWorld::Ptr& ptr) @@ -1524,7 +1589,10 @@ namespace MWMechanics End of tes3mp change (major) */ - if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + // For dead actors we need to remove looping spell particles + if (iter->first.getClass().getCreatureStats(iter->first).isDead()) + ctrl->updateContinuousVfx(); + else { bool cellChanged = world->hasCellChanged(); MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports @@ -1599,14 +1667,17 @@ namespace MWMechanics { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) + { stats.getAiSequence().execute(iter->first, *ctrl, duration); + playIdleDialogue(iter->first); + } } } /* End of tes3mp change (major) */ - if(iter->first.getTypeName() == typeid(ESM::NPC).name()) + if(iter->first.getClass().isNpc()) { updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); calculateNpcStatModifiers(iter->first, duration); @@ -1640,11 +1711,11 @@ namespace MWMechanics if (!inRange) { iter->first.getRefData().getBaseNode()->setNodeMask(0); - world->setActorCollisionMode(iter->first, false); + world->setActorCollisionMode(iter->first, false, false); continue; } else if (!isPlayer) - iter->first.getRefData().getBaseNode()->setNodeMask(1<<3); + iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) ctrl->skipAnim(); @@ -1657,20 +1728,10 @@ namespace MWMechanics continue; } - world->setActorCollisionMode(iter->first, true); + world->setActorCollisionMode(iter->first, true, !iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()); ctrl->update(duration); - // Fade away actors on large distance (>90% of actor's processing distance) - float visibilityRatio = 1.0; - float fadeStartDistance = mActorsProcessingRange*0.9f; - float fadeEndDistance = mActorsProcessingRange; - float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); - if (fadeRatio > 0) - visibilityRatio -= std::max(0.f, fadeRatio); - - visibilityRatio = std::min(1.f, visibilityRatio); - - ctrl->setVisibility(visibilityRatio); + updateVisibility(iter->first, ctrl); } if (playerCharacter) @@ -1699,72 +1760,7 @@ namespace MWMechanics } killDeadActors(); - - static float sneakTimer = 0.f; // times update of sneak icon - - // if player is in sneak state see if anyone detects him - if (playerCharacter && playerCharacter->isSneaking()) - { - static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" - - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const int radius = esmStore.get().find("fSneakUseDist")->mValue.getInteger(); - - static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->mValue.getFloat(); - - if (sneakTimer >= fSneakUseDelay) - sneakTimer = 0.f; - - if (sneakTimer == 0.f) - { - // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. - bool avoidedNotice = false; - - bool detected = false; - - for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) - { - MWWorld::Ptr observer = iter->first; - - if (iter->first == player) // not the player - continue; - - if (observer.getClass().getCreatureStats(observer).isDead()) - continue; - - // is the player in range and can they be detected - if ((observer.getRefData().getPosition().asVec3() - playerPos).length2() <= radius*radius - && MWBase::Environment::get().getWorld()->getLOS(player, observer)) - { - if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) - { - detected = true; - avoidedNotice = false; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - break; - } - else - avoidedNotice = true; - } - } - - if (sneakSkillTimer >= fSneakUseDelay) - sneakSkillTimer = 0.f; - - if (avoidedNotice && sneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); - - if (!detected) - MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); - } - sneakTimer += duration; - sneakSkillTimer += duration; - } - else - { - sneakTimer = 0.f; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - } + updateSneaking(playerCharacter, duration); } updateCombatMusic(); @@ -1967,6 +1963,86 @@ namespace MWMechanics fastForwardAi(); } + void Actors::updateSneaking(CharacterController* ctrl, float duration) + { + static float sneakTimer = 0.f; // Times update of sneak icon + + if (!ctrl) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + MWWorld::Ptr player = getPlayer(); + + CreatureStats& stats = player.getClass().getCreatureStats(player); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool inair = !world->isOnGround(player) && !world->isSwimming(player) && !world->isFlying(player); + sneaking = sneaking && (ctrl->isSneaking() || inair); + + if (!sneaking) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" + + const MWWorld::Store& gmst = world->getStore().get(); + static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); + static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); + + if (sneakTimer >= fSneakUseDelay) + sneakTimer = 0.f; + + if (sneakTimer == 0.f) + { + // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. + bool avoidedNotice = false; + bool detected = false; + + std::vector observers; + osg::Vec3f position(player.getRefData().getPosition().asVec3()); + float radius = std::min(fSneakUseDist, mActorsProcessingRange); + getObjectsInRange(position, radius, observers); + + for (const MWWorld::Ptr &observer : observers) + { + if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) + continue; + + if (world->getLOS(player, observer)) + { + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) + { + detected = true; + avoidedNotice = false; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + else + { + avoidedNotice = true; + } + } + } + + if (sneakSkillTimer >= fSneakUseDelay) + sneakSkillTimer = 0.f; + + if (avoidedNotice && sneakSkillTimer == 0.f) + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + + sneakTimer += duration; + sneakSkillTimer += duration; + } + int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const { float healthPerHour, magickaPerHour; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0fe3a21b8..0cb786e2a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -5,10 +5,23 @@ #include #include #include +#include -#include "../mwbase/world.hpp" +namespace ESM +{ + class ESMReader; + class ESMWriter; +} -#include "movement.hpp" +namespace osg +{ + class Vec3f; +} + +namespace Loading +{ + class Listener; +} namespace MWWorld { @@ -19,6 +32,7 @@ namespace MWWorld namespace MWMechanics { class Actor; + class CharacterController; class CreatureStats; class Actors @@ -102,12 +116,17 @@ namespace MWMechanics */ void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer); + void playIdleDialogue(const MWWorld::Ptr& actor); + void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); void rest(bool sleep); ///< Update actors while the player is waiting or sleeping. This should be called every hour. + void updateSneaking(CharacterController* ctrl, float duration); + ///< Update the sneaking indicator state according to the given player character controller. + void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); int getHoursToRest(const MWWorld::Ptr& ptr) const; @@ -179,6 +198,8 @@ namespace MWMechanics bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; private: + void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); + PtrActorMap mActors; float mTimerDisposeSummonsCorpses; float mActorsProcessingRange; diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 510e41db3..3c6dc940c 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index e7d1ecee1..8fc35de49 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -12,7 +12,7 @@ #include "steering.hpp" MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) -: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mAdjAngle(0) +: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mLastPos(ESM::Position()), mAdjAngle(0) { } diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 38dc2fa73..e448313bf 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -2,6 +2,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 70a8a6bbd..7128fe7a2 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -1,10 +1,13 @@ #ifndef GAME_MWMECHANICS_AICAST_H #define GAME_MWMECHANICS_AICAST_H -#include "../mwbase/world.hpp" - #include "aipackage.hpp" +namespace MWWorld +{ + class Ptr; +} + namespace MWMechanics { /// AiPackage which makes an actor to cast given spell. diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index d990d50cc..045cd9a0f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -639,13 +639,13 @@ namespace MWMechanics if (actor.getClass().isNpc()) { baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); + } - //say a provoking combat phrase - int chance = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } + // Say a provoking combat phrase + const int iVoiceAttackOdds = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99() < iVoiceAttackOdds) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 82dba960e..56c9b1c85 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -5,8 +5,6 @@ #include -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aiface.cpp b/apps/openmw/mwmechanics/aiface.cpp index 7d15cabf2..b99a8c1f4 100644 --- a/apps/openmw/mwmechanics/aiface.cpp +++ b/apps/openmw/mwmechanics/aiface.cpp @@ -1,6 +1,6 @@ #include "aiface.hpp" -#include "../mwbase/world.hpp" +#include "../mwworld/ptr.hpp" #include "steering.hpp" diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 5be858b0b..d7287cd2f 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -7,8 +7,6 @@ #include "obstacle.hpp" #include "aistate.hpp" -#include - namespace MWWorld { class Ptr; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index e5c136980..82641af8d 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -4,6 +4,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 455b0a2fd..ea83a10e5 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -3,10 +3,6 @@ #include "aipackage.hpp" -#include "../mwbase/world.hpp" - -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 40f4ab1d4..949fb74dd 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -2,7 +2,6 @@ #define AISTATE_H #include -#include namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index c297771d0..f9f9f43fc 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -3,8 +3,6 @@ #include "aipackage.hpp" -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d2a57c354..05465c6b0 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -8,7 +8,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -167,8 +166,6 @@ namespace MWMechanics doPerFrameActionsForState(actor, duration, storage); - playIdleDialogueRandomly(actor); - float& lastReaction = storage.mReaction; lastReaction += duration; if (AI_REACTION_TIME <= lastReaction) @@ -492,36 +489,6 @@ namespace MWMechanics } } - void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor) - { - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) - && MWBase::Environment::get().getSoundManager()->sayDone(actor)) - { - MWWorld::Ptr player = getPlayer(); - - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->mValue.getFloat(); - - float roll = Misc::Rng::rollProbability() * 10000.0f; - - // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds - // due to the roll being an integer. - // Our implementation does not have these issues, so needs to be recalibrated. We chose to - // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. - float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - if (roll < x && (playerPos - actorPos).length2() < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } - } - void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage) { // Play a random voice greeting if the player gets too close diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index bba6f7113..d586bb0bc 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -138,7 +138,6 @@ namespace MWMechanics void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void playIdleDialogueRandomly(const MWWorld::Ptr& actor); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp index 50be8a5d9..460713ae3 100644 --- a/apps/openmw/mwmechanics/autocalcspell.hpp +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -1,9 +1,14 @@ #ifndef OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H -#include -#include -#include +#include +#include + +namespace ESM +{ + struct Spell; + struct Race; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 575bd9463..4789e999a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -361,15 +361,14 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) idle = CharState_None; } -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, CharacterState& movement, bool force) +void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force) { - if (!force && jump == mJumpState && idle == CharState_None && movement == CharState_None) + if (!force && jump == mJumpState && idle == CharState_None) return; - if (jump != JumpState_None && !(mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) // FIXME + if (jump == JumpState_InAir) { idle = CharState_None; - movement = CharState_None; } std::string jumpAnimName; @@ -400,6 +399,7 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState if (!force && jump == mJumpState) return; + bool startAtLoop = (jump == mJumpState); mJumpState = jump; if (!mCurrentJump.empty()) @@ -413,7 +413,7 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState if (mAnimation->hasAnimation(jumpAnimName)) { mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, - 1.0f, "start", "stop", 0.f, ~0ul); + 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); mCurrentJump = jumpAnimName; } } @@ -691,7 +691,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (!mPtr.getClass().hasInventoryStore(mPtr)) weap = sWeaponTypeListEnd; - refreshJumpAnims(weap, jump, idle, movement, force); + refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); // idle handled last as it can depend on the other states @@ -2259,12 +2259,6 @@ void CharacterController::update(float duration, bool animationOnly) inJump = false; - // Do not play turning animation for player if rotation speed is very slow. - // Actual threshold should take framerate in account. - float rotationThreshold = 0; - if (isPlayer) - rotationThreshold = 0.015 * 60 * duration; - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) { if(vec.x() > 0.0f) @@ -2289,10 +2283,16 @@ void CharacterController::update(float duration, bool animationOnly) } else if(rot.z() != 0.0f) { + // Do not play turning animation for player if rotation speed is very slow. + // Actual threshold should take framerate in account. + float rotationThreshold = 0.f; + if (isPlayer) + rotationThreshold = 0.015 * 60 * duration; + // It seems only bipedal actors use turning animations. // Also do not use turning animations in the first-person view and when sneaking. bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); - if (!sneak && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) + if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) { if(rot.z() > rotationThreshold) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; @@ -2305,12 +2305,15 @@ void CharacterController::update(float duration, bool animationOnly) if (playLandingSound) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - std::string sound = "DefaultLand"; + std::string sound; osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3()); if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr)) sound = "DefaultLandWater"; + else if (onground) + sound = "DefaultLand"; - sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); + if (!sound.empty()) + sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering @@ -2319,7 +2322,7 @@ void CharacterController::update(float duration, bool animationOnly) float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; float complete; bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); - if (movestate == CharState_None && isTurning()) + if (movestate == CharState_None && jumpstate == JumpState_None && isTurning()) { if (animPlaying && complete < threshold) movestate = mMovementState; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 7e6e82c07..f2b1f33a7 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -31,10 +31,8 @@ enum Priority { Priority_WeaponLowerBody, Priority_SneakIdleLowerBody, Priority_SwimIdle, - Priority_Movement, - // Note: in vanilla movement anims have higher priority than jump ones. - // It causes issues with landing animations during movement. Priority_Jump, + Priority_Movement, Priority_Hit, Priority_Weapon, Priority_Block, @@ -214,7 +212,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); - void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, CharacterState& movement, bool force=false); + void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false); void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 8b93ca204..e5b6b5dd8 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -1,7 +1,15 @@ #ifndef OPENMW_MECHANICS_COMBAT_H #define OPENMW_MECHANICS_COMBAT_H -#include "../mwworld/ptr.hpp" +namespace osg +{ + class Vec3f; +} + +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 4b9b60214..fa896dd86 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -20,6 +20,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 8e3b00e5a..275147671 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -7,9 +7,6 @@ #include "../mwworld/ptr.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - namespace MWMechanics { class Enchanting diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d405ce6b7..8d2e30fd9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1252,8 +1252,7 @@ namespace MWMechanics if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { - const MWWorld::Ptr victimRef = MWBase::Environment::get().getWorld()->searchPtr(ownerCellRef->getOwner(), true); - if (victimRef.isEmpty() || !victimRef.getClass().getCreatureStats(victimRef).isDead()) + if (victim.isEmpty() || (victim.getClass().isActor() && !victim.getClass().getCreatureStats(victim).isDead())) mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; } if (alarm) diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 726508161..d4a393c72 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -7,6 +7,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "character.hpp" #include "movement.hpp" namespace MWMechanics diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 81b95baf8..1bcf646a4 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -3,8 +3,12 @@ #include #include +#include -#include "character.hpp" +namespace osg +{ + class Vec3f; +} namespace MWWorld { @@ -14,6 +18,8 @@ namespace MWWorld namespace MWMechanics { + class CharacterController; + class Objects { typedef std::map PtrControllerMap; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 512e60611..e13b2e64f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -312,17 +312,6 @@ namespace MWMechanics return true; // must still apply to get visual effect and have target regard it as attack } break; - case ESM::MagicEffect::AlmsiviIntervention: - case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::Mark: - case ESM::MagicEffect::Recall: - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return false; - } - break; case ESM::MagicEffect::WaterWalking: if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) return false; @@ -849,21 +838,19 @@ namespace MWMechanics else if (target.getClass().isActor() && target == getPlayer()) { MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled(); - if (effectId == ESM::MagicEffect::DivineIntervention) + if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention) { - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); - anim->removeEffect(ESM::MagicEffect::DivineIntervention); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_end"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1); - return true; - } - else if (effectId == ESM::MagicEffect::AlmsiviIntervention) - { - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); - anim->removeEffect(ESM::MagicEffect::AlmsiviIntervention); + if (!teleportingEnabled) + { + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return true; + } + std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker); + anim->removeEffect(effectId); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_end"); if (fx) @@ -872,8 +859,15 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Mark) { - MWBase::Environment::get().getWorld()->getPlayer().markPosition( - target.getCell(), target.getRefData().getPosition()); + if (teleportingEnabled) + { + MWBase::Environment::get().getWorld()->getPlayer().markPosition( + target.getCell(), target.getRefData().getPosition()); + } + else if (caster == getPlayer()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } /* Start of tes3mp addition @@ -889,6 +883,13 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Recall) { + if (!teleportingEnabled) + { + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return true; + } + MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; @@ -898,7 +899,7 @@ namespace MWMechanics MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); action.execute(target); - anim->removeEffect(ESM::MagicEffect::Recall); + anim->removeEffect(effectId); } return true; } diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 3b242266c..c55d5ab10 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwmechanics/spellpriority.hpp b/apps/openmw/mwmechanics/spellpriority.hpp index 4e5ff5c71..0305f24b5 100644 --- a/apps/openmw/mwmechanics/spellpriority.hpp +++ b/apps/openmw/mwmechanics/spellpriority.hpp @@ -1,9 +1,17 @@ #ifndef OPENMW_SPELL_PRIORITY_H #define OPENMW_SPELL_PRIORITY_H -#include +namespace ESM +{ + struct Spell; + struct EffectList; + struct ENAMstruct; +} -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp index e006370dd..e30b82f5e 100644 --- a/apps/openmw/mwmechanics/trading.hpp +++ b/apps/openmw/mwmechanics/trading.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_MECHANICS_TRADING_H #define OPENMW_MECHANICS_TRADING_H -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmp/DedicatedPlayer.cpp b/apps/openmw/mwmp/DedicatedPlayer.cpp index a1c102d21..7aaf87f2d 100644 --- a/apps/openmw/mwmp/DedicatedPlayer.cpp +++ b/apps/openmw/mwmp/DedicatedPlayer.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/actor.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/mechanicsmanagerimp.hpp" #include "../mwmechanics/spellcasting.hpp" diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5607fc0a6..73a5dbe6a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1238,33 +1238,6 @@ namespace MWPhysics return false; } - void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) - { - ActorMap::iterator found = mActors.find(ptr); - if (found != mActors.end()) - { - bool cmode = found->second->getCollisionMode(); - if (cmode == enabled) - return; - - cmode = enabled; - found->second->enableCollisionMode(cmode); - found->second->enableCollisionBody(cmode); - } - } - - bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr) - { - ActorMap::iterator found = mActors.find(ptr); - if (found != mActors.end()) - { - bool cmode = found->second->getCollisionMode(); - return cmode; - } - - return false; - } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement) { PtrVelocityList::iterator iter = mMovementQueue.begin(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index de6c68d39..6a44b3792 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -92,8 +92,6 @@ namespace MWPhysics const HeightField* getHeightField(int x, int y) const; bool toggleCollisionMode(); - bool isActorCollisionEnabled(const MWWorld::Ptr& ptr); - void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled); void stepSimulation(float dt); void debugDraw(); diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 9f2f961ec..afe8f5cd3 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -328,32 +328,32 @@ void ActorAnimation::updateQuiver() suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow; } - if (ammoNode && suitableAmmo) + if (!suitableAmmo) + return; + + // We should not show more ammo than equipped and more than quiver mesh has + ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); + + // Remove existing ammo nodes + for (unsigned int i=0; igetNumChildren(); ++i) { - // We should not show more ammo than equipped and more than quiver mesh has - ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); + osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); + if (!arrowNode->getNumChildren()) + continue; - // Remove existing ammo nodes - for (unsigned int i=0; igetNumChildren(); ++i) - { - osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); - if (!arrowNode->getNumChildren()) - continue; + osg::ref_ptr arrowChildNode = arrowNode->getChild(0); + arrowNode->removeChild(arrowChildNode); + } - osg::ref_ptr arrowChildNode = arrowNode->getChild(0); - arrowNode->removeChild(arrowChildNode); - } - - // Add new ones - osg::Vec4f glowColor = getEnchantmentColor(*ammo); - std::string model = ammo->getClass().getModel(*ammo); - for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); - osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); - if (!ammo->getClass().getEnchantment(*ammo).empty()) - addGlow(arrow, glowColor); - } + // Add new ones + osg::Vec4f glowColor = getEnchantmentColor(*ammo); + std::string model = ammo->getClass().getModel(*ammo); + for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); + osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); + if (!ammo->getClass().getEnchantment(*ammo).empty()) + addGlow(arrow, glowColor); } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7730b9104..4b0eec7b9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include @@ -91,6 +93,47 @@ namespace std::vector > mToRemove; }; + class DayNightCallback : public osg::NodeCallback + { + public: + DayNightCallback() : mCurrentState(0) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); + const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0; + + if (newState != mCurrentState) + { + mCurrentState = newState; + node->asSwitch()->setSingleChildOn(mCurrentState); + } + + traverse(node, nv); + } + + private: + unsigned int mCurrentState; + }; + + class AddSwitchCallbacksVisitor : public osg::NodeVisitor + { + public: + AddSwitchCallbacksVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { } + + virtual void apply(osg::Switch &switchNode) + { + if (switchNode.getName() == Constants::NightDayLabel) + switchNode.addUpdateCallback(new DayNightCallback()); + + traverse(switchNode); + } + }; + NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) { NifOsg::TextKeyMap::const_iterator iter(keys.begin()); @@ -641,8 +684,6 @@ namespace MWRender mAnimationTimePtr[i].reset(new AnimationTime); mLightListCallback = new SceneUtil::LightListCallback; - - mUseAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); } Animation::~Animation() @@ -754,7 +795,8 @@ namespace MWRender addSingleAnimSource(kfname, baseModel); - if (mUseAdditionalSources) + static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); + if (useAdditionalSources) loadAllAnimationsInFolder(kfname, baseModel); } @@ -1934,6 +1976,12 @@ namespace MWRender mObjectRoot->accept(visitor); visitor.remove(); } + + if (SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) + { + AddSwitchCallbacksVisitor visitor; + mObjectRoot->accept(visitor); + } } Animation::AnimState::~AnimState() diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 47edbdc0b..153f2ead9 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -273,8 +273,6 @@ protected: osg::ref_ptr mLightListCallback; - bool mUseAdditionalSources; - const NodeMap& getNodeMap() const; /* Sets the appropriate animations on the bone groups based on priority. diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index ce6a27389..a3679e844 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -136,6 +137,7 @@ namespace MWRender mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setNodeMask(Mask_RenderToTexture); @@ -152,6 +154,8 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8ea4e3991..a7f9247f7 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -177,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setCullMask(Mask_Scene|Mask_SimpleWater|Mask_Terrain); + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object); camera->setNodeMask(Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; @@ -209,6 +210,8 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); @@ -377,7 +380,7 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); + computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object); mSceneRoot->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 920e09a69..4b36686f1 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -362,11 +362,16 @@ public: if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) { fov = mFov; - osg::RefMatrix* newProjectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); - cv->pushProjectionMatrix(newProjectionMatrix); + osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); + invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); + osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + viewMatrix->postMult(*newProjectionMatrix); + viewMatrix->postMult(*invertedOldMatrix); + cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); traverse(node, nv); - cv->popProjectionMatrix(); + cv->popModelViewMatrix(); } else traverse(node, nv); @@ -465,8 +470,12 @@ void NpcAnimation::updateNpcBase() bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); + std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); + defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + + std::string smodel = defaultSkeleton; + if (!is1stPerson && !isWerewolf & !mNpc->mModel.empty()) + smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); @@ -481,15 +490,13 @@ void NpcAnimation::updateNpcBase() if (smodel != base) addAnimSource(base, smodel); + if (smodel != defaultSkeleton && base != defaultSkeleton) + addAnimSource(defaultSkeleton, smodel); + addAnimSource(smodel, smodel); - if(!isWerewolf) - { - if(mNpc->mModel.length() > 0) - addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()), smodel); - if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\xargonian_swimkna.nif", smodel); - } + if(!isWerewolf && Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) + addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 882c9856f..ec1c4397b 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -71,6 +71,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 322c310a2..51a92a280 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -219,7 +220,7 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -233,7 +234,28 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(mSceneRoot); + int shadowCastingTraversalMask = Mask_Scene; + if (Settings::Manager::getBool("actor shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Actor; + if (Settings::Manager::getBool("player shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Player; + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Terrain; + + int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; + if (Settings::Manager::getBool("object shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Object; + + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + + for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) + globalDefines[itr->first] = itr->second; + + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. + mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); @@ -443,7 +465,7 @@ namespace MWRender osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(diffuse); - mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); + mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) @@ -491,6 +513,10 @@ namespace MWRender void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); + if (enabled) + mShadowManager->enableOutdoorMode(); + else + mShadowManager->enableIndoorMode(); } bool RenderingManager::toggleBorders() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 2a48097bd..3daf81071 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -53,13 +53,14 @@ namespace Fallback namespace SceneUtil { + class ShadowManager; class WorkQueue; class UnrefQueue; } namespace DetourNavigator { - class Navigator; + struct Navigator; struct Settings; } @@ -267,6 +268,7 @@ namespace MWRender TerrainStorage* mTerrainStorage; std::unique_ptr mSky; std::unique_ptr mEffectManager; + std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0ea0905a6..194646f2a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -1127,7 +1128,8 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana skyroot->setName("Sky Root"); // Assign empty program to specify we don't want shaders // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index d52c7c232..1d94b4bf9 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -33,25 +33,26 @@ namespace MWRender Mask_SimpleWater = (1<<7), Mask_Terrain = (1<<8), Mask_FirstPerson = (1<<9), + Mask_Object = (1<<10), // child of Sky - Mask_Sun = (1<<10), - Mask_WeatherParticles = (1<<11), + Mask_Sun = (1<<11), + Mask_WeatherParticles = (1<<12), // top level masks - Mask_Scene = (1<<12), - Mask_GUI = (1<<13), + Mask_Scene = (1<<13), + Mask_GUI = (1<<14), // Set on a ParticleSystem Drawable - Mask_ParticleSystem = (1<<14), + Mask_ParticleSystem = (1<<15), // Set on cameras within the main scene graph - Mask_RenderToTexture = (1<<15), + Mask_RenderToTexture = (1<<16), - Mask_PreCompile = (1<<16), + Mask_PreCompile = (1<<17), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<17) + Mask_Lighting = (1<<18) }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index f668b0820..376924d82 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -224,7 +225,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -263,6 +264,8 @@ public: mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setScene(osg::Node* scene) @@ -315,7 +318,7 @@ public: bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -341,6 +344,8 @@ public: mClipCullNode = new ClipCullNode; addChild(mClipCullNode); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setWaterLevel(float waterLevel) diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index f13f423b2..2a9b5ab97 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -79,6 +79,8 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + /* Start of tes3mp change (major) @@ -92,7 +94,21 @@ namespace MWScript MWWorld::Ptr itemPtr; if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() || packetOrigin != mwmp::CLIENT_CONSOLE) - itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr); + { + // Create a Ptr for the first added item to recover the item name later + itemPtr = *store.add(item, 1, ptr); + + if (itemPtr.getClass().getScript(itemPtr).empty()) + { + store.add(item, count - 1, ptr); + } + else + { + // Adding just one item per time to make sure there isn't a stack of scripted items + for (int i = 1; i < count; i++) + store.add(item, 1, ptr); + } + } /* End of tes3mp change (major) */ @@ -205,8 +221,13 @@ namespace MWScript std::string itemName; for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) + { if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) + { itemName = iter->getClass().getName(*iter); + break; + } + } /* Start of tes3mp change (major) diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 21b3b5587..0d5b1bf3b 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -16,7 +16,6 @@ #include "../mwworld/ptr.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/movement.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -168,12 +167,15 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isRunning = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isRunning && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); + runtime.push(stanceOn && (running || inair)); } }; @@ -184,11 +186,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isSneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isSneaking && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); + runtime.push(stanceOn && (sneaking || inair)); } }; diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index 37d811eb6..62b673dd2 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -2,9 +2,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 5294745a6..4a1d2aefa 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -4,7 +4,6 @@ #include #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 84805c70e..ef435cca9 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -2,9 +2,6 @@ #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - #include "../mwworld/containerstore.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwworld/actioneat.hpp b/apps/openmw/mwworld/actioneat.hpp index ce5330db7..db21ffa17 100644 --- a/apps/openmw/mwworld/actioneat.hpp +++ b/apps/openmw/mwworld/actioneat.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONEAT_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index da794bcd2..30f969193 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -1,7 +1,6 @@ #include "actionequip.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index f678037ce..f9866cd41 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -6,9 +6,6 @@ #include "../mwmechanics/disease.hpp" -#include "class.hpp" -#include "containerstore.hpp" - namespace MWWorld { ActionOpen::ActionOpen (const MWWorld::Ptr& container) diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 3bce25c6b..e36b971a2 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -2,8 +2,6 @@ #define GAME_MWWORLD_ACTIONOPEN_H #include "action.hpp" -#include "ptr.hpp" - namespace MWWorld { diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp index c23bf2900..7c4d7d2f4 100644 --- a/apps/openmw/mwworld/actionread.hpp +++ b/apps/openmw/mwworld/actionread.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONREAD_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 98fe8ee34..f7823daba 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -2,8 +2,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" + #include "../mwmechanics/actorutil.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actionsoulgem.hpp b/apps/openmw/mwworld/actionsoulgem.hpp index 0dd526657..6a8f220bc 100644 --- a/apps/openmw/mwworld/actionsoulgem.hpp +++ b/apps/openmw/mwworld/actionsoulgem.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONSOULGEM_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actiontake.hpp b/apps/openmw/mwworld/actiontake.hpp index b0a9b8247..fb8d2f4ce 100644 --- a/apps/openmw/mwworld/actiontake.hpp +++ b/apps/openmw/mwworld/actiontake.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONTAKE_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp index b88b168d8..01738a0bb 100644 --- a/apps/openmw/mwworld/actiontalk.hpp +++ b/apps/openmw/mwworld/actiontalk.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWWORLD_ACTIONTALK_H #define GAME_MWWORLD_ACTIONTALK_H -#include "ptr.hpp" #include "action.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp index 5ff91613f..5b0d429c4 100644 --- a/apps/openmw/mwworld/actiontrap.hpp +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -4,7 +4,6 @@ #include #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d773b756d..8cf6f2755 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -277,7 +277,9 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) - && cls1.getScript(ptr1) == cls2.getScript(ptr2) + // Items with scripts never stack + && cls1.getScript(ptr1).empty() + && cls2.getScript(ptr2).empty() // item that is already partly used up never stacks && (!cls1.hasItemHealth(ptr1) || ( @@ -362,7 +364,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); - if(script != "") + if (!script.empty()) { if (actorPtr == player) { diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 9853a9163..eb1440fb9 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -42,6 +42,7 @@ namespace MWWorld Player::Player (const ESM::NPC *player) : mCellStore(0), mLastKnownExteriorPosition(0,0,0), + mMarkedPosition(ESM::Position()), mMarkedCell(nullptr), mAutoMove(false), mForwardBackward(0), diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp index 500f86b1e..f749351ce 100644 --- a/apps/openmw/mwworld/recordcmp.hpp +++ b/apps/openmw/mwworld/recordcmp.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWWORLD_RECORDCMP_H #define OPENMW_MWWORLD_RECORDCMP_H -#include - #include #include diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index b8fcf09c0..8c40a4a99 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -7,7 +7,6 @@ #include "../mwscript/locals.hpp" #include -#include namespace SceneUtil { diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 00f5f98b8..0f1ae7298 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -30,7 +30,7 @@ namespace Loading namespace DetourNavigator { - class Navigator; + struct Navigator; class Water; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index cc20af51c..5c2195307 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -560,6 +560,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mFastForward(false) , mWeatherUpdateTime(mHoursBetweenWeatherChanges) , mTransitionFactor(0) + , mNightDayMode(Default) , mCurrentWeather(0) , mNextWeather(0) , mQueuedWeather(0) @@ -767,6 +768,14 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, updateWeatherTransitions(duration); } + bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; + if (isExterior && !isDay) + mNightDayMode = ExteriorNight; + else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) + mNightDayMode = InteriorDay; + else + mNightDayMode = Default; + if(!isExterior) { mRendering.setSkyEnabled(false); @@ -823,7 +832,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, } else { - theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -907,6 +916,11 @@ unsigned int WeatherManager::getWeatherID() const return mCurrentWeather; } +NightDayMode WeatherManager::getNightDayMode() const +{ + return mNightDayMode; +} + bool WeatherManager::useTorches(float hour) const { bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index cff3da226..2a97aab16 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -40,6 +40,13 @@ namespace MWWorld { class TimeStamp; + enum NightDayMode + { + Default = 0, + ExteriorNight = 1, + InteriorDay = 2 + }; + struct WeatherSetting { float mPreSunriseTime; @@ -289,6 +296,7 @@ namespace MWWorld void stopSounds(); float getWindSpeed() const; + NightDayMode getNightDayMode() const; /// Are we in an ash or blight storm? bool isInStorm() const; @@ -374,6 +382,7 @@ namespace MWWorld bool mFastForward; float mWeatherUpdateTime; float mTransitionFactor; + NightDayMode mNightDayMode; int mCurrentWeather; int mNextWeather; int mQueuedWeather; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8f81dd4a0..09ec6e316 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -45,7 +45,8 @@ #include #include -#include +#include +#include #include #include "../mwbase/environment.hpp" @@ -219,40 +220,21 @@ namespace MWWorld mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); - DetourNavigator::Settings navigatorSettings; - navigatorSettings.mBorderSize = Settings::Manager::getInt("border size", "Navigator"); - navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); - navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator"); - navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator"); - navigatorSettings.mDetailSampleMaxError = Settings::Manager::getFloat("detail sample max error", "Navigator"); - navigatorSettings.mMaxClimb = MWPhysics::sStepSizeUp; - navigatorSettings.mMaxSimplificationError = Settings::Manager::getFloat("max simplification error", "Navigator"); - navigatorSettings.mMaxSlope = MWPhysics::sMaxSlope; - navigatorSettings.mRecastScaleFactor = Settings::Manager::getFloat("recast scale factor", "Navigator"); - navigatorSettings.mSwimHeightScale = mSwimHeightScale; - navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator"); - navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); - navigatorSettings.mMaxPolys = Settings::Manager::getInt("max polygons per tile", "Navigator"); - navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator"); - navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator"); - navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); - navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); - navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); - navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); - navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); - navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); - navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); - navigatorSettings.mEnableWriteRecastMeshToFile = Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); - navigatorSettings.mEnableWriteNavMeshToFile = Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); - navigatorSettings.mRecastMeshPathPrefix = Settings::Manager::getString("recast mesh path prefix", "Navigator"); - navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator"); - navigatorSettings.mEnableRecastMeshFileNameRevision = Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); - navigatorSettings.mEnableNavMeshFileNameRevision = Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); - if (Settings::Manager::getBool("enable log", "Navigator")) - DetourNavigator::Log::instance().setSink(std::unique_ptr( - new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator")))); - DetourNavigator::RecastGlobalAllocator::init(); - mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); + if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager()) + { + navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp; + navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope; + navigatorSettings->mSwimHeightScale = mSwimHeightScale; + if (Settings::Manager::getBool("enable log", "Navigator")) + DetourNavigator::Log::instance().setSink(std::unique_ptr( + new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator")))); + DetourNavigator::RecastGlobalAllocator::init(); + mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings)); + } + else + { + mNavigator.reset(new DetourNavigator::NavigatorStub()); + } mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath, *mNavigator)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); @@ -1914,14 +1896,20 @@ namespace MWWorld } } - void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) + void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) { - mPhysics->setActorCollisionMode(ptr, enabled); + MWPhysics::Actor *physicActor = mPhysics->getActor(ptr); + if (physicActor && physicActor->getCollisionMode() != internal) + { + physicActor->enableCollisionMode(internal); + physicActor->enableCollisionBody(external); + } } bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr) { - return mPhysics->isActorCollisionEnabled(ptr); + MWPhysics::Actor *physicActor = mPhysics->getActor(ptr); + return physicActor && physicActor->getCollisionMode(); } bool World::toggleCollisionMode() @@ -2259,6 +2247,11 @@ namespace MWWorld return mWeatherManager->getWeatherID(); } + unsigned int World::getNightDayMode() const + { + return mWeatherManager->getNightDayMode(); + } + void World::changeWeather(const std::string& region, const unsigned int id) { mWeatherManager->changeWeather(region, id); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 18f1f3ac4..89bfe802d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -452,6 +452,8 @@ namespace MWWorld int getCurrentWeather() const override; + unsigned int getNightDayMode() const override; + int getMasserPhase() const override; int getSecundaPhase() const override; @@ -554,7 +556,7 @@ namespace MWWorld bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; - void setActorCollisionMode(const Ptr& ptr, bool enabled) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; bool toggleCollisionMode() override; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 907052882..12775035b 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp + detournavigator/tilecachedrecastmeshmanager.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index febbc0387..8e76b3154 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1,6 +1,6 @@ #include "operators.hpp" -#include +#include #include #include @@ -61,13 +61,14 @@ namespace mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; mSettings.mMaxPolys = 4096; - mNavigator.reset(new Navigator(mSettings)); + mNavigator.reset(new NavigatorImpl(mSettings)); } }; - TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_throw_exception) + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) @@ -76,11 +77,12 @@ namespace EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException); } - TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_throw_exception) + TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_return_empty) { mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp new file mode 100644 index 000000000..5275d9119 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -0,0 +1,239 @@ +#include "operators.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorTileCachedRecastMeshManagerTest : Test + { + Settings mSettings; + + DetourNavigatorTileCachedRecastMeshManagerTest() + { + mSettings.mBorderSize = 16; + mSettings.mCellSize = 0.2f; + mSettings.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mTileSize = 64; + mSettings.mTrianglesPerChunk = 256; + } + }; + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, has_tile_for_empty_should_return_false) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_FALSE(manager.hasTile(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) + { + const TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getRevision(), 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none) + { + TileCachedRecastMeshManager manager(mSettings); + std::size_t calls = 0; + manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); + EXPECT_EQ(calls, 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + EXPECT_TRUE(manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_THAT( + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), + ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), + TilePosition(1, -1), TilePosition(1, 0)) + ); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ( + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), + std::vector() + ); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const auto initialRevision = manager.getRevision(); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), initialRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_existing_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeAddRevision = manager.getRevision(); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeAddRevision); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_moved_object_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + const auto beforeUpdateRevision = manager.getRevision(); + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeUpdateRevision = manager.getRevision(); + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_existing_object_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeRemoveRevision = manager.getRevision(); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_absent_object_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const auto beforeRemoveRevision = manager.getRevision(); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); + } +} diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 7aa84d3b1..a997c9324 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -876,7 +876,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt result.append(info.absoluteFilePath()); break; case Qt::MatchEndsWith: - if (info.fileName().endsWith(fileName), Qt::CaseInsensitive) + if (info.fileName().endsWith(fileName, Qt::CaseInsensitive)) result.append(info.absoluteFilePath()); break; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 08662c282..e9590815a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -52,7 +52,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique ) add_component_dir (nif @@ -229,13 +229,14 @@ add_component_dir(detournavigator recastmeshmanager cachedrecastmeshmanager navmeshmanager - navigator + navigatorimpl asyncnavmeshupdater chunkytrimesh recastmesh tilecachedrecastmeshmanager recastmeshobject navmeshtilescache + settings ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui @@ -294,6 +295,7 @@ target_link_libraries(components ${OSGVIEWER_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} ${SDL2_LIBRARIES} diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 81b732b74..c02e3315b 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -81,21 +81,28 @@ namespace DetourNavigator return *this; } - OutputTransformIterator& operator ++(int) + OutputTransformIterator& operator ++() { - mImpl++; + ++mImpl.get(); return *this; } + OutputTransformIterator operator ++(int) + { + const auto copy = *this; + ++(*this); + return copy; + } + OutputTransformIterator& operator =(const osg::Vec3f& value) { - *mImpl = fromNavMeshCoordinates(mSettings, value); + *mImpl.get() = fromNavMeshCoordinates(mSettings, value); return *this; } private: - OutputIterator& mImpl; - const Settings& mSettings; + std::reference_wrapper mImpl; + std::reference_wrapper mSettings; }; inline void initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 351e0f9f8..a06d97c56 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -3,9 +3,9 @@ #include "findsmoothpath.hpp" #include "flags.hpp" -#include "navmeshmanager.hpp" #include "settings.hpp" -#include "settingsutils.hpp" +#include "objectid.hpp" +#include "navmeshcacheitem.hpp" namespace DetourNavigator { @@ -33,33 +33,28 @@ namespace DetourNavigator }; /** - * @brief Top level class of detournavigator componenet. Navigator allows to build a scene with navmesh and find + * @brief Top level interface of detournavigator component. Navigator allows to build a scene with navmesh and find * a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by * half extents. Each object has unique identifier and could be added, updated or removed. Water could be added once * for each world cell at given level of height. Navmesh builds asynchronously in separate threads. To start build * navmesh call update method. */ - class Navigator + struct Navigator { - public: - /** - * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. - * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. - */ - Navigator(const Settings& settings); + virtual ~Navigator() = default; /** * @brief addAgent should be called for each agent even if all of them has same half extents. * @param agentHalfExtents allows to setup bounding cylinder for each agent, for each different half extents * there is different navmesh. */ - void addAgent(const osg::Vec3f& agentHalfExtents); + virtual void addAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief removeAgent should be called for each agent even if all of them has same half extents * @param agentHalfExtents allows determine which agent to remove */ - void removeAgent(const osg::Vec3f& agentHalfExtents); + virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief addObject is used to add object represented by single btCollisionShape and btTransform. @@ -68,7 +63,7 @@ namespace DetourNavigator * @param transform allows to setup object geometry according to its world state. * @return true if object is added, false if there is already object with given id. */ - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + virtual bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes @@ -77,7 +72,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state * @return true if object is added, false if there is already object with given id */ - bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + virtual bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief addObject is used to add doors. @@ -86,7 +81,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is added, false if there is already object with given id. */ - bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + virtual bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -95,7 +90,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -104,7 +99,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -113,14 +108,14 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief removeObject to make it no more available at the scene. * @param id is used to find object. * @return true if object is removed, false if there is no object with given id. */ - bool removeObject(const ObjectId id); + virtual bool removeObject(const ObjectId id) = 0; /** * @brief addWater is used to set water level at given world cell. @@ -132,26 +127,26 @@ namespace DetourNavigator * at least single object is added to the scene, false if there is already water for given cell or there is no * any other objects. */ - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, - const btTransform& transform); + virtual bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform) = 0; /** * @brief removeWater to make it no more available at the scene. * @param cellPosition allows to find cell. * @return true if there was water at given cell. */ - bool removeWater(const osg::Vec2i& cellPosition); + virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; /** * @brief update start background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ - void update(const osg::Vec3f& playerPosition); + virtual void update(const osg::Vec3f& playerPosition) = 0; /** * @brief wait locks thread until all tiles are updated from last update call. */ - void wait(); + virtual void wait() = 0; /** * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. @@ -175,30 +170,28 @@ namespace DetourNavigator >::value, "out is not an OutputIterator" ); - const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); - return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents), - toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, - mSettings, out); + const auto navMesh = getNavMesh(agentHalfExtents); + if (!navMesh) + return out; + const auto settings = getSettings(); + return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(settings, agentHalfExtents), + toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, end), includeFlags, + settings, out); } + /** + * @brief getNavMesh returns navmesh for specific agent half extents + * @return navmesh + */ + virtual SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const = 0; + /** * @brief getNavMeshes returns all current navmeshes * @return map of agent half extents to navmesh */ - std::map getNavMeshes() const; + virtual std::map getNavMeshes() const = 0; - const Settings& getSettings() const; - - private: - Settings mSettings; - NavMeshManager mNavMeshManager; - std::map mAgents; - std::unordered_map mAvoidIds; - std::unordered_map mWaterIds; - - void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); - void updateWaterShapeId(const ObjectId id, const ObjectId waterId); - void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map& ids); + virtual Settings getSettings() const = 0; }; } diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigatorimpl.cpp similarity index 64% rename from components/detournavigator/navigator.cpp rename to components/detournavigator/navigatorimpl.cpp index 73537ff6f..1b83769f4 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -1,4 +1,4 @@ -#include "navigator.hpp" +#include "navigatorimpl.hpp" #include "debug.hpp" #include "settingsutils.hpp" @@ -6,19 +6,19 @@ namespace DetourNavigator { - Navigator::Navigator(const Settings& settings) + NavigatorImpl::NavigatorImpl(const Settings& settings) : mSettings(settings) , mNavMeshManager(mSettings) { } - void Navigator::addAgent(const osg::Vec3f& agentHalfExtents) + void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents) { ++mAgents[agentHalfExtents]; mNavMeshManager.addAgent(agentHalfExtents); } - void Navigator::removeAgent(const osg::Vec3f& agentHalfExtents) + void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents) { const auto it = mAgents.find(agentHalfExtents); if (it == mAgents.end() || --it->second) @@ -27,12 +27,12 @@ namespace DetourNavigator mNavMeshManager.reset(agentHalfExtents); } - bool Navigator::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); } - bool Navigator::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = addObject(id, shapes.mShape, transform); if (shapes.mAvoid) @@ -47,7 +47,7 @@ namespace DetourNavigator return result; } - bool Navigator::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { if (addObject(id, static_cast(shapes), transform)) { @@ -61,12 +61,12 @@ namespace DetourNavigator return false; } - bool Navigator::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); } - bool Navigator::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = updateObject(id, shapes.mShape, transform); if (shapes.mAvoid) @@ -81,12 +81,12 @@ namespace DetourNavigator return result; } - bool Navigator::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { return updateObject(id, static_cast(shapes), transform); } - bool Navigator::removeObject(const ObjectId id) + bool NavigatorImpl::removeObject(const ObjectId id) { bool result = mNavMeshManager.removeObject(id); const auto avoid = mAvoidIds.find(id); @@ -99,50 +99,55 @@ namespace DetourNavigator return result; } - bool Navigator::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, const btTransform& transform) { return mNavMeshManager.addWater(cellPosition, cellSize, btTransform(transform.getBasis(), btVector3(transform.getOrigin().x(), transform.getOrigin().y(), level))); } - bool Navigator::removeWater(const osg::Vec2i& cellPosition) + bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) { return mNavMeshManager.removeWater(cellPosition); } - void Navigator::update(const osg::Vec3f& playerPosition) + void NavigatorImpl::update(const osg::Vec3f& playerPosition) { for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } - void Navigator::wait() + void NavigatorImpl::wait() { mNavMeshManager.wait(); } - std::map Navigator::getNavMeshes() const + SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const osg::Vec3f& agentHalfExtents) const + { + return mNavMeshManager.getNavMesh(agentHalfExtents); + } + + std::map NavigatorImpl::getNavMeshes() const { return mNavMeshManager.getNavMeshes(); } - const Settings& Navigator::getSettings() const + Settings NavigatorImpl::getSettings() const { return mSettings; } - void Navigator::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) + void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) { updateId(id, avoidId, mWaterIds); } - void Navigator::updateWaterShapeId(const ObjectId id, const ObjectId waterId) + void NavigatorImpl::updateWaterShapeId(const ObjectId id, const ObjectId waterId) { updateId(id, waterId, mWaterIds); } - void Navigator::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) + void NavigatorImpl::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) { auto inserted = ids.insert(std::make_pair(id, updateId)); if (!inserted.second) diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp new file mode 100644 index 000000000..975055984 --- /dev/null +++ b/components/detournavigator/navigatorimpl.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H + +#include "navigator.hpp" +#include "navmeshmanager.hpp" + +namespace DetourNavigator +{ + class NavigatorImpl final : public Navigator + { + public: + /** + * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. + * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. + */ + NavigatorImpl(const Settings& settings); + + void addAgent(const osg::Vec3f& agentHalfExtents) override; + + void removeAgent(const osg::Vec3f& agentHalfExtents) override; + + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; + + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; + + bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; + + bool removeObject(const ObjectId id) override; + + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform) override; + + bool removeWater(const osg::Vec2i& cellPosition) override; + + void update(const osg::Vec3f& playerPosition) override; + + void wait() override; + + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; + + std::map getNavMeshes() const override; + + Settings getSettings() const override; + + private: + Settings mSettings; + NavMeshManager mNavMeshManager; + std::map mAgents; + std::unordered_map mAvoidIds; + std::unordered_map mWaterIds; + + void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); + void updateWaterShapeId(const ObjectId id, const ObjectId waterId); + void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map& ids); + }; +} + +#endif diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp new file mode 100644 index 000000000..5d82d2f59 --- /dev/null +++ b/components/detournavigator/navigatorstub.hpp @@ -0,0 +1,83 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H + +#include "navigator.hpp" + +namespace DetourNavigator +{ + struct NavigatorStub final : public Navigator + { + NavigatorStub() = default; + + void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + + void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + + bool addObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override + { + return false; + } + + bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool addObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool removeObject(const ObjectId /*id*/) override + { + return false; + } + + bool addWater(const osg::Vec2i& /*cellPosition*/, const int /*cellSize*/, const btScalar /*level*/, + const btTransform& /*transform*/) override + { + return false; + } + + bool removeWater(const osg::Vec2i& /*cellPosition*/) override + { + return false; + } + + void update(const osg::Vec3f& /*playerPosition*/) override {} + + void wait() override {} + + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override + { + return SharedNavMeshCacheItem(); + } + + std::map getNavMeshes() const override + { + return std::map(); + } + + Settings getSettings() const override + { + return Settings {}; + } + }; +} + +#endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 66bf39aaf..aa2d62184 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -44,9 +44,11 @@ namespace DetourNavigator bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - if (!mRecastMeshManager.updateObject(id, transform, areaType)) + const auto changedTiles = mRecastMeshManager.updateObject(id, shape, transform, areaType); + if (changedTiles.empty()) return false; - addChangedTiles(shape, transform, ChangeType::update); + for (const auto& tile : changedTiles) + addChangedTile(tile, ChangeType::update); return true; } @@ -133,7 +135,13 @@ namespace DetourNavigator else lastPlayerTile->second = playerTile; std::map tilesToPost; - const auto& cached = getCached(agentHalfExtents); + const auto cached = getCached(agentHalfExtents); + if (!cached) + { + std::ostringstream stream; + stream << "Agent with half extents is not found: " << agentHalfExtents; + throw InvalidArgument(stream.str()); + } const auto changedTiles = mChangedTiles.find(agentHalfExtents); { const auto locked = cached.lock(); @@ -218,13 +226,11 @@ namespace DetourNavigator } } - const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const + SharedNavMeshCacheItem NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const { const auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) return cached->second; - std::ostringstream stream; - stream << "Agent with half extents is not found: " << agentHalfExtents; - throw InvalidArgument(stream.str()); + return SharedNavMeshCacheItem(); } } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f44cdd251..ae006da73 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -67,7 +67,7 @@ namespace DetourNavigator void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); - const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const; + SharedNavMeshCacheItem getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp new file mode 100644 index 000000000..f584e48f9 --- /dev/null +++ b/components/detournavigator/settings.cpp @@ -0,0 +1,47 @@ +#include "settings.hpp" + +#include + +#include + +namespace DetourNavigator +{ + boost::optional makeSettingsFromSettingsManager() + { + if (!::Settings::Manager::getBool("enable", "Navigator")) + return boost::optional(); + + Settings navigatorSettings; + + navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); + navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); + navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); + navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); + navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); + navigatorSettings.mMaxClimb = 0; + navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); + navigatorSettings.mMaxSlope = 0; + navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); + navigatorSettings.mSwimHeightScale = 0; + navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); + navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); + navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); + navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); + navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); + navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); + navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); + navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); + navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); + navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); + navigatorSettings.mMaxSmoothPathSize = static_cast(::Settings::Manager::getInt("max smooth path size", "Navigator")); + navigatorSettings.mTrianglesPerChunk = static_cast(::Settings::Manager::getInt("triangles per chunk", "Navigator")); + navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); + navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); + navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); + navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); + navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); + navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); + + return navigatorSettings; + } +} diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 3e537c2fb..0316092a0 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -1,41 +1,45 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H +#include + #include namespace DetourNavigator { struct Settings { - bool mEnableWriteRecastMeshToFile; - bool mEnableWriteNavMeshToFile; - bool mEnableRecastMeshFileNameRevision; - bool mEnableNavMeshFileNameRevision; - float mCellHeight; - float mCellSize; - float mDetailSampleDist; - float mDetailSampleMaxError; - float mMaxClimb; - float mMaxSimplificationError; - float mMaxSlope; - float mRecastScaleFactor; - float mSwimHeightScale; - int mBorderSize; - int mMaxEdgeLen; - int mMaxNavMeshQueryNodes; - int mMaxPolys; - int mMaxVertsPerPoly; - int mRegionMergeSize; - int mRegionMinSize; - int mTileSize; - std::size_t mAsyncNavMeshUpdaterThreads; - std::size_t mMaxNavMeshTilesCacheSize; - std::size_t mMaxPolygonPathSize; - std::size_t mMaxSmoothPathSize; - std::size_t mTrianglesPerChunk; + bool mEnableWriteRecastMeshToFile = false; + bool mEnableWriteNavMeshToFile = false; + bool mEnableRecastMeshFileNameRevision = false; + bool mEnableNavMeshFileNameRevision = false; + float mCellHeight = 0; + float mCellSize = 0; + float mDetailSampleDist = 0; + float mDetailSampleMaxError = 0; + float mMaxClimb = 0; + float mMaxSimplificationError = 0; + float mMaxSlope = 0; + float mRecastScaleFactor = 0; + float mSwimHeightScale = 0; + int mBorderSize = 0; + int mMaxEdgeLen = 0; + int mMaxNavMeshQueryNodes = 0; + int mMaxPolys = 0; + int mMaxVertsPerPoly = 0; + int mRegionMergeSize = 0; + int mRegionMinSize = 0; + int mTileSize = 0; + std::size_t mAsyncNavMeshUpdaterThreads = 0; + std::size_t mMaxNavMeshTilesCacheSize = 0; + std::size_t mMaxPolygonPathSize = 0; + std::size_t mMaxSmoothPathSize = 0; + std::size_t mTrianglesPerChunk = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; }; + + boost::optional makeSettingsFromSettingsManager(); } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index d624800e9..b878c2d3e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -15,48 +15,57 @@ namespace DetourNavigator bool result = false; auto& tilesPositions = mObjectsTilesPositions[id]; const auto border = getBorderSize(mSettings); - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) - { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + { + auto tiles = mTiles.lock(); + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds))).first; - } - if (tile->second.addObject(id, shape, transform, areaType)) - { - tilesPositions.push_back(tilePosition); - result = true; - } - }); + if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + tilesPositions.insert(tilePosition); + result = true; + } + }); + } if (result) ++mRevision; return result; } - bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, - const AreaType areaType) + std::vector TileCachedRecastMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) - return false; - bool result = false; + return std::vector(); + auto& currentTiles = object->second; + const auto border = getBorderSize(mSettings); + std::vector changedTiles; + std::set newTiles; { - const auto tiles = mTiles.lock(); - for (const auto& tilePosition : object->second) + auto tiles = mTiles.lock(); + const auto onTilePosition = [&] (const TilePosition& tilePosition) { - const auto tile = tiles->find(tilePosition); - if (tile != tiles->end()) - result = tile->second.updateObject(id, transform, areaType) || result; - } + if (currentTiles.count(tilePosition)) + { + newTiles.insert(tilePosition); + if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + changedTiles.push_back(tilePosition); + } + else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + newTiles.insert(tilePosition); + changedTiles.push_back(tilePosition); + } + }; + getTilesPositions(shape, transform, mSettings, onTilePosition); + for (const auto& tile : currentTiles) + if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + changedTiles.push_back(tile); + std::swap(currentTiles, newTiles); } - if (result) + if (!changedTiles.empty()) ++mRevision; - return result; + return changedTiles; } boost::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) @@ -65,17 +74,14 @@ namespace DetourNavigator if (object == mObjectsTilesPositions.end()) return boost::none; boost::optional result; - for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) - continue; - const auto tileResult = tile->second.removeObject(id); - if (tile->second.isEmpty()) - tiles->erase(tile); - if (tileResult && !result) - result = tileResult; + auto tiles = mTiles.lock(); + for (const auto& tilePosition : object->second) + { + const auto removed = removeTile(id, tilePosition, tiles.get()); + if (removed && !result) + result = removed; + } } if (result) ++mRevision; @@ -172,4 +178,38 @@ namespace DetourNavigator { return mRevision; } + + bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles) + { + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + { + auto tileBounds = makeTileBounds(mSettings, tilePosition); + tileBounds.mMin -= osg::Vec2f(border, border); + tileBounds.mMax += osg::Vec2f(border, border); + tile = tiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; + } + return tile->second.addObject(id, shape, transform, areaType); + } + + bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + return tile != tiles.end() && tile->second.updateObject(id, transform, areaType); + } + + boost::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, + const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + return boost::optional(); + const auto tileResult = tile->second.removeObject(id); + if (tile->second.isEmpty()) + tiles.erase(tile); + return tileResult; + } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f82ef85c5..a3d0ae1e5 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace DetourNavigator { @@ -19,7 +20,8 @@ namespace DetourNavigator bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); + std::vector updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType); boost::optional removeObject(const ObjectId id); @@ -43,9 +45,19 @@ namespace DetourNavigator private: const Settings& mSettings; Misc::ScopeGuarded> mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; + + bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles); + + bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, + const TilePosition& tilePosition, std::map& tiles); + + boost::optional removeTile(const ObjectId id, const TilePosition& tilePosition, + std::map& tiles); }; } diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 335863c32..6e41ba302 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -48,6 +48,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + mTargetActorId = -1; esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); @@ -67,6 +68,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + mTargetActorId = -1; esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index 7174ae888..01aeb2fc1 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_CONSTANTS_H #define OPENMW_CONSTANTS_H +#include + namespace Constants { @@ -22,6 +24,9 @@ const float GravityConst = 8.96f; // Size of one exterior cell in game units const int CellSizeInUnits = 8192; +// A label to mark night/day visual switches +const std::string NightDayLabel = "NightDaySwitch"; + } #endif diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 4cb0564b1..114a77b10 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -106,6 +106,11 @@ namespace Misc return Locked(*mMutex, *mValue); } + operator bool() const + { + return static_cast(mValue); + } + private: std::shared_ptr mMutex; std::shared_ptr mValue; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 1b064c85c..a2dad247d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -11,9 +11,11 @@ // resource #include +#include #include #include #include +#include // particle #include @@ -432,8 +434,23 @@ namespace NifOsg osg::ref_ptr node; osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED; + // TODO: it is unclear how to handle transformations of LOD and Switch nodes and controllers for them. switch (nifNode->recType) { + case Nif::RC_NiLODNode: + { + const Nif::NiLODNode* niLodNode = static_cast(nifNode); + node = handleLodNode(niLodNode); + dataVariance = osg::Object::STATIC; + break; + } + case Nif::RC_NiSwitchNode: + { + const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); + node = handleSwitchNode(niSwitchNode); + dataVariance = osg::Object::STATIC; + break; + } case Nif::RC_NiTriShape: case Nif::RC_NiAutoNormalParticles: case Nif::RC_NiRotatingParticles: @@ -595,31 +612,6 @@ namespace NifOsg if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); - if (nifNode->recType == Nif::RC_NiLODNode) - { - const Nif::NiLODNode* niLodNode = static_cast(nifNode); - osg::ref_ptr lod = handleLodNode(niLodNode); - node->addChild(lod); // unsure if LOD should be above or below this node's transform - node = lod; - } - - if (nifNode->recType == Nif::RC_NiSwitchNode) - { - const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); - osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); - node->addChild(switchNode); - const Nif::NodeList &children = niSwitchNode->children; - for(size_t i = 0;i < children.length();++i) - { - if(!children[i].empty()) - handleNode(children[i].getPtr(), switchNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); - } - - // show only first child by default - switchNode->setSingleChildOn(0); - return switchNode; - } - const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { @@ -638,6 +630,16 @@ namespace NifOsg } } + if (nifNode->recType == Nif::RC_NiSwitchNode) + { + // show only first child by default + node->asSwitch()->setSingleChildOn(0); + + const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); + if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) + rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); + } + return node; } @@ -1120,7 +1122,7 @@ namespace NifOsg const Nif::NodeList &bones = skin->bones; for(size_t i = 0;i < bones.length();i++) { - std::string boneName = bones[i].getPtr()->name; + std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); SceneUtil::RigGeometry::BoneInfluence influence; const std::vector &weights = data->bones[i].weights; @@ -1282,6 +1284,7 @@ namespace NifOsg boundTextures.clear(); } + // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly. for (int i=0; itextures[i].inUse) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index e102653b9..cb6a12c87 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -399,7 +399,7 @@ namespace SceneUtil return false; } - if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) + if (!(cv->getTraversalMask() & mLightManager->getLightingMask())) return false; // Possible optimizations: diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp new file mode 100644 index 000000000..64c2e6e55 --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -0,0 +1,3180 @@ +/* This file is based on OpenSceneGraph's src/osgShadow/ViewDependentShadowMap.cpp. + * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. + * The original copyright notice is listed below. + */ + +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#include "mwshadowtechnique.hpp" + +#include +#include +#include +#include + +#include + +using namespace osgShadow; +using namespace SceneUtil; + +////////////////////////////////////////////////////////////////// +// fragment shader +// +#if 0 +static const char fragmentShaderSource_withBaseTexture[] = + "uniform sampler2D baseTexture; \n" + "uniform sampler2DShadow shadowTexture; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture, gl_TexCoord[1] ).r ); \n" + " gl_FragColor = color; \n" + "} \n"; +#else +static const char fragmentShaderSource_withBaseTexture[] = + "uniform sampler2D baseTexture; \n" + "uniform int baseTextureUnit; \n" + "uniform sampler2DShadow shadowTexture0; \n" + "uniform int shadowTextureUnit0; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r ); \n" + " gl_FragColor = color; \n" + "} \n"; + +static const char fragmentShaderSource_withBaseTexture_twoShadowMaps[] = + "uniform sampler2D baseTexture; \n" + "uniform int baseTextureUnit; \n" + "uniform sampler2DShadow shadowTexture0; \n" + "uniform int shadowTextureUnit0; \n" + "uniform sampler2DShadow shadowTexture1; \n" + "uniform int shadowTextureUnit1; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" + " float shadow0 = shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r; \n" + " float shadow1 = shadow2DProj( shadowTexture1, gl_TexCoord[shadowTextureUnit1] ).r; \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow0*shadow1 ); \n" + " gl_FragColor = color; \n" + "} \n"; +#endif + +std::string debugVertexShaderSource = "void main(void){gl_Position = gl_Vertex; gl_TexCoord[0]=gl_MultiTexCoord0;}"; +std::string debugFragmentShaderSource = + "uniform sampler2D texture; \n" + " \n" + "void main(void) \n" + "{ \n" +#if 1 + " float f = texture2D(texture, gl_TexCoord[0].xy).r; \n" + " \n" + " f = 256.0 * f; \n" + " float fC = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fS = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fH = floor( f ) / 256.0; \n" + " \n" + " fS *= 0.5; \n" + " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" + " \n" + " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" + " abs( fC - 0.333333 ), \n" + " abs( fC - 0.666667 ) ); \n" + " \n" + " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" + " \n" + " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" + " fMax = 1.0 / fMax; \n" + " \n" + " vec3 color = fMax * rgb; \n" + " \n" + " gl_FragColor = vec4( fS + fH * color, 1 ); \n" +#else + " gl_FragColor = texture2D(texture, gl_TexCoord[0].xy); \n" +#endif + "} \n"; + +std::string debugFrustumVertexShaderSource = "varying float depth; uniform mat4 transform; void main(void){gl_Position = transform * gl_Vertex; depth = gl_Position.z / gl_Position.w;}"; +std::string debugFrustumFragmentShaderSource = + "varying float depth; \n" + " \n" + "void main(void) \n" + "{ \n" +#if 1 + " float f = depth; \n" + " \n" + " f = 256.0 * f; \n" + " float fC = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fS = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fH = floor( f ) / 256.0; \n" + " \n" + " fS *= 0.5; \n" + " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" + " \n" + " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" + " abs( fC - 0.333333 ), \n" + " abs( fC - 0.666667 ) ); \n" + " \n" + " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" + " \n" + " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" + " fMax = 1.0 / fMax; \n" + " \n" + " vec3 color = fMax * rgb; \n" + " \n" + " gl_FragColor = vec4( fS + fH * color, 1 ); \n" +#else + " gl_FragColor = vec4(0.0, 0.0, 1.0, 0.0); \n" +#endif + "} \n"; + + +template +class RenderLeafTraverser : public T +{ +public: + + RenderLeafTraverser() + { + } + + void traverse(const osgUtil::RenderStage* rs) + { + traverse(static_cast(rs)); + } + + void traverse(const osgUtil::RenderBin* renderBin) + { + const osgUtil::RenderBin::RenderBinList& rbl = renderBin->getRenderBinList(); + for(osgUtil::RenderBin::RenderBinList::const_iterator itr = rbl.begin(); + itr != rbl.end(); + ++itr) + { + traverse(itr->second.get()); + } + + const osgUtil::RenderBin::RenderLeafList& rll = renderBin->getRenderLeafList(); + for(osgUtil::RenderBin::RenderLeafList::const_iterator itr = rll.begin(); + itr != rll.end(); + ++itr) + { + handle(*itr); + } + + const osgUtil::RenderBin::StateGraphList& rgl = renderBin->getStateGraphList(); + for(osgUtil::RenderBin::StateGraphList::const_iterator itr = rgl.begin(); + itr != rgl.end(); + ++itr) + { + traverse(*itr); + } + + } + + void traverse(const osgUtil::StateGraph* stateGraph) + { + const osgUtil::StateGraph::ChildList& cl = stateGraph->_children; + for(osgUtil::StateGraph::ChildList::const_iterator itr = cl.begin(); + itr != cl.end(); + ++itr) + { + traverse(itr->second.get()); + } + + const osgUtil::StateGraph::LeafList& ll = stateGraph->_leaves; + for(osgUtil::StateGraph::LeafList::const_iterator itr = ll.begin(); + itr != ll.end(); + ++itr) + { + handle(itr->get()); + } + } + + inline void handle(const osgUtil::RenderLeaf* renderLeaf) + { + this->operator()(renderLeaf); + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// VDSMCameraCullCallback +// +class VDSMCameraCullCallback : public osg::NodeCallback +{ + public: + + VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope); + + virtual void operator()(osg::Node*, osg::NodeVisitor* nv); + + osg::RefMatrix* getProjectionMatrix() { return _projectionMatrix.get(); } + osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); } + + protected: + + MWShadowTechnique* _vdsm; + osg::ref_ptr _projectionMatrix; + osg::ref_ptr _renderStage; + osg::Polytope _polytope; +}; + +VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope): + _vdsm(vdsm), + _polytope(polytope) +{ +} + +void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + osgUtil::CullVisitor* cv = static_cast(nv); + osg::Camera* camera = node->asCamera(); + OSG_INFO<<"VDSMCameraCullCallback::operator()(osg::Node* "<getProjectionCullingStack().back(); + + cs.setFrustum(_polytope); + + cv->pushCullingSet(); + } +#endif + if (_vdsm->getShadowedScene()) + { + _vdsm->getShadowedScene()->osg::Group::traverse(*nv); + } +#if 1 + if (!_polytope.empty()) + { + OSG_INFO<<"Popping custom Polytope"<popCullingSet(); + } +#endif + + _renderStage = cv->getCurrentRenderBin()->getStage(); + + OSG_INFO<<"VDSM second : _renderStage = "<<_renderStage<getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + // make sure that the near plane is computed correctly. + cv->computeNearPlane(); + + osg::Matrixd projection = *(cv->getProjectionMatrix()); + + OSG_INFO<<"RTT Projection matrix "<setProjectionMatrix(projection); + } + + _projectionMatrix = cv->getProjectionMatrix(); +} + +MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) +{ + setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); + + pushViewport(viewport); + pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); + pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); + + setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) +{ + if (isCulled(node)) return; + + // push the culling mode. + pushCurrentMask(); + + traverse(node); + + // pop the culling mode. + popCurrentMask(); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) +{ + if (isCulled(drawable)) return; + + // push the culling mode. + pushCurrentMask(); + + updateBound(drawable.getBoundingBox()); + + // pop the culling mode. + popCurrentMask(); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(Terrain::QuadTreeWorld & quadTreeWorld) +{ + // For now, just expand the bounds fully as terrain will fill them up and possible ways to detect which terrain definitely won't cast shadows aren't implemented. + + update(osg::Vec3(-1.0, -1.0, 0.0)); + update(osg::Vec3(1.0, 1.0, 0.0)); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) +{ + OSG_INFO << "Warning Billboards not yet supported" << std::endl; + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Projection&) +{ + // projection nodes won't affect a shadow map so their subgraphs should be ignored + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform) +{ + if (isCulled(transform)) return; + + // push the culling mode. + pushCurrentMask(); + + // absolute transforms won't affect a shadow map so their subgraphs should be ignored. + if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) + { + osg::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); + transform.computeLocalToWorldMatrix(*matrix, this); + pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); + + traverse(transform); + + popModelViewMatrix(); + } + + // pop the culling mode. + popCurrentMask(); + +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Camera&) +{ + // camera nodes won't affect a shadow map so their subgraphs should be ignored + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::updateBound(const osg::BoundingBox& bb) +{ + if (!bb.valid()) return; + + const osg::Matrix& matrix = *getModelViewMatrix() * *getProjectionMatrix(); + + update(bb.corner(0) * matrix); + update(bb.corner(1) * matrix); + update(bb.corner(2) * matrix); + update(bb.corner(3) * matrix); + update(bb.corner(4) * matrix); + update(bb.corner(5) * matrix); + update(bb.corner(6) * matrix); + update(bb.corner(7) * matrix); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::update(const osg::Vec3& v) +{ + if (v.z()<-1.0f) + { + //OSG_NOTICE<<"discarding("<1.0f) x = 1.0f; + float y = v.y(); + if (y<-1.0f) y = -1.0f; + if (y>1.0f) y = 1.0f; + _bb.expandBy(osg::Vec3(x, y, v.z())); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// LightData +// +MWShadowTechnique::LightData::LightData(MWShadowTechnique::ViewDependentData* vdd): + _viewDependentData(vdd), + directionalLight(false) +{ +} + +void MWShadowTechnique::LightData::setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix) +{ + lightMatrix = lm; + light = l; + + lightPos = light->getPosition(); + directionalLight = (light->getPosition().w()== 0.0); + if (directionalLight) + { + lightPos3.set(0.0, 0.0, 0.0); // directional light has no destinct position + lightDir.set(-lightPos.x(), -lightPos.y(), -lightPos.z()); + lightDir.normalize(); + OSG_INFO<<" Directional light, lightPos="<setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + + //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); + _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); + + //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + + // Now we are using Depth Clamping, we want to not cull things on the wrong side of the near plane. + // When the near and far planes are computed, OSG always culls anything on the wrong side of the near plane, even if it's told not to. + // Even if that weren't an issue, the near plane can't go past any shadow receivers or the depth-clamped fragments which ended up on the near plane can't cast shadows on those receivers. + // Unfortunately, this change will make shadows have less depth precision when there are no casters outside the view frustum. + // TODO: Find a better solution. E.g. detect when there are no casters outside the view frustum, write a new cull visitor that does all the wacky things we'd need it to. + _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + + // switch off small feature culling as this can cull out geometry that will still be large enough once perspective correction takes effect. + _camera->setCullingMode(_camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING); + + // set viewport + _camera->setViewport(0,0,textureSize.x(),textureSize.y()); + + + if (debug) + { + // clear just the depth buffer + _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + // render after the main camera + _camera->setRenderOrder(osg::Camera::POST_RENDER); + + // attach the texture and use it as the color buffer. + //_camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); + _camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); + } + else + { + // clear the depth and colour bufferson each clear. + _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + // set the camera to render before the main camera. + _camera->setRenderOrder(osg::Camera::PRE_RENDER); + + // tell the camera to use OpenGL frame buffer object where supported. + _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + // attach the texture and use it as the color buffer. + _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); + //_camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); + } +} + +void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const +{ + OSG_INFO<<"MWShadowTechnique::ShadowData::releaseGLObjects"<releaseGLObjects(state); + _camera->releaseGLObjects(state); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// Frustum +// +MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): + corners(8), + faces(6), + edges(12) +{ + projectionMatrix = *(cv->getProjectionMatrix()); + modelViewMatrix = *(cv->getModelViewMatrix()); + + OSG_INFO<<"Projection matrix "<getComputeNearFarMode()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + osg::Matrix::value_type zNear = osg::maximum(cv->getCalculatedNearPlane(),minZNear); + osg::Matrix::value_type zFar = osg::minimum(cv->getCalculatedFarPlane(),maxZFar); + + cv->clampProjectionMatrix(projectionMatrix, zNear, zFar); + + OSG_INFO<<"zNear = "<releaseGLObjects(state); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// MWShadowTechnique +// +MWShadowTechnique::MWShadowTechnique(): + ShadowTechnique(), + _enableShadows(false), + _debugHud(nullptr) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; +} + +MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): + ShadowTechnique(vdsm,copyop) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; +} + +MWShadowTechnique::~MWShadowTechnique() +{ +} + + +void MWShadowTechnique::init() +{ + if (!_shadowedScene) return; + + OSG_INFO<<"MWShadowTechnique::init()"<getShadowSettings()->getNumShadowMapsPerLight()); +} + +void SceneUtil::MWShadowTechnique::disableDebugHUD() +{ + _debugHud = nullptr; +} + +void SceneUtil::MWShadowTechnique::setSplitPointUniformLogarithmicRatio(double ratio) +{ + _splitPointUniformLogRatio = ratio; +} + +void SceneUtil::MWShadowTechnique::setSplitPointDeltaBias(double bias) +{ + _splitPointDeltaBias = bias; +} + +void SceneUtil::MWShadowTechnique::setPolygonOffset(float factor, float units) +{ + _polygonOffsetFactor = factor; + _polygonOffsetUnits = units; + + if (_polygonOffset) + { + _polygonOffset->setFactor(factor); + _polygonOffset->setUnits(units); + } +} + +void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() +{ + _useFrontFaceCulling = true; + + if (_shadowCastingStateSet) + _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); +} + +void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() +{ + _useFrontFaceCulling = false; + + if (_shadowCastingStateSet) + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); +} + +void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) +{ + // This can't be part of the constructor as OSG mandates that there be a trivial constructor available + + _castingProgram = new osg::Program(); + + _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); + _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) +{ + return new ViewDependentData(this); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv) +{ + OpenThreads::ScopedLock lock(_viewDependentDataMapMutex); + ViewDependentDataMap::iterator itr = _viewDependentDataMap.find(cv); + if (itr!=_viewDependentDataMap.end()) return itr->second.get(); + + osg::ref_ptr vdd = createViewDependentData(cv); + _viewDependentDataMap[cv] = vdd; + return vdd.release(); +} + +void MWShadowTechnique::update(osg::NodeVisitor& nv) +{ + OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<osg::Group::traverse(nv); +} + +void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) +{ + if (!_enableShadows) + { + _shadowedScene->osg::Group::traverse(cv); + return; + } + + OSG_INFO<osg::Group::traverse(cv); + return; + } + + ViewDependentData* vdd = getViewDependentData(&cv); + + if (!vdd) + { + OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); + return; + } + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getMaximumShadowMapDistance(),maxZFar); + if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); + + //OSG_NOTICE<<"maxZFar "< vertexArray = new osg::Vec3Array(); + for (osg::Vec3d &vertex : frustum.corners) + vertexArray->push_back((osg::Vec3)vertex); + _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); + } + + double reducedNear, reducedFar; + if (cv.getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + reducedNear = osg::maximum(cv.getCalculatedNearPlane(), minZNear); + reducedFar = osg::minimum(cv.getCalculatedFarPlane(), maxZFar); + } + else + { + reducedNear = minZNear; + reducedFar = maxZFar; + } + + // return compute near far mode back to it's original settings + cv.setComputeNearFarMode(cachedNearFarMode); + + OSG_INFO<<"frustum.eye="<1 &&*/ _shadowedScene->getCastsShadowTraversalMask()!=0xffffffff) + { + // osg::ElapsedTime timer; + + osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); + ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); + clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); + + osg::Matrixd invertModelView; + invertModelView.invert(viewMatrix); + osg::Polytope local_polytope(polytope); + local_polytope.transformProvidingInverse(invertModelView); + + osg::CullingSet& cs = clsb.getProjectionCullingStack().back(); + cs.setFrustum(local_polytope); + clsb.pushCullingSet(); + + _shadowedScene->accept(clsb); + + // OSG_NOTICE<<"Extents of LightSpace "<(maxZ, -corner.z()); + minZ = osg::minimum(minZ, -corner.z()); + } + reducedNear = osg::maximum(reducedNear, minZ); + reducedFar = osg::minimum(reducedFar, maxZ); + + // OSG_NOTICE<<" xMid="< camera = sd->_camera; + + camera->setProjectionMatrix(projectionMatrix); + camera->setViewMatrix(viewMatrix); + + if (settings->getDebugDraw()) + { + camera->getViewport()->x() = pos_x; + pos_x += static_cast(camera->getViewport()->width()) + 40; + } + + // transform polytope in model coords into light spaces eye coords. + osg::Matrixd invertModelView; + invertModelView.invert(camera->getViewMatrix()); + + osg::Polytope local_polytope(polytope); + local_polytope.transformProvidingInverse(invertModelView); + + double cascaseNear = reducedNear; + double cascadeFar = reducedFar; + if (numShadowMapsPerLight>1) + { + // compute the start and end range in non-dimensional coords +#if 0 + double r_start = (sm_i==0) ? -1.0 : (double(sm_i)/double(numShadowMapsPerLight)*2.0-1.0); + double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : (double(sm_i+1)/double(numShadowMapsPerLight)*2.0-1.0); +#elif 0 + + // hardwired for 2 splits + double r_start = (sm_i==0) ? -1.0 : splitPoint; + double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : splitPoint; +#else + double r_start, r_end; + + // split system based on the original Parallel Split Shadow Maps paper. + double n = reducedNear; + double f = reducedFar; + double i = double(sm_i); + double m = double(numShadowMapsPerLight); + if (sm_i == 0) + r_start = -1.0; + else + { + // compute the split point in main camera view + double ciLog = n * pow(f / n, i / m); + double ciUniform = n + (f - n) * i / m; + double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; + cascaseNear = ci; + + // work out where this is in light space + osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; + osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; + r_start = lightSpacePos.y(); + } + + if (sm_i + 1 == numShadowMapsPerLight) + r_end = 1.0; + else + { + // compute the split point in main camera view + double ciLog = n * pow(f / n, (i + 1) / m); + double ciUniform = n + (f - n) * (i + 1) / m; + double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; + cascadeFar = ci; + + // work out where this is in light space + osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; + osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; + r_end = lightSpacePos.y(); + } +#endif + // for all by the last shadowmap shift the r_end so that it overlaps slightly with the next shadowmap + // to prevent a seam showing through between the shadowmaps + if (sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i>0) + { + // not the first shadowmap so insert a polytope to clip the scene from before r_start + + // plane in clip space coords + osg::Plane plane(0.0,1.0,0.0,-r_start); + + // transform into eye coords + plane.transformProvidingInverse(projectionMatrix); + local_polytope.getPlaneList().push_back(plane); + + //OSG_NOTICE<<"Adding r_start plane "<getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT) + { + // OSG_NOTICE<<"Need to adjust RTT camera projection and view matrix here, r_start="< validRegionUniform; + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + for (auto uniform : _uniforms) + { + if (uniform->getName() == validRegionUniformName) + validRegionUniform = uniform; + } + + if (!validRegionUniform) + { + validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); + _uniforms.push_back(validRegionUniform); + } + + validRegionUniform->set(validRegionMatrix); + } + + if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) + adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar); + else + adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), reducedNear, reducedFar); + if (vdsmCallback->getProjectionMatrix()) + { + vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); + } + } + + // 4.4 compute main scene graph TexGen + uniform settings + setup state + // + assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); + + // mark the light as one that has active shadows and requires shaders + pl.textureUnits.push_back(textureUnit); + + // pass on shadow data to ShadowDataList + sd->_textureUnit = textureUnit; + + if (textureUnit >= 8) + { + OSG_NOTICE<<"Shadow texture unit is invalid for texgen, will not be used."<draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv); + } + } + + if (numValidShadows>0) + { + decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd)); + } + + // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<getLightDataList(); + + LightDataList previous_ldl; + previous_ldl.swap(pll); + + //MR testing giving a specific light + osgUtil::RenderStage * rs = cv->getCurrentRenderBin()->getStage(); + + OSG_INFO<<"selectActiveLights osgUtil::RenderStage="<getModelViewMatrix()); + + osgUtil::PositionalStateContainer::AttrMatrixList& aml = + rs->getPositionalStateContainer()->getAttrMatrixList(); + + + const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + for(osgUtil::PositionalStateContainer::AttrMatrixList::reverse_iterator itr = aml.rbegin(); + itr != aml.rend(); + ++itr) + { + const osg::Light* light = dynamic_cast(itr->first.get()); + if (light && light->getLightNum() >= 0) + { + // is LightNum matched to that defined in settings + if (settings && settings->getLightNum()>=0 && light->getLightNum()!=settings->getLightNum()) continue; + + LightDataList::iterator pll_itr = pll.begin(); + for(; pll_itr != pll.end(); ++pll_itr) + { + if ((*pll_itr)->light->getLightNum()==light->getLightNum()) break; + } + + if (pll_itr==pll.end()) + { + OSG_INFO<<"Light num "<getLightNum()<setLightData(itr->second.get(), light, modelViewMatrix); + pll.push_back(ld); + } + else + { + OSG_INFO<<"Light num "<getLightNum()<<" already used, ignore light"< lock(_accessUniformsAndProgramMutex); + + _shadowCastingStateSet = new osg::StateSet; + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + if (!settings->getDebugDraw()) + { + // note soft (attribute only no mode override) setting. When this works ? + // 1. for objects prepared for backface culling + // because they usually also set CullFace and CullMode on in their state + // For them we override CullFace but CullMode remains set by them + // 2. For one faced, trees, and similar objects which cannot use + // backface nor front face so they usually use CullMode off set here. + // In this case we will draw them in their entirety. + + if (_useFrontFaceCulling) + { + _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + // make sure GL_CULL_FACE is off by default + // we assume that if object has cull face attribute set to back + // it will also set cull face mode ON so no need for override + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + } + else + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } + + _polygonOffset = new osg::PolygonOffset(_polygonOffsetFactor, _polygonOffsetUnits); + _shadowCastingStateSet->setAttribute(_polygonOffset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + _shadowCastingStateSet->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + + _uniforms.clear(); + osg::ref_ptr baseTextureSampler = new osg::Uniform("baseTexture",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureSampler.get()); + + osg::ref_ptr baseTextureUnit = new osg::Uniform("baseTextureUnit",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureUnit.get()); + + for(unsigned int sm_i=0; sm_igetNumShadowMapsPerLight(); ++sm_i) + { + { + std::stringstream sstr; + sstr<<"shadowTexture"< shadowTextureSampler = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); + _uniforms.push_back(shadowTextureSampler.get()); + } + + { + std::stringstream sstr; + sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); + _uniforms.push_back(shadowTextureUnit.get()); + } + } + + switch(settings->getShaderHint()) + { + case(ShadowSettings::NO_SHADERS): + { + OSG_INFO<<"No shaders provided by, user must supply own shaders"< fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_noBaseTexture); + if (settings->getNumShadowMapsPerLight()==2) + { + _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture_twoShadowMaps)); + } + else + { + _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture)); + } + + break; + } + } + + { + osg::ref_ptr image = new osg::Image; + image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE ); + *(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF ); + + _fallbackBaseTexture = new osg::Texture2D(image.get()); + _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + _fallbackBaseTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + _fallbackBaseTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + _fallbackShadowMapTexture = new osg::Texture2D(image.get()); + _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + _fallbackShadowMapTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + _fallbackShadowMapTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + } + + if (!_castingProgram) + OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; + + _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied + _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); + _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + + _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); + + _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + + // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader + // TODO: compare performance when we set a bunch of GL state to the default here with OVERRIDE set so that there are fewer pointless state switches +} + +osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight) +{ + OSG_INFO<<"computeLightViewFrustumPolytope()"<getShadowSettings(); + + double dotProduct_v = positionedLight.lightDir * frustum.frustumCenterLine; + double gamma_v = acos(dotProduct_v); + if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180.0-settings->getPerspectiveShadowMapCutOffAngle())) + { + OSG_INFO<<"View direction and Light direction below tolerance"<=0.0 && d1>=0.0) + { + // OSG_NOTICE<<" Edge completely inside"<first; + osg::Vec3d& v1 = itr->second; + osg::Vec3d intersection = v0 - (v1-v0)*(d0/(d1-d0)); + intersections.push_back(intersection); + // OSG_NOTICE<<" Edge across clip plane, v0="<=side_y.length2()) ? side_x : side_y; + side.normalize(); + + osg::Vec3d up = side ^ normal; + up.normalize(); + + osg::Vec3d center; + for(Vertices::iterator itr = intersections.begin(); + itr != intersections.end(); + ++itr) + { + center += *itr; + + center.x() = osg::maximum(center.x(), -DBL_MAX); + center.y() = osg::maximum(center.y(), -DBL_MAX); + center.z() = osg::maximum(center.z(), -DBL_MAX); + + center.x() = osg::minimum(center.x(), DBL_MAX); + center.y() = osg::minimum(center.y(), DBL_MAX); + center.z() = osg::minimum(center.z(), DBL_MAX); + } + + center /= double(intersections.size()); + + typedef std::map>> VertexMap; + VertexMap vertexMap; + for(Vertices::iterator itr = intersections.begin(); + itr != intersections.end(); + ++itr) + { + osg::Vec3d dv = (*itr-center); + double h = dv * side; + double v = dv * up; + double angle = atan2(h,v); + // OSG_NOTICE<<"angle = "<_modelview.get()!=previous_modelview) + { + previous_modelview = renderLeaf->_modelview.get(); + if (previous_modelview) + { + light_mvp.mult(*renderLeaf->_modelview, light_p); + } + else + { + // no modelview matrix (such as when LightPointNode is in the scene graph) so assume + // that modelview matrix is indentity. + light_mvp = light_p; + } + // OSG_INFO<<"Computing new light_mvp "<_drawable->getBoundingBox(); + if (bb.valid()) + { + // OSG_NOTICE<<"checked extents of "<_drawable->getName()<max_z) { max_z=ls.z(); /* OSG_NOTICE<<" + ";*/ } + + // OSG_NOTICE<<" bb.z() in ls = "<& planeList) +{ + osg::Matrixd light_p = camera->getProjectionMatrix(); + osg::Matrixd light_v = camera->getViewMatrix(); + osg::Matrixd light_vp = light_v * light_p; + osg::Matrixd oldLightP = light_p; + + ConvexHull convexHull; + convexHull.setToFrustum(frustum); + + osg::Vec3d nearPoint = frustum.eye + frustum.frustumCenterLine * viewNear; + osg::Vec3d farPoint = frustum.eye + frustum.frustumCenterLine * viewFar; + + double nearDist = -frustum.frustumCenterLine * nearPoint; + double farDist = frustum.frustumCenterLine * farPoint; + + convexHull.clip(osg::Plane(frustum.frustumCenterLine, nearDist)); + convexHull.clip(osg::Plane(-frustum.frustumCenterLine, farDist)); + + convexHull.transform(light_vp); + + double xMin = -1.0, xMax = 1.0; + double yMin = -1.0, yMax = 1.0; + double zMin = -1.0, zMax = 1.0; + + if (convexHull.valid()) + { + xMin = osg::maximum(-1.0, convexHull.min(0)); + xMax = osg::minimum(1.0, convexHull.max(0)); + yMin = osg::maximum(-1.0, convexHull.min(1)); + yMax = osg::minimum(1.0, convexHull.max(1)); + zMin = osg::maximum(-1.0, convexHull.min(2)); + zMax = osg::minimum(1.0, convexHull.max(2)); + } + else + return false; + + if (xMin != -1.0 || yMin != -1.0 || zMin != -1.0 || + xMax != 1.0 || yMax != 1.0 || zMax != 1.0) + { + osg::Matrix m; + m.makeTranslate(osg::Vec3d(-0.5*(xMax + xMin), + -0.5*(yMax + yMin), + -0.5*(zMax + zMin))); + + m.postMultScale(osg::Vec3d(2.0 / (xMax - xMin), + 2.0 / (yMax - yMin), + 2.0 / (zMax - zMin))); + + light_p.postMult(m); + camera->setProjectionMatrix(light_p); + + convexHull.transform(osg::Matrixd::inverse(oldLightP)); + + xMin = convexHull.min(0); + xMax = convexHull.max(0); + yMin = convexHull.min(1); + yMax = convexHull.max(1); + zMin = convexHull.min(2); + + planeList.push_back(osg::Plane(0.0, -1.0, 0.0, yMax)); + planeList.push_back(osg::Plane(0.0, 1.0, 0.0, -yMin)); + planeList.push_back(osg::Plane(-1.0, 0.0, 0.0, xMax)); + planeList.push_back(osg::Plane(1.0, 0.0, 0.0, -xMin)); + // In view space, the light is at the most positive value, and we want to cull stuff beyond the minimum value. + planeList.push_back(osg::Plane(0.0, 0.0, 1.0, -zMin)); + // Don't add a zMax culling plane - we still want those objects, but don't care about their depth buffer value. + } + + return true; +} + +bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& /*positionedLight*/, osg::Camera* camera, double viewNear, double viewFar) +{ + const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + //frustum.projectionMatrix; + //frustum.modelViewMatrix; + + osg::Matrixd light_p = camera->getProjectionMatrix(); + osg::Matrixd light_v = camera->getViewMatrix(); + osg::Matrixd light_vp = light_v * light_p; + osg::Vec3d lightdir(0.0,0.0,-1.0); + + // check whether this light space projection is perspective or orthographic. + bool orthographicLightSpaceProjection = light_p(0,3)==0.0 && light_p(1,3)==0.0 && light_p(2,3)==0.0; + + if (!orthographicLightSpaceProjection) + { + OSG_INFO<<"perspective light space projection not yet supported."<setProjectionMatrix(light_p); + } + +#endif + + osg::Vec3d eye_v = frustum.eye * light_v; + //osg::Vec3d centerNearPlane_v = frustum.centerNearPlane * light_v; + osg::Vec3d center_v = frustum.center * light_v; + osg::Vec3d viewdir_v = center_v-eye_v; viewdir_v.normalize(); + + double dotProduct_v = lightdir * viewdir_v; + double gamma_v = acos(dotProduct_v); + if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180-settings->getPerspectiveShadowMapCutOffAngle())) + { + // OSG_NOTICE<<"Light and view vectors near parallel - use standard shadow map."<getTraversalMask(); + + cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getReceivesShadowTraversalMask() ); + + _shadowedScene->osg::Group::traverse(*cv); + + cv->setTraversalMask( traversalMask ); + + return; +} + +void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const +{ + OSG_INFO<<"cullShadowCastingScene()"<getTraversalMask(); + + cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getCastsShadowTraversalMask() ); + + if (camera) camera->accept(*cv); + + cv->setTraversalMask( traversalMask ); + + return; +} + +osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependentData& vdd) const +{ + OSG_INFO<<" selectStateSetForRenderingShadow() "< stateset = vdd.getStateSet(); + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + vdd.getStateSet()->clear(); + + vdd.getStateSet()->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); + + for(Uniforms::const_iterator itr=_uniforms.begin(); + itr!=_uniforms.end(); + ++itr) + { + OSG_INFO<<"addUniform("<<(*itr)->getName()<<")"<addUniform(itr->get()); + } + + if (_program.valid()) + { + stateset->setAttribute(_program.get()); + } + + LightDataList& pll = vdd.getLightDataList(); + for(LightDataList::iterator itr = pll.begin(); + itr != pll.end(); + ++itr) + { + // 3. create per light/per shadow map division of lightspace/frustum + // create a list of light/shadow map data structures + + LightData& pl = (**itr); + + // if no texture units have been activated for this light then no shadow state required. + if (pl.textureUnits.empty()) continue; + + for(LightData::ActiveTextureUnits::iterator atu_itr = pl.textureUnits.begin(); + atu_itr != pl.textureUnits.end(); + ++atu_itr) + { + OSG_INFO<<" Need to assign state for "<<*atu_itr<getShadowSettings(); + unsigned int shadowMapModeValue = settings->getUseOverrideForShadowMapTexture() ? + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE : + osg::StateAttribute::ON; + + + ShadowDataList& sdl = vdd.getShadowDataList(); + for(ShadowDataList::iterator itr = sdl.begin(); + itr != sdl.end(); + ++itr) + { + // 3. create per light/per shadow map division of lightspace/frustum + // create a list of light/shadow map data structures + + ShadowData& sd = (**itr); + + OSG_INFO<<" ShadowData for "<setTextureAttributeAndModes(sd._textureUnit, sd._texture.get(), shadowMapModeValue); + + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON); + } + + return vdd.getStateSet(); +} + +void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/) +{ + // the way that ViewDependentData is mapped shouldn't +} + +void MWShadowTechnique::releaseGLObjects(osg::State* state) const +{ + OpenThreads::ScopedLock lock(_viewDependentDataMapMutex); + for(ViewDependentDataMap::const_iterator itr = _viewDependentDataMap.begin(); + itr != _viewDependentDataMap.end(); + ++itr) + { + ViewDependentData* vdd = itr->second.get(); + if (vdd) + { + vdd->releaseGLObjects(state); + } + } + if (_debugHud) + _debugHud->releaseGLObjects(state); +} + +class DoubleBufferCallback : public osg::Callback +{ +public: + DoubleBufferCallback(osg::NodeList &children) : mChildren(children) {} + + virtual bool run(osg::Object* node, osg::Object* visitor) override + { + // We can't use a static cast as NodeVisitor virtually inherits from Object + osg::ref_ptr nodeVisitor = visitor->asNodeVisitor(); + unsigned int traversalNumber = nodeVisitor->getTraversalNumber(); + mChildren[traversalNumber % 2]->accept(*nodeVisitor); + + return true; + } + +protected: + osg::NodeList mChildren; +}; + +SceneUtil::MWShadowTechnique::DebugHUD::DebugHUD(int numberOfShadowMapsPerLight) : mDebugProgram(new osg::Program) +{ + osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, debugVertexShaderSource); + mDebugProgram->addShader(vertexShader); + osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFragmentShaderSource); + mDebugProgram->addShader(fragmentShader); + + osg::ref_ptr frustumProgram = new osg::Program; + vertexShader = new osg::Shader(osg::Shader::VERTEX, debugFrustumVertexShaderSource); + frustumProgram->addShader(vertexShader); + fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFrustumFragmentShaderSource); + frustumProgram->addShader(fragmentShader); + + for (int i = 0; i < 2; ++i) + { + mFrustumGeometries.emplace_back(new osg::Geometry()); + mFrustumGeometries[i]->setCullingActive(false); + + mFrustumGeometries[i]->getOrCreateStateSet()->setAttributeAndModes(frustumProgram, osg::StateAttribute::ON); + } + + osg::ref_ptr frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP); + for (auto & geom : mFrustumGeometries) + geom->addPrimitiveSet(frustumDrawElements); + frustumDrawElements->push_back(0); + frustumDrawElements->push_back(1); + frustumDrawElements->push_back(2); + frustumDrawElements->push_back(3); + frustumDrawElements->push_back(0); + frustumDrawElements->push_back(4); + frustumDrawElements->push_back(5); + frustumDrawElements->push_back(6); + frustumDrawElements->push_back(7); + frustumDrawElements->push_back(4); + + frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES); + for (auto & geom : mFrustumGeometries) + geom->addPrimitiveSet(frustumDrawElements); + frustumDrawElements->push_back(1); + frustumDrawElements->push_back(5); + frustumDrawElements->push_back(2); + frustumDrawElements->push_back(6); + frustumDrawElements->push_back(3); + frustumDrawElements->push_back(7); + + for (int i = 0; i < numberOfShadowMapsPerLight; ++i) + addAnotherShadowMap(); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv) +{ + // It might be possible to change shadow settings at runtime + if (shadowMapNumber > mDebugCameras.size()) + addAnotherShadowMap(); + + mFrustumUniforms[shadowMapNumber]->set(matrix); + + osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); + stateSet->setTextureAttributeAndModes(sDebugTextureUnit, texture, osg::StateAttribute::ON); + + // Some of these calls may be superfluous. + unsigned int traversalMask = cv.getTraversalMask(); + cv.setTraversalMask(mDebugGeometry[shadowMapNumber]->getNodeMask()); + cv.pushStateSet(stateSet); + mDebugCameras[shadowMapNumber]->accept(cv); + cv.popStateSet(); + cv.setTraversalMask(traversalMask); + + // cv.getState()->setCheckForGLErrors(osg::State::ONCE_PER_ATTRIBUTE); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::releaseGLObjects(osg::State* state) const +{ + for (auto const& camera : mDebugCameras) + camera->releaseGLObjects(state); + mDebugProgram->releaseGLObjects(state); + for (auto const& node : mDebugGeometry) + node->releaseGLObjects(state); + for (auto const& node : mFrustumTransforms) + node->releaseGLObjects(state); + for (auto const& node : mFrustumGeometries) + node->releaseGLObjects(state); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber) +{ + mFrustumGeometries[traversalNumber % 2]->setVertexArray(vertices); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::addAnotherShadowMap() +{ + unsigned int shadowMapNumber = mDebugCameras.size(); + + mDebugCameras.push_back(new osg::Camera); + mDebugCameras[shadowMapNumber]->setViewport(200 * shadowMapNumber, 0, 200, 200); + mDebugCameras[shadowMapNumber]->setRenderOrder(osg::Camera::POST_RENDER); + mDebugCameras[shadowMapNumber]->setClearColor(osg::Vec4(1.0, 1.0, 0.0, 1.0)); + mDebugCameras[shadowMapNumber]->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + mDebugGeometry.push_back(osg::createTexturedQuadGeometry(osg::Vec3(-1, -1, 0), osg::Vec3(2, 0, 0), osg::Vec3(0, 2, 0))); + mDebugGeometry[shadowMapNumber]->setCullingActive(false); + mDebugCameras[shadowMapNumber]->addChild(mDebugGeometry[shadowMapNumber]); + osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); + stateSet->setAttributeAndModes(mDebugProgram, osg::StateAttribute::ON); + osg::ref_ptr textureUniform = new osg::Uniform("texture", sDebugTextureUnit); + //textureUniform->setType(osg::Uniform::SAMPLER_2D); + stateSet->addUniform(textureUniform.get()); + + mFrustumTransforms.push_back(new osg::Group); + osg::NodeList frustumGeometryNodeList(mFrustumGeometries.cbegin(), mFrustumGeometries.cend()); + mFrustumTransforms[shadowMapNumber]->setCullCallback(new DoubleBufferCallback(frustumGeometryNodeList)); + mFrustumTransforms[shadowMapNumber]->setCullingActive(false); + mDebugCameras[shadowMapNumber]->addChild(mFrustumTransforms[shadowMapNumber]); + + mFrustumUniforms.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); + mFrustumTransforms[shadowMapNumber]->getOrCreateStateSet()->addUniform(mFrustumUniforms[shadowMapNumber]); +} diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp new file mode 100644 index 000000000..621066799 --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -0,0 +1,287 @@ +/* This file is based on OpenSceneGraph's include/osgShadow/ViewDependentShadowMap. + * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. + * The original copyright notice is listed below. + */ + +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H +#define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1 + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace SceneUtil { + + /** ViewDependentShadowMap provides an base implementation of view dependent shadow mapping techniques.*/ + class MWShadowTechnique : public osgShadow::ShadowTechnique + { + public: + MWShadowTechnique(); + + MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(SceneUtil, MWShadowTechnique); + + /** initialize the ShadowedScene and local cached data structures.*/ + virtual void init(); + + /** run the update traversal of the ShadowedScene and update any loca chached data structures.*/ + virtual void update(osg::NodeVisitor& nv); + + /** run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/ + virtual void cull(osgUtil::CullVisitor& cv); + + /** Resize any per context GLObject buffers to specified size. */ + virtual void resizeGLObjectBuffers(unsigned int maxSize); + + /** If State is non-zero, this function releases any associated OpenGL objects for + * the specified graphics context. Otherwise, releases OpenGL objects + * for all graphics contexts. */ + virtual void releaseGLObjects(osg::State* = 0) const; + + /** Clean scene graph from any shadow technique specific nodes, state and drawables.*/ + virtual void cleanSceneGraph(); + + virtual void enableShadows(); + + virtual void disableShadows(); + + virtual void enableDebugHUD(); + + virtual void disableDebugHUD(); + + virtual void setSplitPointUniformLogarithmicRatio(double ratio); + + virtual void setSplitPointDeltaBias(double bias); + + virtual void setPolygonOffset(float factor, float units); + + virtual void enableFrontFaceCulling(); + + virtual void disableFrontFaceCulling(); + + virtual void setupCastingShader(Shader::ShaderManager &shaderManager); + + class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack + { + public: + ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + void apply(osg::Node& node); + + void apply(osg::Drawable& drawable); + + void apply(Terrain::QuadTreeWorld& quadTreeWorld); + + void apply(osg::Billboard&); + + void apply(osg::Projection&); + + void apply(osg::Transform& transform); + + void apply(osg::Camera&); + + void updateBound(const osg::BoundingBox& bb); + + void update(const osg::Vec3& v); + + osg::BoundingBox _bb; + }; + + struct Frustum + { + Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar); + + osg::Matrixd projectionMatrix; + osg::Matrixd modelViewMatrix; + + typedef std::vector Vertices; + Vertices corners; + + typedef std::vector Indices; + typedef std::vector Faces; + Faces faces; + + typedef std::vector Edges; + Edges edges; + + osg::Vec3d eye; + osg::Vec3d centerNearPlane; + osg::Vec3d centerFarPlane; + osg::Vec3d center; + osg::Vec3d frustumCenterLine; + }; + + // forward declare + class ViewDependentData; + + struct LightData : public osg::Referenced + { + LightData(ViewDependentData* vdd); + + virtual void setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix); + + ViewDependentData* _viewDependentData; + + osg::ref_ptr lightMatrix; + osg::ref_ptr light; + + osg::Vec4d lightPos; + osg::Vec3d lightPos3; + osg::Vec3d lightDir; + bool directionalLight; + + typedef std::vector ActiveTextureUnits; + ActiveTextureUnits textureUnits; + }; + + typedef std::list< osg::ref_ptr > LightDataList; + + struct ShadowData : public osg::Referenced + { + ShadowData(ViewDependentData* vdd); + + virtual void releaseGLObjects(osg::State* = 0) const; + + ViewDependentData* _viewDependentData; + + unsigned int _textureUnit; + osg::ref_ptr _texture; + osg::ref_ptr _texgen; + osg::ref_ptr _camera; + }; + + typedef std::list< osg::ref_ptr > ShadowDataList; + + + class ViewDependentData : public osg::Referenced + { + public: + ViewDependentData(MWShadowTechnique* vdsm); + + const MWShadowTechnique* getViewDependentShadowMap() const { return _viewDependentShadowMap; } + + LightDataList& getLightDataList() { return _lightDataList; } + + ShadowDataList& getShadowDataList() { return _shadowDataList; } + + osg::StateSet* getStateSet() { return _stateset.get(); } + + virtual void releaseGLObjects(osg::State* = 0) const; + + protected: + virtual ~ViewDependentData() {} + + MWShadowTechnique* _viewDependentShadowMap; + + osg::ref_ptr _stateset; + + LightDataList _lightDataList; + ShadowDataList _shadowDataList; + }; + + virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); + + ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + + + + virtual void createShaders(); + + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; + + virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); + + virtual bool computeShadowCameraSettings(Frustum& frustum, LightData& positionedLight, osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector& planeList); + + virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar); + + virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen); + + virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; + + virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; + + virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd) const; + + protected: + virtual ~MWShadowTechnique(); + + typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; + mutable OpenThreads::Mutex _viewDependentDataMapMutex; + ViewDependentDataMap _viewDependentDataMap; + + osg::ref_ptr _shadowRecievingPlaceholderStateSet; + + osg::ref_ptr _shadowCastingStateSet; + osg::ref_ptr _polygonOffset; + osg::ref_ptr _fallbackBaseTexture; + osg::ref_ptr _fallbackShadowMapTexture; + + typedef std::vector< osg::ref_ptr > Uniforms; + mutable OpenThreads::Mutex _accessUniformsAndProgramMutex; + Uniforms _uniforms; + osg::ref_ptr _program; + + bool _enableShadows; + + double _splitPointUniformLogRatio = 0.5; + double _splitPointDeltaBias = 0.0; + + float _polygonOffsetFactor = 1.1; + float _polygonOffsetUnits = 4.0; + + bool _useFrontFaceCulling = true; + + class DebugHUD : public osg::Referenced + { + public: + DebugHUD(int numberOfShadowMapsPerLight); + + virtual void draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv); + + virtual void releaseGLObjects(osg::State* state = 0) const; + + virtual void setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber); + protected: + virtual void addAnotherShadowMap(); + + static const int sDebugTextureUnit = 0; + + std::vector> mDebugCameras; + osg::ref_ptr mDebugProgram; + std::vector> mDebugGeometry; + std::vector> mFrustumTransforms; + std::vector> mFrustumUniforms; + std::vector> mFrustumGeometries; + }; + + osg::ref_ptr _debugHud; + osg::ref_ptr _castingProgram; + }; + +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 4b550b69c..f1acf33e9 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -51,6 +51,8 @@ RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) : Drawable(copy, copyop) , mSkeleton(nullptr) , mInfluenceMap(copy.mInfluenceMap) + , mBone2VertexVector(copy.mBone2VertexVector) + , mBoneSphereVector(copy.mBoneSphereVector) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { @@ -69,6 +71,8 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class + to.setComputeBoundingBoxCallback(new CopyBoundingBoxCallback()); + to.setComputeBoundingSphereCallback(new CopyBoundingSphereCallback()); // vertices and normals are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. @@ -134,37 +138,38 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) return false; } - typedef std::map > Vertex2BoneMap; - Vertex2BoneMap vertex2BoneMap; - mBoneSphereVector.clear(); - for (auto& influencePair : mInfluenceMap->mData) + mBoneNodesVector.clear(); + for (auto& bonePair : mBoneSphereVector->mData) { - Bone* bone = mSkeleton->getBone(influencePair.first); + const std::string& boneName = bonePair.first; + Bone* bone = mSkeleton->getBone(boneName); if (!bone) { - Log(Debug::Error) << "Error: RigGeometry did not find bone " << influencePair.first; + mBoneNodesVector.push_back(nullptr); + Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; continue; } - const BoneInfluence& bi = influencePair.second; - mBoneSphereVector.emplace_back(bone, bi.mBoundSphere); + mBoneNodesVector.push_back(bone); + } - for (auto& weightPair: bi.mWeights) + for (auto& pair : mBone2VertexVector->mData) + { + for (auto &weight : pair.first) { - std::vector& vec = vertex2BoneMap[weightPair.first]; + const std::string& boneName = weight.first.first; + Bone* bone = mSkeleton->getBone(boneName); + if (!bone) + { + mBoneNodesVector.push_back(nullptr); + Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; + continue; + } - vec.emplace_back(std::make_pair(bone, bi.mInvBindMatrix), weightPair.second); + mBoneNodesVector.push_back(bone); } } - Bone2VertexMap bone2VertexMap; - for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it) - { - bone2VertexMap[it->second].push_back(it->first); - } - - mBone2VertexVector.assign(bone2VertexMap.begin(), bone2VertexMap.end()); - return true; } @@ -201,7 +206,8 @@ void RigGeometry::cull(osg::NodeVisitor* nv) osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); - for (auto &pair : mBone2VertexVector) + int index = mBoneSphereVector->mData.size(); + for (auto &pair : mBone2VertexVector->mData) { osg::Matrixf resultMat (0, 0, 0, 0, 0, 0, 0, 0, @@ -210,7 +216,12 @@ void RigGeometry::cull(osg::NodeVisitor* nv) for (auto &weight : pair.first) { - accumulateMatrix(weight.first.second, weight.first.first->mMatrixInSkeletonSpace, weight.second, resultMat); + Bone* bone = mBoneNodesVector[index]; + if (bone == nullptr) + continue; + + accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat); + index++; } if (mGeomToSkelMatrix) @@ -263,9 +274,15 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) updateGeomToSkelMatrix(nv->getNodePath()); osg::BoundingBox box; - for (auto& boundPair : mBoneSphereVector) + + int index = 0; + for (auto& boundPair : mBoneSphereVector->mData) { - Bone* bone = boundPair.first; + Bone* bone = mBoneNodesVector[index]; + if (bone == nullptr) + continue; + + index++; osg::BoundingSpheref bs = boundPair.second; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); @@ -281,6 +298,14 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) _boundingSphereComputed = true; for (unsigned int i=0; idirtyBound(); + + for (unsigned int i = 0; i < 2; ++i) + { + osg::Geometry& geom = *mGeometry[i]; + static_cast(geom.getComputeBoundingBoxCallback())->boundingBox = _boundingBox; + static_cast(geom.getComputeBoundingSphereCallback())->boundingSphere = _boundingSphere; + geom.dirtyBound(); + } } } @@ -313,6 +338,34 @@ void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) { mInfluenceMap = influenceMap; + + typedef std::map > Vertex2BoneMap; + Vertex2BoneMap vertex2BoneMap; + mBoneSphereVector = new BoneSphereVector; + mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size()); + mBone2VertexVector = new Bone2VertexVector; + for (auto& influencePair : mInfluenceMap->mData) + { + const std::string& boneName = influencePair.first; + const BoneInfluence& bi = influencePair.second; + mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere); + + for (auto& weightPair: bi.mWeights) + { + std::vector& vec = vertex2BoneMap[weightPair.first]; + + vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second); + } + } + + Bone2VertexMap bone2VertexMap; + for (auto& vertexPair : vertex2BoneMap) + { + bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first); + } + + mBone2VertexVector->mData.reserve(bone2VertexMap.size()); + mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end()); } void RigGeometry::accept(osg::NodeVisitor &nv) diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 5dd05507c..a393530ec 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -6,7 +6,6 @@ namespace SceneUtil { - class Skeleton; class Bone; @@ -51,6 +50,20 @@ namespace SceneUtil virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } virtual void accept(osg::PrimitiveFunctor&) const; + struct CopyBoundingBoxCallback : osg::Drawable::ComputeBoundingBoxCallback + { + osg::BoundingBox boundingBox; + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const override { return boundingBox; } + }; + + struct CopyBoundingSphereCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere boundingSphere; + + virtual osg::BoundingSphere computeBound(const osg::Node&) const override { return boundingSphere; } + }; + private: void cull(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv); @@ -66,20 +79,26 @@ namespace SceneUtil osg::ref_ptr mInfluenceMap; - typedef std::pair BoneBindMatrixPair; + typedef std::pair BoneBindMatrixPair; typedef std::pair BoneWeight; typedef std::vector VertexList; typedef std::map, VertexList> Bone2VertexMap; - typedef std::vector, VertexList>> Bone2VertexVector; - Bone2VertexVector mBone2VertexVector; + struct Bone2VertexVector : public osg::Referenced + { + std::vector, VertexList>> mData; + }; + osg::ref_ptr mBone2VertexVector; - typedef std::vector> BoneSphereVector; - - BoneSphereVector mBoneSphereVector; + struct BoneSphereVector : public osg::Referenced + { + std::vector> mData; + }; + osg::ref_ptr mBoneSphereVector; + std::vector mBoneNodesVector; unsigned int mLastFrameNumber; bool mBoundsFirstFrame; diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp new file mode 100644 index 000000000..a69fd8090 --- /dev/null +++ b/components/sceneutil/shadow.cpp @@ -0,0 +1,158 @@ +#include "shadow.hpp" + +#include + +#include + +namespace SceneUtil +{ + using namespace osgShadow; + + void ShadowManager::setupShadowSettings() + { + mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + + if (!mEnableShadows) + { + mShadowTechnique->disableShadows(); + return; + } + + mShadowTechnique->enableShadows(); + + mShadowSettings->setLightNum(0); + mShadowSettings->setReceivesShadowTraversalMask(~0u); + + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); + mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight); + + mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + if (Settings::Manager::getBool("compute tight scene bounds", "Shadows")) + mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + + int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); + + mShadowTechnique->setSplitPointUniformLogarithmicRatio(Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); + mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + + mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), Settings::Manager::getFloat("polygon offset units", "Shadows")); + + if (Settings::Manager::getBool("use front face culling", "Shadows")) + mShadowTechnique->enableFrontFaceCulling(); + else + mShadowTechnique->disableFrontFaceCulling(); + + if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); + else + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); + + if (Settings::Manager::getBool("enable debug hud", "Shadows")) + mShadowTechnique->enableDebugHUD(); + else + mShadowTechnique->disableDebugHUD(); + } + + void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + { + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; + + osg::ref_ptr fakeShadowMapImage = new osg::Image(); + fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); + *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); + osg::ref_ptr fakeShadowMapTexture = new osg::Texture2D(fakeShadowMapImage); + fakeShadowMapTexture->setShadowComparison(true); + fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); + for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + { + stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); + stateset->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + } + } + + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), + mShadowTechnique(new MWShadowTechnique), + mOutdoorShadowCastingMask(outdoorShadowCastingMask), + mIndoorShadowCastingMask(indoorShadowCastingMask) + { + mShadowedScene->setShadowTechnique(mShadowTechnique); + + mShadowedScene->addChild(sceneRoot); + rootNode->addChild(mShadowedScene); + + mShadowSettings = mShadowedScene->getShadowSettings(); + setupShadowSettings(); + + mShadowTechnique->setupCastingShader(shaderManager); + + enableOutdoorMode(); + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + { + if (!mEnableShadows) + return getShadowsDisabledDefines(); + + Shader::ShaderManager::DefineMap definesWithShadows; + + definesWithShadows["shadows_enabled"] = "1"; + + for (unsigned int i = 0; i < mShadowSettings->getNumShadowMapsPerLight(); ++i) + definesWithShadows["shadow_texture_unit_list"] += std::to_string(i) + ","; + // remove extra comma + definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr(0, definesWithShadows["shadow_texture_unit_list"].length() - 1); + + definesWithShadows["shadowMapsOverlap"] = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + + definesWithShadows["useShadowDebugOverlay"] = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + + // switch this to reading settings if it's ever exposed to the user + definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; + + definesWithShadows["disableNormalOffsetShadows"] = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + + definesWithShadows["shadowNormalOffset"] = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + + return definesWithShadows; + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowsDisabledDefines() + { + Shader::ShaderManager::DefineMap definesWithoutShadows; + + definesWithoutShadows["shadows_enabled"] = "0"; + + definesWithoutShadows["shadow_texture_unit_list"] = ""; + + definesWithoutShadows["shadowMapsOverlap"] = "0"; + + definesWithoutShadows["useShadowDebugOverlay"] = "0"; + + definesWithoutShadows["perspectiveShadowMaps"] = "0"; + + definesWithoutShadows["disableNormalOffsetShadows"] = "0"; + + definesWithoutShadows["shadowNormalOffset"] = "0.0"; + + return definesWithoutShadows; + } + + void ShadowManager::enableIndoorMode() + { + if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); + else + mShadowTechnique->disableShadows(); + } + + void ShadowManager::enableOutdoorMode() + { + if (mEnableShadows) + mShadowTechnique->enableShadows(); + mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); + } +} diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp new file mode 100644 index 000000000..24deff253 --- /dev/null +++ b/components/sceneutil/shadow.hpp @@ -0,0 +1,43 @@ +#ifndef COMPONENTS_SCENEUTIL_SHADOW_H +#define COMPONENTS_SCENEUTIL_SHADOW_H + +#include +#include + +#include + +#include "mwshadowtechnique.hpp" + +namespace SceneUtil +{ + class ShadowManager + { + public: + static void disableShadowsForStateSet(osg::ref_ptr stateSet); + + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); + + virtual ~ShadowManager() = default; + + virtual void setupShadowSettings(); + + virtual Shader::ShaderManager::DefineMap getShadowDefines(); + + virtual Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + + virtual void enableIndoorMode(); + + virtual void enableOutdoorMode(); + protected: + bool mEnableShadows; + + osg::ref_ptr mShadowedScene; + osg::ref_ptr mShadowSettings; + osg::ref_ptr mShadowTechnique; + + unsigned int mOutdoorShadowCastingMask; + unsigned int mIndoorShadowCastingMask; + }; +} + +#endif //COMPONENTS_SCENEUTIL_SHADOW_H diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index bbeda683a..a9857bed3 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -1,5 +1,7 @@ #include "util.hpp" +#include + namespace SceneUtil { @@ -53,4 +55,22 @@ float makeOsgColorComponent(unsigned int value, unsigned int shift) return float((value >> shift) & 0xFFu) / 255.0f; } +bool hasUserDescription(const osg::Node* node, const std::string pattern) +{ + if (node == nullptr) + return false; + + const osg::UserDataContainer* udc = node->getUserDataContainer(); + if (udc && udc->getNumDescriptions() > 0) + { + for (auto& descr : udc->getDescriptions()) + { + if (descr == pattern) + return true; + } + } + + return false; +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 156d173d2..f293b9975 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -19,6 +19,7 @@ namespace SceneUtil float makeOsgColorComponent (unsigned int value, unsigned int shift); + bool hasUserDescription(const osg::Node* node, const std::string pattern); } #endif diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index f7111fdf8..c67edfbf3 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -368,7 +368,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v } else { - throw new std::runtime_error("Tried to package non-motion event!"); + throw std::runtime_error("Tried to package non-motion event!"); } return pack_evt; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index f3d52f5f0..0f4dc2e73 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -21,6 +21,44 @@ namespace Shader mPath = path; } + bool addLineDirectivesAfterConditionalBlocks(std::string& source) + { + for (size_t position = 0; position < source.length(); ) + { + size_t foundPos = source.find("#endif", position); + foundPos = std::min(foundPos, source.find("#elif", position)); + foundPos = std::min(foundPos, source.find("#else", position)); + + if (foundPos == std::string::npos) + break; + + foundPos = source.find_first_of("\n\r", foundPos); + foundPos = source.find_first_not_of("\n\r", foundPos); + + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); + + source.replace(foundPos, 0, "#line " + std::to_string(lineNumber) + "\n"); + + position = foundPos; + } + + return true; + } + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) { boost::replace_all(source, "\r\n", "\n"); @@ -54,14 +92,30 @@ namespace Shader std::stringstream buffer; buffer << includeFstream.rdbuf(); + std::string stringRepresentation = buffer.str(); + addLineDirectivesAfterConditionalBlocks(stringRepresentation); // insert #line directives so we get correct line numbers in compiler errors int includedFileNumber = fileNumber++; - int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); std::stringstream toInsert; - toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; source.replace(foundPos, (end-foundPos+1), toInsert.str()); @@ -74,13 +128,97 @@ namespace Shader return true; } - bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + bool parseFors(std::string& source) { - const char escapeCharacter = '@'; + const char escapeCharacter = '$'; size_t foundPos = 0; while ((foundPos = source.find(escapeCharacter)) != std::string::npos) { - size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); + if (endPos == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); + if (command != "foreach") + { + Log(Debug::Error) << "Unknown shader directive: $" << command; + return false; + } + + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); + + size_t listStart = iterNameEnd + 1; + size_t listEnd = source.find_first_of("\n\r", listStart); + if (listEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string list = source.substr(listStart, listEnd - listStart); + std::vector listElements; + if (list != "") + boost::split(listElements, list, boost::is_any_of(",")); + + size_t contentStart = source.find_first_not_of("\n\r", listEnd); + size_t contentEnd = source.find("$endforeach", contentStart); + if (contentEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string content = source.substr(contentStart, contentEnd - contentStart); + + size_t overallEnd = contentEnd + std::string("$endforeach").length(); + + size_t lineDirectivePosition = source.rfind("#line", overallEnd); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString); + } + else + { + lineDirectivePosition = 0; + lineNumber = 2; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); + + std::string replacement = ""; + for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) + { + std::string contentInstance = content; + size_t foundIterator; + while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) + contentInstance.replace(foundIterator, iteratorName.length(), *element); + replacement += contentInstance; + } + replacement += "\n#line " + std::to_string(lineNumber); + source.replace(foundPos, overallEnd - foundPos, replacement); + } + + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + std::vector forIterators; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { Log(Debug::Error) << "Unexpected EOF"; @@ -88,14 +226,46 @@ namespace Shader } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); - if (defineFound == defines.end()) + ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); + if (define == "foreach") { - Log(Debug::Error) << "Undefined " << define; - return false; + source.replace(foundPos, 1, "$"); + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); + } + else if (define == "endforeach") + { + source.replace(foundPos, 1, "$"); + if (forIterators.empty()) + { + Log(Debug::Error) << "endforeach without foreach"; + return false; + } + else + forIterators.pop_back(); + } + else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) + { + source.replace(foundPos, 1, "$"); + } + else if (defineFound != defines.end()) + { + source.replace(foundPos, endPos - foundPos, defineFound->second); + } + else if (globalDefineFound != globalDefines.end()) + { + source.replace(foundPos, endPos - foundPos, globalDefineFound->second); } else { - source.replace(foundPos, endPos-foundPos, defineFound->second); + Log(Debug::Error) << "Undefined " << define; + return false; } } return true; @@ -122,7 +292,7 @@ namespace Shader // parse includes std::string source = buffer.str(); - if (!parseIncludes(boost::filesystem::path(mPath), source)) + if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(boost::filesystem::path(mPath), source)) return nullptr; templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; @@ -132,7 +302,7 @@ namespace Shader if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - if (!parseDefines(shaderSource, defines)) + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); @@ -164,11 +334,39 @@ namespace Shader return found->second; } + ShaderManager::DefineMap ShaderManager::getGlobalDefines() + { + return DefineMap(mGlobalDefines); + } + + void ShaderManager::setGlobalDefines(DefineMap & globalDefines) + { + mGlobalDefines = globalDefines; + for (auto shaderMapElement: mShaders) + { + std::string templateId = shaderMapElement.first.first; + ShaderManager::DefineMap defines = shaderMapElement.first.second; + osg::ref_ptr shader = shaderMapElement.second; + if (shader == nullptr) + // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. + continue; + std::string shaderSource = mShaderTemplates[templateId]; + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) + // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. + // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. + continue; + shader->setShaderSource(shaderSource); + } + } + void ShaderManager::releaseGLObjects(osg::State *state) { OpenThreads::ScopedLock lock(mMutex); for (auto shader : mShaders) - shader.second->releaseGLObjects(state); + { + if (shader.second != nullptr) + shader.second->releaseGLObjects(state); + } for (auto program : mPrograms) program.second->releaseGLObjects(state); } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 6b2e02124..05775edb6 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -8,6 +8,8 @@ #include +#include + #include namespace Shader @@ -32,11 +34,21 @@ namespace Shader osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + /// Get (a copy of) the DefineMap used to construct all shaders + DefineMap getGlobalDefines(); + + /// Set the DefineMap used to construct all shaders + /// @param defines The DefineMap to use + /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. + void setGlobalDefines(DefineMap & globalDefines); + void releaseGLObjects(osg::State* state); private: std::string mPath; + DefineMap mGlobalDefines; + // typedef std::map TemplateMap; TemplateMap mShaderTemplates; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a0a6832d0..3080e1318 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -23,8 +23,7 @@ namespace Shader ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) - , mColorMaterial(false) - , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mColorMode(0) , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) @@ -217,6 +216,13 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } + + if (diffuseMap) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); + } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); @@ -230,8 +236,29 @@ namespace Shader mRequirements.back().mMaterialOverridden = true; const osg::Material* mat = static_cast(it->second.first.get()); - mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); - mRequirements.back().mVertexColorMode = mat->getColorMode(); + + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case GL_AMBIENT: + colorMode = 3; + break; + default: + case GL_AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case GL_EMISSION: + colorMode = 1; + break; + } + + mRequirements.back().mColorMode = colorMode; } } } @@ -272,30 +299,13 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } - if (!reqs.mColorMaterial) - defineMap["colorMode"] = "0"; - else - { - switch (reqs.mVertexColorMode) - { - case GL_AMBIENT: - defineMap["colorMode"] = "3"; - break; - default: - case GL_AMBIENT_AND_DIFFUSE: - defineMap["colorMode"] = "2"; - break; - case GL_EMISSION: - defineMap["colorMode"] = "1"; - break; - } - } - defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; defineMap["clamp"] = mClampLighting ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index cb0538d9d..8737baf59 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -81,9 +81,8 @@ namespace Shader bool mShaderRequired; - bool mColorMaterial; - // osg::Material::ColorMode - int mVertexColorMode; + int mColorMode; + bool mMaterialOverridden; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index a0f051524..83684b5c9 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -218,7 +218,6 @@ namespace Terrain defineMap["clamp"] = clampLighting ? "1" : "0"; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = !firstLayer ? "1" : "0"; - defineMap["colorMode"] = "2"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; @@ -231,6 +230,7 @@ namespace Terrain } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->addUniform(new osg::Uniform("colorMode", 2)); } else { diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 2222cbb84..4b4df4365 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -134,7 +134,7 @@ ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv) { osgUtil::CullVisitor* cv = static_cast(&nv); ViewData* vd = mViewDataMap->getViewData(cv->getCurrentCamera()); - vd->setEyePoint(nv.getEyePoint()); + vd->setEyePoint(nv.getViewPoint()); return vd; } else // INTERSECTION_VISITOR diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 40f7a6fb0..6ec89721a 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -332,7 +333,19 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunk void QuadTreeWorld::accept(osg::NodeVisitor &nv) { if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) + { + if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) + { + if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos) + { + SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast(&nv); + clsb->apply(*this); + } + else + nv.apply(*mRootNode); + } return; + } ViewData* vd = mRootNode->getView(nv); @@ -350,7 +363,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) traverseToCell(mRootNode.get(), vd, x,y); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getEyePoint(), true); + traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true); } else mRootNode->traverse(nv); diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index f3080e31c..f216bb33b 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -47,10 +47,22 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); + if (cv->getComputeNearFarMode() && bb.valid()) + { + if (!cv->updateCalculatedNearFar(matrix, *this, false)) + return; + } + float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f; if (osg::isNaN(depth)) return; + if (cv->getCurrentCamera()->getName() == "ShadowCamera") + { + cv->addDrawableAndDepth(this, &matrix, depth); + return; + } + bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index f7c86b567..b4bf1d2d3 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -47,6 +47,7 @@ The ranges included with each setting are the physically possible ranges, not re game general shaders + shadows input saves sound diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 321c28afd..51c72e15d 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -120,3 +120,16 @@ If this setting is true, moving the mouse away from the player will look down, while moving it towards the player will look up. This setting does not affect cursor movement in GUI mode. This setting can be toggled in game with the Invert Y Axis button in the Controls panel of the Options menu. + +enable controller +----------------- + +:Type: boolean +:Range: True/False +:Default: True + +Enable support of controller input — or rather not ignore controller events, +which are always sent if a controller is present and detected. +Disabling this setting can be useful for working around controller-related issues or for setting up split-screen gameplay configurations. + +This setting can be toggled in game with the Enable Joystick button in the Controls panel of the Options menu. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b36f64285..b8a3b45b9 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -1,6 +1,7 @@ Shaders Settings ################ +.. _force-shaders-label: force shaders ------------- diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst new file mode 100644 index 000000000..c8d3250a0 --- /dev/null +++ b/docs/source/reference/modding/settings/shadows.rst @@ -0,0 +1,205 @@ +Shadow Settings +############### + +Main settings +************* + +enable shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the rendering of shadows. +Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can have a performance impact, but has more realistic results. +Bear in mind that this will force OpenMW to use shaders as if :ref:`force-shaders-label` was enabled. +A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post `_, but it may be more difficult than it seems. + + +number of shadow maps +--------------------- + +:Type: integer +:Range: 1 to 8, but higher values may conflict with other texture effects +:Default: 3 + +Control how many shadow maps to use - more of these means each shadow map texel covers less area, producing better-looking shadows, but may decrease performance. +Using too many shadow maps will lead to them overriding texture slots used for other effects, producing unpleasant artefacts. +A value of three is recommended in most cases, but other values may produce better results or performance. + +allow shadow map overlap +------------------------ + +:Type: boolean +:Range: True/False +:Default: True + +If true, allow shadow maps to overlap. +Counter-intuitively, will produce much better results when the light is behind the camera. +When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. + +enable debug hud +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug hud to see what the shadow map(s) contain. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +enable debug overlay +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug overlay to see the area covered by each shadow map. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +compute tight scene bounds +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. +This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value. +The performance impact of this may be very large. + +shadow map resolution +--------------------- + +:Type: integer +:Range: Dependent on GPU/driver combination +:Default: 1024 + +Control How large to make the shadow map(s). +Higher values increase GPU load but can produce better-looking results. +Power-of-two values may turn out to be faster than smaller values which are not powers of two on some GPU/driver combinations. + +actor shadows +------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow actors to cast shadows. +Potentially decreases performance. + +player shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow the player to cast shadows. +Potentially decreases performance. + +terrain shadows +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow terrain to cast shadows. +Potentially decreases performance. + +object shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow static objects to cast shadows. +Potentially decreases performance. + +enable indoor shadows +--------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow shadows indoors. +Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere. +Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely. + +Expert settings +*************** + +These settings are probably too complicated for regular users to judge what might be good values to set them to. +If you've got a good understanding of how shadow mapping works, or you've got enough time to try a large set of values, you may get better results tuning these yourself. +Copying values from another user who's done careful tuning is the recommended way of arriving at an optimal value for these settings. + +Understanding what some of these do might be easier for people who've read `this paper on Parallel Split Shadow Maps `_ and understood how they interact with the transformation used with Light Space Perspective Shadow Maps. + +polygon offset factor +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values just above 1.0 are most sensible. +:Default: 1.1 + +Used as the factor parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +polygon offset units +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values between 1 and 10 are most sensible. +:Default: 4.0 + +Used as the units parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +use front face culling +---------------------- + +:Type: boolean +:Range: True/False +:Default: True + +Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. +In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. + +split point uniform logarithmic ratio +------------------------------------- + +:Type: float +:Range: 0.0-1.0 for sensible results. Other values may 'work' but could behave bizarrely. +:Default: 0.5 + +Controls the ratio of :math:`C_i^{log}` versus :math:`C_i^{uniform}` used to form the Practical Split Scheme as described in the linked paper. +When using a larger-than-default viewing distance and distant terrain, and you have `allow shadow map overlap`_ enabled, larger values will prevent nearby shadows losing quality. +It is therefore recommended that this isn't left at the default when the viewing distance is changed. + +split point bias +---------------- + +:Type: float +:Range: Any value supported by C++ floats on your platform, although undesirable behaviour is more likely to appear the further the value is from zero. +:Default: 0.0 + +The :math:`\delta_{bias}` parameter used to form the Practical Split Scheme as described in the linked paper. + +minimum lispsm near far ratio +----------------------------- + +:Type: float +:Range: Must be greater than zero. +:Default: 0.25 + +Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. +Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. +Increasing this pushes detail further away by moving the frustum apex further from the near plane. \ No newline at end of file diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8062980ff..2d9c13bc9 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -222,21 +222,19 @@ - - - + - + - + - + @@ -246,14 +244,24 @@ - + - + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 71a93d1a2..a6616ecc1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -339,6 +339,9 @@ invert x axis = false # Invert the vertical axis while not in GUI mode. invert y axis = false +# Enable controller support. +enable controller = true + [Saves] # Name of last character played, and default for loading save files. @@ -542,6 +545,10 @@ companion h = 0.63 [Navigator] +# Enable navigator (true, false). When enabled background threads are started to build navmesh for world geometry. +# Pathfinding system uses navmesh to build paths. When disabled only pathgrid is used to build paths. +enable = true + # Scale of NavMesh coordinates to world coordinates (value > 0.0). Recastnavigation builds voxels for world geometry. # Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size # "recast scale factor" / "cell size". Default value calculates by this equation: @@ -643,3 +650,62 @@ enable nav mesh render = false # Render agents paths (true, false) enable agents paths render = false + +[Shadows] + +# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. +enable shadows = false + +# How many shadow maps to use - more of these means each shadow map texel covers less area, producing better looking shadows, but may decrease performance. +number of shadow maps = 3 + +# If true, allow shadow maps to overlap. Counter-intuitively, will produce better results when the light is behind the camera. When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. +allow shadow map overlap = true + +# Indirectly controls where to split the shadow map(s). Values closer to 1.0 bring more detail closer to the camera (potentially excessively so), and values closer to 0.0 spread it more evenly across the whole viewing distance. 0.5 is recommended for most viewing distances by the original Parallel Split Shadow Maps paper, but this does not take into account use of a Light Space Perspective transformation, so other values may be preferable. If some of the terms used here go over your head, you might not want to change this, especially not without reading the associated papers first. When "allow shadow map overlap" is combined with a higher-than-default viewing distance, values closer to 1.0 will prevent nearby shadows losing a lot of quality. +split point uniform logarithmic ratio = 0.5 + +# Indirectly controls where to split the shadow map(s). Positive values move split points away from the camera and negative values move them towards the camera. Intended to be used in conjunction with changes to 'split point uniform logarithmic ratio' to counteract side effects, but may cause additional, more serious side effects. Read the Parallel Split Shadow Maps paper by F Zhang et al before changing. +split point bias = 0.0 + +# Enable the debug hud to see what the shadow map(s) contain. +enable debug hud = false + +# Enable the debug overlay to see where each shadow map affects. +enable debug overlay = false + +# Attempt to better use the shadow map by making them cover a smaller area. Especially helpful when looking downwards with a high viewing distance. The performance impact of this may be very large. +compute tight scene bounds = false + +# How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. +shadow map resolution = 1024 + +# Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. Increasing this pushes detail further away. +minimum lispsm near far ratio = 0.25 + +# Used as the factor parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset factor = 1.1 + +# Used as the units parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset units = 4.0 + +# How far along the surface normal to project shadow coordinates. Higher values significantly reduce shadow flicker, usually with a lower increase of Peter Panning than the Polygon Offset settings. This value is in in-game units, so 1.0 is roughly 1.4 cm. +normal offset distance = 1.0 + +# Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. +use front face culling = true + +# Allow actors to cast shadows. Potentially decreases performance. +actor shadows = false + +# Allow the player to cast shadows. Potentially decreases performance. +player shadows = false + +# Allow terrain to cast shadows. Potentially decreases performance. +terrain shadows = false + +# Allow world objects to cast shadows. Potentially decreases performance. +object shadows = false + +# Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting. +enable indoor shadows = true diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 7baca78ef..8012c2bc1 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -18,6 +18,10 @@ set(SHADER_FILES parallax.glsl s360_fragment.glsl s360_vertex.glsl + shadows_vertex.glsl + shadows_fragment.glsl + shadowcasting_vertex.glsl + shadowcasting_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 06eebea68..90d849472 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,38 +1,66 @@ #define MAX_LIGHTS 8 -vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +uniform int colorMode; + +void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient) { vec3 lightDir; - float d; + float lightDistance; -#if @colorMode == 3 - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = vertexColor.xyz; -#elif @colorMode == 2 - vec4 diffuse = vertexColor; - vec3 ambient = vertexColor.xyz; + lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w); + lightDistance = length(lightDir); + lightDir = normalize(lightDir); + float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + + ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination; + diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination; +} + +#if PER_PIXEL_LIGHTING +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing) #else - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = gl_FrontMaterial.ambient.xyz; +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) #endif +{ + vec4 diffuse; + vec3 ambient; + if (colorMode == 3) + { + diffuse = gl_FrontMaterial.diffuse; + ambient = vertexColor.xyz; + } + else if (colorMode == 2) + { + diffuse = vertexColor; + ambient = vertexColor.xyz; + } + else + { + diffuse = gl_FrontMaterial.diffuse; + ambient = gl_FrontMaterial.ambient.xyz; + } vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + vec3 diffuseLight, ambientLight; + perLight(ambientLight, diffuseLight, 0, viewPos, viewNormal, diffuse, ambient); +#if PER_PIXEL_LIGHTING + lightResult.xyz += diffuseLight * shadowing - diffuseLight; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#else + shadowDiffuse = diffuseLight; + lightResult.xyz -= shadowDiffuse; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#endif for (int i=0; i non-linear depth value and returns <0,1> linearized value @@ -163,7 +165,7 @@ void main(void) vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; UV.y *= -1.0; - float shadow = 1.0; + float shadow = unshadowedLightRatio(); vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; screenCoords.y = (1.0-screenCoords.y); @@ -288,4 +290,6 @@ void main(void) #else gl_FragData[0].w = clamp(fresnel*6.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif + + applyShadowDebugOverlay(); } diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 7d7b7b18a..575f8f3c2 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -4,14 +4,16 @@ varying vec3 screenCoordsPassthrough; varying vec4 position; varying float depthPassthrough; +#include "shadows_vertex.glsl" + void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, -0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); vec4 texcoordProj = ((scalemat) * ( gl_Position)); screenCoordsPassthrough = vec3(texcoordProj.x, texcoordProj.y, texcoordProj.w); @@ -19,4 +21,6 @@ void main(void) position = gl_Vertex; depthPassthrough = gl_Position.z; + + setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); }