diff --git a/CMakeLists.txt b/CMakeLists.txt index c2621fee7..b9f452c7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,7 +217,8 @@ endif() IF(BUILD_OPENMW OR BUILD_OPENCS) - 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 osgdb_bmp @@ -261,8 +262,6 @@ IF(BUILD_OPENMW OR BUILD_OPENCS) ENDIF(BUILD_OPENMW OR BUILD_OPENCS) -include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) - set(BOOST_COMPONENTS system filesystem program_options) if(WIN32) 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} diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 9a65a7bc9..e6d71eaa2 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -257,6 +257,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/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/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); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a35bed8a5..640a3be55 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -269,6 +269,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(); 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; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index c51c41483..301a73d6f 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -985,7 +985,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 6e2f068af..0bb6d2c5d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -851,6 +851,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 a73c405c8..680e3c9cb 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -117,6 +117,7 @@ namespace MWMechanics End of tes3mp addition */ + 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 e7db2a501..66b889e95 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2357,6 +2357,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 325173503..b922c9d74 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -436,6 +436,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 d6b6d1275..7660d651e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -220,8 +220,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); 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) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 98d29bfc9..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: @@ -1192,6 +1195,9 @@ namespace MWRender mObjectRoot->addChild(created); mInsert->addChild(mObjectRoot); } + osg::ref_ptr skel = dynamic_cast(mObjectRoot.get()); + if (skel) + mSkeleton = skel.get(); } else { 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); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c02fab8c9..b25adbbd1 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 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 78186c439..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,106 +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); } - 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); - - triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); + osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - 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; } @@ -1219,6 +1067,7 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); + rig->setName(triShape->name); const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); @@ -1233,7 +1082,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); @@ -1246,17 +1094,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 1385f771e..1df1be27f 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::Drawable& drawable) { - std::string lowerName = Misc::StringUtils::lowerCase(node.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)) - mToCopy.push_back(&node); - else - traverse(node); + { + osg::Node* node = &drawable; + while (node && node->getNumParents() && !node->getStateSet()) + node = node->getParent(0); + if (node) + mToCopy.push_back(node); + } } void doCopy() 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..2ffbace3b --- /dev/null +++ b/components/sceneutil/morphgeometry.cpp @@ -0,0 +1,193 @@ +#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); + + const 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 + nv.apply(*this); + + nv.popFromNodePath(); +} + +void MorphGeometry::accept(osg::PrimitiveFunctor& func) const +{ + getGeometry(mLastFrameNumber)->accept(func); +} + +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) + { + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); + 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..122c1456c --- /dev/null +++ b/components/sceneutil/morphgeometry.hpp @@ -0,0 +1,83 @@ +#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 bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; + + 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/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 9adfdcddb..7f148cf5e 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) + for (unsigned int i=0; i<2; ++i) { - vertexArray->setVertexBufferObject(vbo); - setVertexArray(vertexArray); - } - - if (osg::Array* normals = from.getNormalArray()) - { - osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); - if (normalArray) + const 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 (const 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 (const 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,27 @@ 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()) + { + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); 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 +232,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 +307,32 @@ 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 + 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 39829bcb0..64f4bf312 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,15 +47,19 @@ namespace SceneUtil osg::ref_ptr getSourceGeometry(); - // Called automatically by our CullCallback - void update(osg::NodeVisitor* nv); + virtual void accept(osg::NodeVisitor &nv); + virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; - // 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; + osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; osg::ref_ptr mGeomToSkelMatrix; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 64094275c..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 osg::Geometry 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/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; }; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index d85b72203..9b3876d6c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "shadermanager.hpp" @@ -26,6 +27,7 @@ namespace Shader , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mNode(NULL) { } @@ -69,7 +71,7 @@ namespace Shader { if (node.getStateSet()) { - pushRequirements(); + pushRequirements(node); applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); @@ -234,9 +236,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 +247,12 @@ namespace Shader mRequirements.pop_back(); } - void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) + void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { + if (!reqs.mShaderRequired && !mForceShaders) + return; + + osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = NULL; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); @@ -302,12 +309,42 @@ 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); - if (geometry.getStateSet()) + if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { - pushRequirements(); + pushRequirements(geometry); applyStateSet(geometry.getStateSet(), geometry); } @@ -315,44 +352,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); - } - - // TODO: find a better place for the stateset - if (useShader) - createProgram(reqs, geometry); + createProgram(reqs); } if (needPop) @@ -366,16 +368,27 @@ 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 (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) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 8f4597ff3..cb0538d9d 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,17 @@ 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); + bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; } 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 --------------------------------