Merge pull request #286 from OpenMW/master while resolving conflicts

# Conflicts:
#	CMakeLists.txt
#	apps/openmw/mwmechanics/actors.hpp
pull/311/head
David Cernat 7 years ago
commit b64f379949

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

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

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

@ -3,6 +3,7 @@
#include <components/esm/loadlock.hpp>
#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<int, std::string> 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;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
///< Generate action for using via inventory menu

@ -3,6 +3,7 @@
#include <components/esm/loadprob.hpp>
#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<int, std::string> 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;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
///< Generate action for using via inventory menu

@ -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<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);

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

@ -344,16 +344,17 @@ namespace MWGui
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
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;
}

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

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

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

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

@ -263,6 +263,7 @@ public:
void forceStateUpdate();
bool isAttackPrepairing() const;
bool isReadyToBlock() const;
bool isKnockedOut() const;
bool isSneaking() const;

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

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

@ -18,7 +18,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> 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)

@ -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<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(mObjectRoot.get());
if (skel)
mSkeleton = skel.get();
}
else
{

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

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

@ -6,11 +6,10 @@
#include <osg/Texture2D>
#include <osg/UserDataContainer>
#include <osgAnimation/MorphGeometry>
#include <osgParticle/Emitter>
#include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#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<osgAnimation::MorphGeometry*>(drawable);
SceneUtil::MorphGeometry* morphGeom = static_cast<SceneUtil::MorphGeometry*>(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()

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

@ -13,9 +13,6 @@
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/imagemanager.hpp>
// skel
#include <osgAnimation/MorphGeometry>
// particle
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
@ -39,6 +36,7 @@
#include <components/nif/effect.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#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; i<getNumChildren(); ++i)
{
if (i%2 == nv.getTraversalNumber()%2)
getChild(i)->accept(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<osgAnimation::MorphGeometry*>(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<int>& boundTextures, int animflags)
{
osg::ref_ptr<osg::Geometry> geometry;
osg::ref_ptr<osg::Drawable> 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<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
static_cast<const Nif::NiGeomMorpherController*>(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<osg::Geometry> 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> frameswitch = new FrameSwitch;
drawable->setName(triShape->name);
osg::ref_ptr<osg::Geometry> 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<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
{
osg::ref_ptr<osgAnimation::MorphGeometry> 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<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Geometry> sourceGeometry (new osg::Geometry);
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
morphGeom->setSourceGeometry(sourceGeometry);
const std::vector<Nif::NiMorphData::MorphData>& 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<osg::Geometry> 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<osg::BoundingBox> 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; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 1; i < morphs.size(); ++i)
{
for (unsigned int j=0; j<morphs[i].mVertices.size() && vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + morphs[i].mVertices[j]);
bounds.expandBy(bounds._min + morphs[i].mVertices[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += morphs[0].mVertices[i];
vertBounds[i]._min += morphs[0].mVertices[i];
box.expandBy(vertBounds[i]);
}
// For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box
morphGeom->getBound();
// 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<SceneUtil::RigGeometry> 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<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
//influence.mWeights.reserve(weights.size());
for(size_t j = 0;j < weights.size();j++)
{
std::pair<unsigned short, float> 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> 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)

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

@ -8,7 +8,7 @@
#include <osgParticle/Emitter>
#include <osgParticle/Program>
#include <osgAnimation/MorphGeometry>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
@ -49,46 +49,12 @@ namespace SceneUtil
{
if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
return operator()(partsys);
if (dynamic_cast<const osgAnimation::MorphGeometry*>(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<const SceneUtil::RigGeometry*>(drawable))
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
{
return osg::clone(drawable, *this);
}
return osg::CopyOp::operator()(drawable);
}

@ -0,0 +1,193 @@
#include "morphgeometry.hpp"
#include <cassert>
namespace SceneUtil
{
MorphGeometry::MorphGeometry()
: mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
}
MorphGeometry::MorphGeometry(const MorphGeometry &copy, const osg::CopyOp &copyop)
: osg::Drawable(copy, copyop)
, mMorphTargets(copy.mMorphTargets)
, mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
setSourceGeometry(copy.getSourceGeometry());
}
void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> 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<osg::Geometry> 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<mMorphTargets.size(); ++i)
if (mMorphTargets[i].getWeight() > 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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
std::vector<osg::BoundingBox> 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; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 0; i < mMorphTargets.size(); ++i)
{
const osg::Vec3Array& offsets = *mMorphTargets[i].getOffsets();
for (unsigned int j=0; j<offsets.size() && j<vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + offsets[j]);
bounds.expandBy(bounds._min + offsets[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += sourceVerts[i];
vertBounds[i]._min += sourceVerts[i];
box.expandBy(vertBounds[i]);
}
return box;
}
}
void MorphGeometry::cull(osg::NodeVisitor *nv)
{
if (mLastFrameNumber == nv->getTraversalNumber() || !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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
assert(positionSrc->size() == positionDst->size());
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
(*positionDst)[vertex] = (*positionSrc)[vertex];
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
{
float weight = mMorphTargets[i].getWeight();
if (weight == 0.f)
continue;
const osg::Vec3Array* offsets = mMorphTargets[i].getOffsets();
for (unsigned int vertex=0; vertex<positionSrc->size(); ++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];
}
}

@ -0,0 +1,83 @@
#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H
#define OPENMW_COMPONENTS_MORPHGEOMETRY_H
#include <osg/Geometry>
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<osg::Geometry> sourceGeom);
class MorphTarget
{
protected:
osg::ref_ptr<osg::Vec3Array> 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<MorphTarget> 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<osg::Geometry> 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<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
unsigned int mLastFrameNumber;
bool mDirty; // Have any morph targets changed?
mutable bool mMorphedBoundingBox;
};
}
#endif

@ -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<RigGeometry*>(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<RigGeometry*>(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 &copy, const osg::CopyOp &copyop)
: osg::Geometry(copy, copyop)
: Drawable(copy, copyop)
, mSkeleton(NULL)
, mInfluenceMap(copy.mInfluenceMap)
, mLastFrameNumber(0)
@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> 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<texCoordList.size(); ++i)
if (texCoordList[i])
setTexCoordArray(i, texCoordList[i], osg::Array::BIND_PER_VERTEX);
setVertexAttribArrayList(from.getVertexAttribArrayList());
// 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> 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<osg::Array> 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> 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<osg::Array> 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<osg::Vec4Array*>(from.getTexCoordArray(7)))
{
mSourceTangents = tangents;
osg::ref_ptr<osg::Array> 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<const osg::Vec4Array*>(from.getTexCoordArray(7)))
{
mSourceTangents = tangents;
osg::ref_ptr<osg::Array> 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<osg::Geometry> 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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
osg::Vec4Array* tangentSrc = mSourceTangents;
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
const osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
const osg::Vec4Array* tangentSrc = mSourceTangents;
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray());
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(getTexCoordArray(7));
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(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> 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();
}
}

@ -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<osg::Geometry> 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<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
osg::ref_ptr<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Vec4Array> mSourceTangents;
osg::ref_ptr<const osg::Vec4Array> mSourceTangents;
Skeleton* mSkeleton;
osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix;

@ -6,6 +6,7 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
namespace SceneUtil
{
@ -37,20 +38,20 @@ public:
}
};
class FrameSwitchSerializer : public osgDB::ObjectWrapper
class RigGeometrySerializer : public osgDB::ObjectWrapper
{
public:
FrameSwitchSerializer()
: osgDB::ObjectWrapper(createInstanceFunc<osg::Group>, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch")
RigGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "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>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry")
MorphGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::MorphGeometry>, "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);

@ -38,8 +38,6 @@ Skeleton::Skeleton()
, mNeedToUpdateBoneMatrices(true)
, mActive(true)
, mLastFrameNumber(0)
, mTraversedEvenFrame(false)
, mTraversedOddFrame(false)
{
}
@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton &copy, const osg::CopyOp &copyop)
, 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);
}

@ -74,8 +74,6 @@ namespace SceneUtil
bool mActive;
unsigned int mLastFrameNumber;
bool mTraversedEvenFrame;
bool mTraversedOddFrame;
};
}

@ -13,6 +13,7 @@
#include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#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<int, std::string>::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<osgUtil::TangentSpaceGenerator> 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<osg::Geometry> sourceGeometry = &geometry;
SceneUtil::RigGeometry* rig = dynamic_cast<SceneUtil::RigGeometry*>(&geometry);
if (rig)
sourceGeometry = rig->getSourceGeometry();
bool requiresSetGeometry = false;
adjustGeometry(geometry, reqs);
// make sure that all UV sets are there
for (std::map<int, std::string>::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<osgUtil::TangentSpaceGenerator> 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<SceneUtil::RigGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = rig->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
rig->setSourceGeometry(sourceGeometry);
}
else if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = morph->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
morph->setSourceGeometry(sourceGeometry);
}
}
if (needPop)

@ -52,7 +52,7 @@ namespace Shader
void applyStateSet(osg::ref_ptr<osg::StateSet> 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<ShaderRequirements> 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);
};
}

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

Loading…
Cancel
Save