From 6f22d819c51fbd9499952507caaa34bac6c4686d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 31 Aug 2017 12:36:54 +0400 Subject: [PATCH 01/18] Do not allow to switch weapon to probe or lockpick during attack --- apps/openmw/mwclass/lockpick.cpp | 11 +++++++++++ apps/openmw/mwclass/lockpick.hpp | 2 ++ apps/openmw/mwclass/probe.cpp | 11 +++++++++++ apps/openmw/mwclass/probe.hpp | 2 ++ apps/openmw/mwgui/quickkeysmenu.cpp | 5 +++-- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index b8f6f5ecb..15499c1a8 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -155,6 +156,16 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + std::pair Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + { + // Do not allow equip tools from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + + return std::make_pair(1, ""); + } + bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index efa675c95..1bcf7fb85 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -51,6 +51,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; + virtual std::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ecaa056f9..030ee3f8b 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -155,6 +156,16 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + std::pair Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + { + // Do not allow equip tools from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + + return std::make_pair(1, ""); + } + bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index f67d8af86..9ac3ab0c9 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -51,6 +51,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; + virtual std::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 4e4462409..e912193bf 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -344,16 +344,17 @@ namespace MWGui { MWWorld::Ptr item = *button->getUserData(); bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); + bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); // delay weapon switching if player is busy - if (isDelayNeeded && isWeapon) + if (isDelayNeeded && (isWeapon || isTool)) { mActivatedIndex = index; return; } // disable weapon switching if player is dead or paralyzed - if (isReturnNeeded && isWeapon) + if (isReturnNeeded && (isWeapon || isTool)) { return; } From 91dcd5bc6e6de5ee25231c9b64e75206dba3ddb7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 31 Aug 2017 13:28:09 +0400 Subject: [PATCH 02/18] Do not allow to unequip weapon from inventory during attack --- apps/openmw/mwgui/inventorywindow.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 4cba7a0ae..6a2d3ff83 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -255,6 +255,19 @@ namespace MWGui } } + // If we unequip weapon during attack, it can lead to unexpected behaviour + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) + { + bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name(); + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + + if (isWeapon && invStore.isEquipped(item.mBase)) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}"); + return; + } + } + if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); From b9931fb71cb028aa8f8bb9eefb611ecb23ec096e Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Thu, 31 Aug 2017 21:39:21 +0000 Subject: [PATCH 03/18] Set the shader on the node containing the StateSet --- components/shader/shadervisitor.cpp | 21 +++++++++++---------- components/shader/shadervisitor.hpp | 7 +++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index d85b72203..2d5fdfb39 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -26,6 +26,7 @@ namespace Shader , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mNode(NULL) { } @@ -69,7 +70,7 @@ namespace Shader { if (node.getStateSet()) { - pushRequirements(); + pushRequirements(node); applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); @@ -234,9 +235,10 @@ namespace Shader } } - void ShaderVisitor::pushRequirements() + void ShaderVisitor::pushRequirements(osg::Node& node) { mRequirements.push_back(mRequirements.back()); + mRequirements.back().mNode = &node; } void ShaderVisitor::popRequirements() @@ -244,8 +246,9 @@ namespace Shader mRequirements.pop_back(); } - void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) + void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { + osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = NULL; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); @@ -305,9 +308,9 @@ namespace Shader void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = (geometry.getStateSet() != NULL); - if (geometry.getStateSet()) + if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { - pushRequirements(); + pushRequirements(geometry); applyStateSet(geometry.getStateSet(), geometry); } @@ -350,9 +353,8 @@ namespace Shader rig->setSourceGeometry(sourceGeometry); } - // TODO: find a better place for the stateset if (useShader) - createProgram(reqs, geometry); + createProgram(reqs); } if (needPop) @@ -366,16 +368,15 @@ namespace Shader if (drawable.getStateSet()) { - pushRequirements(); + pushRequirements(drawable); applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); - // TODO: find a better place for the stateset if (reqs.mShaderRequired || mForceShaders) - createProgram(reqs, drawable); + createProgram(reqs); } if (needPop) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 8f4597ff3..83f28e063 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -52,7 +52,7 @@ namespace Shader void applyStateSet(osg::ref_ptr stateset, osg::Node& node); - void pushRequirements(); + void pushRequirements(osg::Node& node); void popRequirements(); private: @@ -89,13 +89,16 @@ namespace Shader // -1 == no tangents required int mTexStageRequiringTangents; + + // the Node that requested these requirements + osg::Node* mNode; }; std::vector mRequirements; std::string mDefaultVsTemplate; std::string mDefaultFsTemplate; - void createProgram(const ShaderRequirements& reqs, osg::Node& node); + void createProgram(const ShaderRequirements& reqs); }; } From 45f7563a5579bcf3674cc44c5d9d3bd80b1c0e80 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Thu, 31 Aug 2017 21:40:35 +0000 Subject: [PATCH 04/18] Revert "Revert "Check for a Geometry node when attaching bodyparts"" Issue with shaders has been fixed with b9931fb71cb028aa8f8bb9eefb611ecb23ec096e This reverts commit a1e3fb7604a11307ed08f1ec84b31a359cb5ead5. --- components/nifosg/nifloader.cpp | 3 +++ components/sceneutil/attach.cpp | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 78186c439..b7d1c15b8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1130,6 +1130,8 @@ namespace NifOsg triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); } + geometry->setName(triShape->name); + if (geometry->getDataVariance() == osg::Object::DYNAMIC) { // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch @@ -1219,6 +1221,7 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); + rig->setName(triShape->name); const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 1385f771e..8634a4df6 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -32,29 +32,29 @@ namespace SceneUtil virtual void apply(osg::MatrixTransform& node) { - applyNode(node); - } - virtual void apply(osg::Geometry& node) - { - applyNode(node); + traverse(node); } virtual void apply(osg::Node& node) { - applyNode(node); + traverse(node); } virtual void apply(osg::Group& node) { - applyNode(node); + traverse(node); } - void applyNode(osg::Node& node) + virtual void apply(osg::Geometry& geom) { - std::string lowerName = Misc::StringUtils::lowerCase(node.getName()); + std::string lowerName = Misc::StringUtils::lowerCase(geom.getName()); if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) - mToCopy.push_back(&node); - else - traverse(node); + { + osg::Node* node = &geom; + while (node && node->getNumParents() && !node->getStateSet()) + node = node->getParent(0); + if (node) + mToCopy.push_back(node); + } } void doCopy() From a8005c33d9ea82ffa49c30f40022908f7215261e Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Thu, 31 Aug 2017 21:57:26 +0000 Subject: [PATCH 05/18] Revert unintended change to mOnGround variable that was somehow introduced with 38a2de3c51edd808e5a3acba9e94c8ed11d227dd --- apps/openmw/mwphysics/actor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e5e36def8..79c6dcabf 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -18,7 +18,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(false), mOnSlope(false) + , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mCollisionWorld(world) From 0d7279ea2a6086232bc8235597924579f1aecd3f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Sep 2017 08:59:05 +0400 Subject: [PATCH 06/18] Fixes a regression with bound weapons equipping (bug #4050) --- apps/openmw/mwclass/weapon.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 62a9b6d0f..9fb4a9767 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -374,7 +374,9 @@ namespace MWClass if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) + // Do not allow equip weapons from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) return std::make_pair(0, "#{sCantEquipWeapWarning}"); std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); From a5b01fefec97ca69e07872034853f2bd7f24b9c4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Sep 2017 09:34:15 +0400 Subject: [PATCH 07/18] Allow to interrupt an attack, if attack button is held --- apps/openmw/mwbase/mechanicsmanager.hpp | 1 + apps/openmw/mwinput/inputmanagerimp.cpp | 6 +++++- apps/openmw/mwmechanics/actors.cpp | 10 ++++++++++ apps/openmw/mwmechanics/actors.hpp | 1 + apps/openmw/mwmechanics/character.cpp | 6 ++++++ apps/openmw/mwmechanics/character.hpp | 1 + apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +++++ apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 ++ 8 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e1059d06..84d43156e 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -247,6 +247,7 @@ namespace MWBase virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; + virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; }; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5f3e3152d..e4fa8fc2b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -948,7 +948,11 @@ namespace MWInput if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) + // We want to interrupt animation only if attack is prepairing, but still is not triggered + // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice + if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer())) + mPlayer->setAttackingOrSpell(false); + else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index fba8d1a4d..ed510e616 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -802,6 +802,16 @@ namespace MWMechanics } } + bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr) + { + PtrActorMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isAttackPrepairing(); + } + bool Actors::isRunning(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 052bf4920..6eb3a2955 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -107,6 +107,7 @@ namespace MWMechanics int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. + bool isAttackPrepairing(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e9f42476b..51dc37e18 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2218,6 +2218,12 @@ void CharacterController::setAttackTypeBasedOnMovement() mAttackType = "chop"; } +bool CharacterController::isAttackPrepairing() const +{ + return mUpperBodyState == UpperCharState_StartToMinAttack || + mUpperBodyState == UpperCharState_MinAttackToMaxAttack; +} + bool CharacterController::isReadyToBlock() const { return updateCarriedLeftVisible(mWeaponType); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index bde64cdfb..9bcad0994 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -263,6 +263,7 @@ public: void forceStateUpdate(); + bool isAttackPrepairing() const; bool isReadyToBlock() const; bool isKnockedOut() const; bool isSneaking() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 83780e9f8..5f3dd58af 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -423,6 +423,11 @@ namespace MWMechanics mObjects.update(duration, paused); } + bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr) + { + return mActors.isAttackPrepairing(ptr); + } + bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) { return mActors.isRunning(ptr); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 47dc6e6a2..adad21916 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -210,8 +210,10 @@ namespace MWMechanics virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count); + virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr); virtual bool isRunning(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr); + private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); From 7f5f5458d448fbc8eaa422047d14d6c0762b6733 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Sep 2017 18:03:23 +0400 Subject: [PATCH 08/18] Reset player attack animation when unequip weapon or tool --- apps/openmw/mwrender/npcanimation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 73927e0ab..e4d0abf7b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -23,6 +23,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" @@ -919,6 +920,9 @@ void NpcAnimation::showWeapons(bool showWeapon) else { removeIndividualPart(ESM::PRT_Weapon); + // If we remove/hide weapon from player, we should reset attack animation as well + if (mPtr == MWMechanics::getPlayer()) + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } } From 132ac6001b36c91f59465204f8e799c32346ae89 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 18:24:48 +0200 Subject: [PATCH 09/18] Fix bug of Animation::mSkeleton not being assigned This bug resulted in deactivating a Skeleton not working properly for creatures (that are out of processing range), therefore reduced performance. --- apps/openmw/mwrender/animation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 98d29bfc9..22bafaa71 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1192,6 +1192,9 @@ namespace MWRender mObjectRoot->addChild(created); mInsert->addChild(mObjectRoot); } + osg::ref_ptr skel = dynamic_cast(mObjectRoot.get()); + if (skel) + mSkeleton = skel.get(); } else { From 209e139aa89015cad87b39dc160f9a5af0998040 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 18:27:00 +0200 Subject: [PATCH 10/18] Move double buffering implementation inside RigGeometry The double buffering is an implementation detail so it should be handled as such, rather than mandating the scene graph to be structured in a certain way. Override accept(NodeVisitor&) instead of using callbacks. --- components/nifosg/nifloader.cpp | 13 +- components/sceneutil/attach.cpp | 6 +- components/sceneutil/riggeometry.cpp | 193 +++++++++++---------------- components/sceneutil/riggeometry.hpp | 20 +-- components/sceneutil/serialize.cpp | 2 +- components/sceneutil/skeleton.cpp | 17 +-- components/sceneutil/skeleton.hpp | 2 - 7 files changed, 97 insertions(+), 156 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index b7d1c15b8..6b603cc9e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1236,7 +1236,6 @@ namespace NifOsg SceneUtil::RigGeometry::BoneInfluence influence; const std::vector &weights = data->bones[i].weights; - //influence.mWeights.reserve(weights.size()); for(size_t j = 0;j < weights.size();j++) { std::pair indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); @@ -1249,17 +1248,7 @@ namespace NifOsg } rig->setInfluenceMap(map); - // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch - // This is so we can set the DataVariance as STATIC, giving a huge performance boost - rig->setDataVariance(osg::Object::STATIC); - - osg::ref_ptr frameswitch = new FrameSwitch; - - SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); - frameswitch->addChild(rig); - frameswitch->addChild(rig2); - - parentNode->addChild(frameswitch); + parentNode->addChild(rig); } osg::BlendFunc::BlendFuncMode getBlendMode(int mode) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 8634a4df6..1df1be27f 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -43,13 +43,13 @@ namespace SceneUtil traverse(node); } - virtual void apply(osg::Geometry& geom) + virtual void apply(osg::Drawable& drawable) { - std::string lowerName = Misc::StringUtils::lowerCase(geom.getName()); + std::string lowerName = Misc::StringUtils::lowerCase(drawable.getName()); if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) { - osg::Node* node = &geom; + osg::Node* node = &drawable; while (node && node->getNumParents() && !node->getStateSet()) node = node->getParent(0); if (node) diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 9adfdcddb..ec01f62d0 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -10,73 +10,17 @@ namespace SceneUtil { -class UpdateRigBounds : public osg::Drawable::UpdateCallback -{ -public: - UpdateRigBounds() - { - } - - UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop) - : osg::Drawable::UpdateCallback(copy, copyop) - { - } - - META_Object(SceneUtil, UpdateRigBounds) - - void update(osg::NodeVisitor* nv, osg::Drawable* drw) - { - RigGeometry* rig = static_cast(drw); - - rig->updateBounds(nv); - } -}; - -// TODO: make threadsafe for multiple cull threads -class UpdateRigGeometry : public osg::Drawable::CullCallback -{ -public: - UpdateRigGeometry() - { - } - - UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop) - : osg::Drawable::CullCallback(copy, copyop) - { - } - - META_Object(SceneUtil, UpdateRigGeometry) - - virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const - { - RigGeometry* geom = static_cast(drw); - geom->update(nv); - return false; - } -}; - -// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix. -// So we return nothing. Bounds are updated every frame in the UpdateCallback. -class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback -{ -public: - virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); } -}; - RigGeometry::RigGeometry() : mSkeleton(NULL) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { - setCullCallback(new UpdateRigGeometry); - setUpdateCallback(new UpdateRigBounds); - setSupportsDisplayList(false); - setUseVertexBufferObjects(true); - setComputeBoundingBoxCallback(new DummyComputeBoundCallback); + setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct + // update done in accept(NodeVisitor&) } RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) - : osg::Geometry(copy, copyop) + : Drawable(copy, copyop) , mSkeleton(NULL) , mInfluenceMap(copy.mInfluenceMap) , mLastFrameNumber(0) @@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) { mSourceGeometry = sourceGeometry; - osg::Geometry& from = *sourceGeometry; - - if (from.getStateSet()) - setStateSet(from.getStateSet()); - - // shallow copy primitive sets & vertex attributes that we will not modify - setPrimitiveSetList(from.getPrimitiveSetList()); - setColorArray(from.getColorArray()); - setSecondaryColorArray(from.getSecondaryColorArray()); - setFogCoordArray(from.getFogCoordArray()); - - // need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5 - osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList(); - for (unsigned int i=0; i vbo (new osg::VertexBufferObject); - vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); - if (vertexArray) - { - vertexArray->setVertexBufferObject(vbo); - setVertexArray(vertexArray); - } - - if (osg::Array* normals = from.getNormalArray()) + for (unsigned int i=0; i<2; ++i) { - osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); - if (normalArray) + osg::Geometry& from = *sourceGeometry; + mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); + osg::Geometry& to = *mGeometry[i]; + to.setSupportsDisplayList(false); + to.setUseVertexBufferObjects(true); + to.setCullingActive(false); // make sure to disable culling since that's handled by this class + + // 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. + osg::ref_ptr vbo (new osg::VertexBufferObject); + vbo->setUsage(GL_DYNAMIC_DRAW_ARB); + + osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + if (vertexArray) { - normalArray->setVertexBufferObject(vbo); - setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); + vertexArray->setVertexBufferObject(vbo); + to.setVertexArray(vertexArray); } - } + if (osg::Array* normals = from.getNormalArray()) + { + osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); + if (normalArray) + { + normalArray->setVertexBufferObject(vbo); + to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); + } + } - if (osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) - { - mSourceTangents = tangents; - osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); - tangentArray->setVertexBufferObject(vbo); - setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); + if (osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) + { + mSourceTangents = tangents; + osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); + tangentArray->setVertexBufferObject(vbo); + to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); + } + else + mSourceTangents = NULL; } - else - mSourceTangents = NULL; } osg::ref_ptr RigGeometry::getSourceGeometry() @@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat ptrresult[14] += ptr[14] * weight; } -void RigGeometry::update(osg::NodeVisitor* nv) +void RigGeometry::cull(osg::NodeVisitor* nv) { if (!mSkeleton) { @@ -238,23 +172,24 @@ void RigGeometry::update(osg::NodeVisitor* nv) return; } - if (!mSkeleton->getActive() && mLastFrameNumber != 0) - return; - - if (mLastFrameNumber == nv->getTraversalNumber()) + if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber()) + { + nv->apply(*getGeometry(mLastFrameNumber)); return; + } mLastFrameNumber = nv->getTraversalNumber(); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); // skinning - osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); - osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); - osg::Vec4Array* tangentSrc = mSourceTangents; + const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + const osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); + const osg::Vec4Array* tangentSrc = mSourceTangents; - osg::Vec3Array* positionDst = static_cast(getVertexArray()); - osg::Vec3Array* normalDst = static_cast(getNormalArray()); - osg::Vec4Array* tangentDst = static_cast(getTexCoordArray(7)); + osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); + osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); + osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) { @@ -294,6 +229,10 @@ void RigGeometry::update(osg::NodeVisitor* nv) normalDst->dirty(); if (tangentDst) tangentDst->dirty(); + + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); } void RigGeometry::updateBounds(osg::NodeVisitor *nv) @@ -365,5 +304,29 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) mInfluenceMap = influenceMap; } +void RigGeometry::accept(osg::NodeVisitor &nv) +{ + if (!nv.validNodeMask(*this)) + return; + + nv.pushOntoNodePath(this); + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(&nv); + else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + updateBounds(&nv); + else if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR) + nv.apply(*getGeometry(mLastFrameNumber)); + else + nv.apply(*this); + + nv.popFromNodePath(); +} + +osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const +{ + return mGeometry[frame%2].get(); +} + } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 39829bcb0..097e7ea69 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -13,10 +13,9 @@ namespace SceneUtil /// @brief Mesh skinning implementation. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. - /// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done - /// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing - /// the double buffering inside RigGeometry. - class RigGeometry : public osg::Geometry + /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while + /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. + class RigGeometry : public osg::Drawable { public: RigGeometry(); @@ -24,6 +23,9 @@ namespace SceneUtil META_Object(SceneUtil, RigGeometry) + // At this point compileGLObjects() remains unimplemented, hard to avoid race conditions + // and there is limited value in compiling anyway since the data will change again for the next frame + struct BoneInfluence { osg::Matrixf mInvBindMatrix; @@ -45,13 +47,15 @@ namespace SceneUtil osg::ref_ptr getSourceGeometry(); - // Called automatically by our CullCallback - void update(osg::NodeVisitor* nv); + virtual void accept(osg::NodeVisitor &nv); - // Called automatically by our UpdateCallback + private: + void cull(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv); - private: + osg::ref_ptr mGeometry[2]; + osg::Geometry* getGeometry(unsigned int frame) const; + osg::ref_ptr mSourceGeometry; osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 64094275c..177d700b2 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -50,7 +50,7 @@ class RigGeometrySerializer : public osgDB::ObjectWrapper { public: RigGeometrySerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry") + : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry") { } }; diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 49bc5b70f..116edfdb4 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -38,8 +38,6 @@ Skeleton::Skeleton() , mNeedToUpdateBoneMatrices(true) , mActive(true) , mLastFrameNumber(0) - , mTraversedEvenFrame(false) - , mTraversedOddFrame(false) { } @@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) - , mTraversedEvenFrame(false) - , mTraversedOddFrame(false) { } @@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber) mLastFrameNumber = traversalNumber; - if (mLastFrameNumber % 2 == 0) - mTraversedEvenFrame = true; - else - mTraversedOddFrame = true; - if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) @@ -144,18 +135,14 @@ bool Skeleton::getActive() const void Skeleton::markDirty() { - mTraversedEvenFrame = false; - mTraversedOddFrame = false; + mLastFrameNumber = 0; mBoneCache.clear(); mBoneCacheInit = false; } void Skeleton::traverse(osg::NodeVisitor& nv) { - if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR - // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized - // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node - && mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame) + if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0) return; osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index 542471ff1..245e3522c 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -74,8 +74,6 @@ namespace SceneUtil bool mActive; unsigned int mLastFrameNumber; - bool mTraversedEvenFrame; - bool mTraversedOddFrame; }; } From 4c5992a0d5b58ca18ed00a116f33d78b9b12aeec Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 21:45:18 +0200 Subject: [PATCH 11/18] Warn if removing a node fails --- apps/openmw/mwrender/animation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 22bafaa71..df9b8545a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -182,7 +182,10 @@ namespace void remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - it->second->removeChild(it->first); + { + if (!it->second->removeChild(it->first)) + std::cerr << "error removing " << it->first->getName() << std::endl; + } } protected: From f1ebb129c139690450a74abbc7ae46674dcaef62 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 22:01:50 +0200 Subject: [PATCH 12/18] Fix ShaderVisitor to deal with the fact RigGeometry no longer derives from Geometry --- components/shader/shadervisitor.cpp | 81 +++++++++++++++-------------- components/shader/shadervisitor.hpp | 1 + 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2d5fdfb39..2c0b9f0e3 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -248,6 +248,9 @@ namespace Shader void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { + if (!reqs.mShaderRequired && !mForceShaders) + return; + osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = NULL; if (mAllowedToModifyStateSets) @@ -305,6 +308,36 @@ namespace Shader } } + bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) + { + bool useShader = reqs.mShaderRequired || mForceShaders; + bool generateTangents = reqs.mTexStageRequiringTangents != -1; + bool changed = false; + + if (mAllowedToModifyStateSets && (useShader || generateTangents)) + { + // make sure that all UV sets are there + for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) + { + if (sourceGeometry.getTexCoordArray(it->first) == NULL) + { + sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0)); + changed = true; + } + } + + if (generateTangents) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents); + + sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + changed = true; + } + } + return changed; + } + void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = (geometry.getStateSet() != NULL); @@ -318,43 +351,9 @@ namespace Shader { const ShaderRequirements& reqs = mRequirements.back(); - bool useShader = reqs.mShaderRequired || mForceShaders; - bool generateTangents = reqs.mTexStageRequiringTangents != -1; - - if (mAllowedToModifyStateSets && (useShader || generateTangents)) - { - osg::ref_ptr sourceGeometry = &geometry; - SceneUtil::RigGeometry* rig = dynamic_cast(&geometry); - if (rig) - sourceGeometry = rig->getSourceGeometry(); - - bool requiresSetGeometry = false; + adjustGeometry(geometry, reqs); - // make sure that all UV sets are there - for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) - { - if (sourceGeometry->getTexCoordArray(it->first) == NULL) - { - sourceGeometry->setTexCoordArray(it->first, sourceGeometry->getTexCoordArray(0)); - requiresSetGeometry = true; - } - } - - if (generateTangents) - { - osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); - generator->generate(sourceGeometry, reqs.mTexStageRequiringTangents); - - sourceGeometry->setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); - requiresSetGeometry = true; - } - - if (rig && requiresSetGeometry) - rig->setSourceGeometry(sourceGeometry); - } - - if (useShader) - createProgram(reqs); + createProgram(reqs); } if (needPop) @@ -375,8 +374,14 @@ namespace Shader if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); - if (reqs.mShaderRequired || mForceShaders) - createProgram(reqs); + createProgram(reqs); + + if (SceneUtil::RigGeometry* rig = dynamic_cast(&drawable)) + { + osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); + if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) + rig->setSourceGeometry(sourceGeometry); + } } if (needPop) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 83f28e063..cb0538d9d 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -99,6 +99,7 @@ namespace Shader std::string mDefaultFsTemplate; void createProgram(const ShaderRequirements& reqs); + bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; } From 5d524a6a10672c3800f5ac396272635ea1cc09a8 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 22:56:09 +0200 Subject: [PATCH 13/18] Add custom version of MorphGeometry replacing osgAnimation Double buffering, custom bounding box and the update in the cull visitor (instead of update) are now all handled internally rather than needing hacks and/or callbacks. --- CMakeLists.txt | 2 +- components/CMakeLists.txt | 2 +- components/nifosg/controller.cpp | 9 +- components/nifosg/controller.hpp | 7 +- components/nifosg/nifloader.cpp | 186 +++--------------------- components/sceneutil/clone.cpp | 38 +---- components/sceneutil/morphgeometry.cpp | 187 +++++++++++++++++++++++++ components/sceneutil/morphgeometry.hpp | 81 +++++++++++ components/sceneutil/serialize.cpp | 15 +- components/shader/shadervisitor.cpp | 9 +- 10 files changed, 308 insertions(+), 228 deletions(-) create mode 100644 components/sceneutil/morphgeometry.cpp create mode 100644 components/sceneutil/morphgeometry.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad2c64b6e..a78eac572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,7 +207,7 @@ if(NOT HAVE_STDINT_H) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6430dbfce..88de17903 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -49,7 +49,7 @@ add_component_dir (shader ) add_component_dir (sceneutil - clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller + clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer ) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 87a0b830e..262966e95 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -6,11 +6,10 @@ #include #include -#include - #include #include +#include #include "userdata.hpp" @@ -188,7 +187,7 @@ GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data) void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) { - osgAnimation::MorphGeometry* morphGeom = static_cast(drawable); + SceneUtil::MorphGeometry* morphGeom = static_cast(drawable); if (hasInput()) { if (mKeyFrames.size() <= 1) @@ -202,7 +201,7 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable val = it->interpKey(input); val = std::max(0.f, std::min(1.f, val)); - osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); @@ -210,8 +209,6 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable } } } - - // morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries } UVController::UVController() diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 5fbb172f9..0e87af44f 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -31,11 +31,6 @@ namespace osgParticle class Emitter; } -namespace osgAnimation -{ - class MorphGeometry; -} - namespace NifOsg { @@ -172,7 +167,7 @@ namespace NifOsg virtual float getMaximum() const; }; - /// Must be set on an osgAnimation::MorphGeometry. + /// Must be set on a SceneUtil::MorphGeometry. class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller { public: diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6b603cc9e..8810f171a 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -13,9 +13,6 @@ #include #include -// skel -#include - // particle #include #include @@ -39,6 +36,7 @@ #include #include #include +#include #include "particle.hpp" #include "userdata.hpp" @@ -83,35 +81,6 @@ namespace collectDrawableProperties(nifNode->parent, out); } - class FrameSwitch : public osg::Group - { - public: - FrameSwitch() - { - } - - FrameSwitch(const FrameSwitch& copy, const osg::CopyOp& copyop) - : osg::Group(copy, copyop) - { - } - - META_Object(NifOsg, FrameSwitch) - - virtual void traverse(osg::NodeVisitor& nv) - { - if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR) - osg::Group::traverse(nv); - else - { - for (unsigned int i=0; iaccept(nv); - } - } - } - }; - // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. // Must be set as a cull callback. @@ -154,70 +123,6 @@ namespace } }; - struct UpdateMorphGeometry : public osg::Drawable::CullCallback - { - UpdateMorphGeometry() - : mLastFrameNumber(0) - { - } - - UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop) - : osg::Drawable::CullCallback(copy, copyop) - , mLastFrameNumber(0) - { - } - - META_Object(NifOsg, UpdateMorphGeometry) - - virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const - { - osgAnimation::MorphGeometry* geom = static_cast(drw); - if (!geom) - return false; - - if (mLastFrameNumber == nv->getTraversalNumber()) - return false; - mLastFrameNumber = nv->getTraversalNumber(); - - geom->transformSoftwareMethod(); - return false; - } - - private: - mutable unsigned int mLastFrameNumber; - }; - - // Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box - // every time the morph weights change. To do so we return a maximum containing box that is big enough for all possible combinations of morph targets. - class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback - { - public: - StaticBoundingBoxCallback() - { - } - - StaticBoundingBoxCallback(const osg::BoundingBox& bounds) - : mBoundingBox(bounds) - { - } - - StaticBoundingBoxCallback(const StaticBoundingBoxCallback& copy, const osg::CopyOp& copyop) - : osg::Drawable::ComputeBoundingBoxCallback(copy, copyop) - , mBoundingBox(copy.mBoundingBox) - { - } - - META_Object(NifOsg, StaticBoundingBoxCallback) - - virtual osg::BoundingBox computeBound(const osg::Drawable&) const - { - return mBoundingBox; - } - - private: - osg::BoundingBox mBoundingBox; - }; - void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) @@ -1107,108 +1012,49 @@ namespace NifOsg void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - osg::ref_ptr geometry; + osg::ref_ptr drawable; for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { - geometry = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); + drawable = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController( static_cast(ctrl.getPtr())->data.getPtr()); setupController(ctrl.getPtr(), morphctrl, animflags); - geometry->setUpdateCallback(morphctrl); + drawable->setUpdateCallback(morphctrl); break; } } - if (!geometry.get()) + if (!drawable.get()) { - geometry = new osg::Geometry; - triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + osg::ref_ptr geom (new osg::Geometry); + drawable = geom; + triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags); } - geometry->setName(triShape->name); - - if (geometry->getDataVariance() == osg::Object::DYNAMIC) - { - // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch - // This is so we can set the DataVariance as STATIC, giving a huge performance boost - geometry->setDataVariance(osg::Object::STATIC); - osg::ref_ptr frameswitch = new FrameSwitch; + drawable->setName(triShape->name); - osg::ref_ptr geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); - frameswitch->addChild(geometry); - frameswitch->addChild(geom2); - - parentNode->addChild(frameswitch); - } - else - parentNode->addChild(geometry); + parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; - morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); - // No normals available in the MorphData - morphGeom->setMorphNormals(false); - - morphGeom->setUpdateCallback(NULL); - morphGeom->setCullCallback(new UpdateMorphGeometry); - morphGeom->setUseVertexBufferObjects(true); + osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); - - morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB); + osg::ref_ptr sourceGeometry (new osg::Geometry); + triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags); + morphGeom->setSourceGeometry(sourceGeometry); const std::vector& morphs = morpher->data.getPtr()->mMorphs; if (morphs.empty()) return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) - { - osg::ref_ptr morphTarget = new osg::Geometry; - morphTarget->setVertexArray(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0])); - morphGeom->addMorphTarget(morphTarget, 0.f); - } - - // build the bounding box containing all possible morph combinations - - std::vector vertBounds(morphs[0].mVertices.size()); - - // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. - // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. - - // Start with zero offsets which will happen when no morphs are applied. - for (unsigned int i=0; igetBound(); - - // Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating - morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); + morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f); return morphGeom; } diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 738c7a30d..08f36cfcf 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include @@ -49,46 +49,12 @@ namespace SceneUtil { if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) return operator()(partsys); - if (dynamic_cast(drawable)) - { - osg::CopyOp copyop = *this; - copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS); - -#if OSG_VERSION_LESS_THAN(3,5,0) - /* - - Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor: - - if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS)) - { - if (_useVertexBufferObjects) - { - // copying of arrays doesn't set up buffer objects so we'll need to force - // Geometry to assign these, we'll do this by switching off VBO's then renabling them. - setUseVertexBufferObjects(false); - setUseVertexBufferObjects(true); - } - } - - In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from, - causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation. - Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). - - */ - - copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES); -#endif - - osg::Drawable* cloned = osg::clone(drawable, copyop); - return cloned; - } - if (dynamic_cast(drawable)) + if (dynamic_cast(drawable) || dynamic_cast(drawable)) { return osg::clone(drawable, *this); } - return osg::CopyOp::operator()(drawable); } diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp new file mode 100644 index 000000000..5d55e75ec --- /dev/null +++ b/components/sceneutil/morphgeometry.cpp @@ -0,0 +1,187 @@ +#include "morphgeometry.hpp" + +#include + +namespace SceneUtil +{ + +MorphGeometry::MorphGeometry() + : mLastFrameNumber(0) + , mDirty(true) + , mMorphedBoundingBox(false) +{ + +} + +MorphGeometry::MorphGeometry(const MorphGeometry ©, const osg::CopyOp ©op) + : osg::Drawable(copy, copyop) + , mMorphTargets(copy.mMorphTargets) + , mLastFrameNumber(0) + , mDirty(true) + , mMorphedBoundingBox(false) +{ + setSourceGeometry(copy.getSourceGeometry()); +} + +void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) +{ + mSourceGeometry = sourceGeom; + + for (unsigned int i=0; i<2; ++i) + { + mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); + + osg::Geometry& from = *mSourceGeometry; + osg::Geometry& to = *mGeometry[i]; + to.setSupportsDisplayList(false); + to.setUseVertexBufferObjects(true); + to.setCullingActive(false); // make sure to disable culling since that's handled by this class + + // vertices 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. + osg::ref_ptr vbo (new osg::VertexBufferObject); + vbo->setUsage(GL_DYNAMIC_DRAW_ARB); + + osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + if (vertexArray) + { + vertexArray->setVertexBufferObject(vbo); + to.setVertexArray(vertexArray); + } + } +} + +void MorphGeometry::addMorphTarget(osg::Vec3Array *offsets, float weight) +{ + mMorphTargets.push_back(MorphTarget(offsets, weight)); + mMorphedBoundingBox = false; + dirty(); +} + +void MorphGeometry::dirty() +{ + mDirty = true; + if (!mMorphedBoundingBox) + { + _boundingBoxComputed = false; + dirtyBound(); + } +} + +osg::ref_ptr MorphGeometry::getSourceGeometry() const +{ + return mSourceGeometry; +} + +void MorphGeometry::accept(osg::NodeVisitor &nv) +{ + if (!nv.validNodeMask(*this)) + return; + + nv.pushOntoNodePath(this); + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(&nv); + else if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR) + nv.apply(*getGeometry(mLastFrameNumber)); + else + nv.apply(*this); + + nv.popFromNodePath(); +} + +osg::BoundingBox MorphGeometry::computeBoundingBox() const +{ + bool anyMorphTarget = false; + for (unsigned int i=0; i 0) + { + anyMorphTarget = true; + break; + } + + // before the MorphGeometry has started animating, we will use a regular bounding box (this is required + // for correct object placements, which uses the bounding box) + if (!mMorphedBoundingBox && !anyMorphTarget) + { + return mSourceGeometry->getBoundingBox(); + } + // once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating + else + { + mMorphedBoundingBox = true; + + osg::Vec3Array& sourceVerts = *static_cast(mSourceGeometry->getVertexArray()); + std::vector vertBounds(sourceVerts.size()); + + // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. + // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. + + // Start with zero offsets which will happen when no morphs are applied. + for (unsigned int i=0; igetTraversalNumber() || !mDirty) + { + nv->apply(*getGeometry(mLastFrameNumber)); + return; + } + + mDirty = false; + mLastFrameNumber = nv->getTraversalNumber(); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + + const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); + assert(positionSrc->size() == positionDst->size()); + for (unsigned int vertex=0; vertexsize(); ++vertex) + (*positionDst)[vertex] = (*positionSrc)[vertex]; + + for (unsigned int i=0; isize(); ++vertex) + (*positionDst)[vertex] += (*offsets)[vertex] * weight; + } + + positionDst->dirty(); + + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); +} + +osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const +{ + return mGeometry[frame%2]; +} + + +} diff --git a/components/sceneutil/morphgeometry.hpp b/components/sceneutil/morphgeometry.hpp new file mode 100644 index 000000000..1afef1895 --- /dev/null +++ b/components/sceneutil/morphgeometry.hpp @@ -0,0 +1,81 @@ +#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H +#define OPENMW_COMPONENTS_MORPHGEOMETRY_H + +#include + +namespace SceneUtil +{ + + /// @brief Vertex morphing implementation. + /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while + /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. + class MorphGeometry : public osg::Drawable + { + public: + MorphGeometry(); + MorphGeometry(const MorphGeometry& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, MorphGeometry) + + /// Initialize this geometry from the source geometry. + /// @note The source geometry will not be modified. + void setSourceGeometry(osg::ref_ptr sourceGeom); + + class MorphTarget + { + protected: + osg::ref_ptr mOffsets; + float mWeight; + public: + MorphTarget(osg::Vec3Array* offsets, float w = 1.0) : mOffsets(offsets), mWeight(w) {} + void setWeight(float weight) { mWeight = weight; } + float getWeight() const { return mWeight; } + osg::Vec3Array* getOffsets() { return mOffsets.get(); } + const osg::Vec3Array* getOffsets() const { return mOffsets.get(); } + void setOffsets(osg::Vec3Array* offsets) { mOffsets = offsets; } + }; + + typedef std::vector MorphTargetList; + + virtual void addMorphTarget( osg::Vec3Array* offsets, float weight = 1.0 ); + + /** Set the MorphGeometry dirty.*/ + void dirty(); + + /** Get the list of MorphTargets.*/ + const MorphTargetList& getMorphTargetList() const { return mMorphTargets; } + + /** Get the list of MorphTargets. Warning if you modify this array you will have to call dirty() */ + MorphTargetList& getMorphTargetList() { return mMorphTargets; } + + /** Return the \c MorphTarget at position \c i.*/ + inline const MorphTarget& getMorphTarget( unsigned int i ) const { return mMorphTargets[i]; } + + /** Return the \c MorphTarget at position \c i.*/ + inline MorphTarget& getMorphTarget( unsigned int i ) { return mMorphTargets[i]; } + + osg::ref_ptr getSourceGeometry() const; + + virtual void accept(osg::NodeVisitor &nv); + + virtual osg::BoundingBox computeBoundingBox() const; + + private: + void cull(osg::NodeVisitor* nv); + + MorphTargetList mMorphTargets; + + osg::ref_ptr mSourceGeometry; + + osg::ref_ptr mGeometry[2]; + osg::Geometry* getGeometry(unsigned int frame) const; + + unsigned int mLastFrameNumber; + bool mDirty; // Have any morph targets changed? + + mutable bool mMorphedBoundingBox; + }; + +} + +#endif diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 177d700b2..ab0321532 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace SceneUtil { @@ -37,20 +38,20 @@ public: } }; -class FrameSwitchSerializer : public osgDB::ObjectWrapper +class RigGeometrySerializer : public osgDB::ObjectWrapper { public: - FrameSwitchSerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch") + RigGeometrySerializer() + : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry") { } }; -class RigGeometrySerializer : public osgDB::ObjectWrapper +class MorphGeometrySerializer : public osgDB::ObjectWrapper { public: - RigGeometrySerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry") + MorphGeometrySerializer() + : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry") { } }; @@ -95,8 +96,8 @@ void registerSerializers() osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager(); mgr->addWrapper(new PositionAttitudeTransformSerializer); mgr->addWrapper(new SkeletonSerializer); - mgr->addWrapper(new FrameSwitchSerializer); mgr->addWrapper(new RigGeometrySerializer); + mgr->addWrapper(new MorphGeometrySerializer); mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2c0b9f0e3..9b3876d6c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "shadermanager.hpp" @@ -376,12 +377,18 @@ namespace Shader const ShaderRequirements& reqs = mRequirements.back(); createProgram(reqs); - if (SceneUtil::RigGeometry* rig = dynamic_cast(&drawable)) + if (auto rig = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) rig->setSourceGeometry(sourceGeometry); } + else if (auto morph = dynamic_cast(&drawable)) + { + osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); + if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) + morph->setSourceGeometry(sourceGeometry); + } } if (needPop) From 4bef8260abcfe137bd206262e980b0249d041143 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 23:02:19 +0200 Subject: [PATCH 14/18] Add const qualifiers --- components/sceneutil/morphgeometry.cpp | 2 +- components/sceneutil/riggeometry.cpp | 6 +++--- components/sceneutil/riggeometry.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 5d55e75ec..4c53b68a5 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -31,7 +31,7 @@ void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) { mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); - osg::Geometry& from = *mSourceGeometry; + const osg::Geometry& from = *mSourceGeometry; osg::Geometry& to = *mGeometry[i]; to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index ec01f62d0..f3eea1bbd 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -35,7 +35,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) for (unsigned int i=0; i<2; ++i) { - osg::Geometry& from = *sourceGeometry; + const osg::Geometry& from = *sourceGeometry; mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); osg::Geometry& to = *mGeometry[i]; to.setSupportsDisplayList(false); @@ -54,7 +54,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) to.setVertexArray(vertexArray); } - if (osg::Array* normals = from.getNormalArray()) + if (const osg::Array* normals = from.getNormalArray()) { osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); if (normalArray) @@ -64,7 +64,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) } } - if (osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) + if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) { mSourceTangents = tangents; osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 097e7ea69..638f53679 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -57,7 +57,7 @@ namespace SceneUtil osg::Geometry* getGeometry(unsigned int frame) const; osg::ref_ptr mSourceGeometry; - osg::ref_ptr mSourceTangents; + osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; osg::ref_ptr mGeomToSkelMatrix; From 5e790b567e460de29bea43ec5410822db58a6e57 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 1 Sep 2017 22:12:40 +0000 Subject: [PATCH 15/18] Fix node path issue Needs to be set so that the 'cullingActive' flag of the node path's end can be checked --- components/sceneutil/morphgeometry.cpp | 5 ++++- components/sceneutil/riggeometry.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 4c53b68a5..98e5a92bc 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -147,7 +147,10 @@ void MorphGeometry::cull(osg::NodeVisitor *nv) { if (mLastFrameNumber == nv->getTraversalNumber() || !mDirty) { - nv->apply(*getGeometry(mLastFrameNumber)); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); return; } diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index f3eea1bbd..f52bca80c 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -174,7 +174,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber()) { - nv->apply(*getGeometry(mLastFrameNumber)); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); return; } mLastFrameNumber = nv->getTraversalNumber(); From e0ffc94f3e7fc9922bda949b7d0184071e5ef91b Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 2 Sep 2017 18:07:12 -0500 Subject: [PATCH 16/18] Fix OpenMW-CS internal name --- apps/opencs/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0a146dc06..fc8930b71 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -199,7 +199,7 @@ if(APPLE) RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" OUTPUT_NAME ${OPENCS_BUNDLE_NAME} MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" - MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" + MACOSX_BUNDLE_BUNDLE_NAME "OpenMW-CS" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} From 10fff499a2103b2c0ca6e15172155e2b61a245b5 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sat, 2 Sep 2017 19:02:55 -0500 Subject: [PATCH 17/18] Adding that Reflect Actors can be toggled in the video panel --- docs/source/reference/modding/settings/water.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index 2344222e5..a1c6c2068 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -56,7 +56,7 @@ Enabling this feature results in better visuals, and a marginally lower frame ra This setting has no effect if the shader setting is false. -This setting can be toggled with the Refraction button in the Water tab of the Video panel of the Options menu. +This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. reflect actors -------------- @@ -68,6 +68,8 @@ reflect actors This setting controls whether or not NPCs and creatures are drawn in water reflections. Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance. +This setting can be toggled with the 'Reflect actors' button in the Water tab of the Video panel of the Options menu. + small feature culling pixel size -------------------------------- From 2e58024f1c8adc44d02d42a4c2d20a31073d2ce4 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Sun, 3 Sep 2017 16:02:40 +0200 Subject: [PATCH 18/18] Fix intersections with Rig/MorphGeometry, was caused by an issue in the LineSegmentIntersector not respecting the cullingActive flag of a drawable. --- components/sceneutil/morphgeometry.cpp | 7 +++++-- components/sceneutil/morphgeometry.hpp | 2 ++ components/sceneutil/riggeometry.cpp | 7 +++++-- components/sceneutil/riggeometry.hpp | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 98e5a92bc..2ffbace3b 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -82,14 +82,17 @@ void MorphGeometry::accept(osg::NodeVisitor &nv) if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) cull(&nv); - else if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR) - nv.apply(*getGeometry(mLastFrameNumber)); else nv.apply(*this); nv.popFromNodePath(); } +void MorphGeometry::accept(osg::PrimitiveFunctor& func) const +{ + getGeometry(mLastFrameNumber)->accept(func); +} + osg::BoundingBox MorphGeometry::computeBoundingBox() const { bool anyMorphTarget = false; diff --git a/components/sceneutil/morphgeometry.hpp b/components/sceneutil/morphgeometry.hpp index 1afef1895..122c1456c 100644 --- a/components/sceneutil/morphgeometry.hpp +++ b/components/sceneutil/morphgeometry.hpp @@ -57,6 +57,8 @@ namespace SceneUtil osg::ref_ptr getSourceGeometry() const; virtual void accept(osg::NodeVisitor &nv); + virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; virtual osg::BoundingBox computeBoundingBox() const; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index f52bca80c..7f148cf5e 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -318,14 +318,17 @@ void RigGeometry::accept(osg::NodeVisitor &nv) cull(&nv); else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) updateBounds(&nv); - else if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR) - nv.apply(*getGeometry(mLastFrameNumber)); else nv.apply(*this); nv.popFromNodePath(); } +void RigGeometry::accept(osg::PrimitiveFunctor& func) const +{ + getGeometry(mLastFrameNumber)->accept(func); +} + osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const { return mGeometry[frame%2].get(); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 638f53679..64f4bf312 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -48,6 +48,8 @@ namespace SceneUtil osg::ref_ptr getSourceGeometry(); virtual void accept(osg::NodeVisitor &nv); + virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; private: void cull(osg::NodeVisitor* nv);