Add custom version of MorphGeometry replacing osgAnimation
Double buffering, custom bounding box and the update in the cull visitor (instead of update) are now all handled internally rather than needing hacks and/or callbacks.pull/286/head
parent
f1ebb129c1
commit
5d524a6a10
@ -0,0 +1,187 @@
|
||||
#include "morphgeometry.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
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<osg::Geometry> sourceGeom)
|
||||
{
|
||||
mSourceGeometry = sourceGeom;
|
||||
|
||||
for (unsigned int i=0; i<2; ++i)
|
||||
{
|
||||
mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY);
|
||||
|
||||
osg::Geometry& from = *mSourceGeometry;
|
||||
osg::Geometry& to = *mGeometry[i];
|
||||
to.setSupportsDisplayList(false);
|
||||
to.setUseVertexBufferObjects(true);
|
||||
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
|
||||
|
||||
// vertices are modified every frame, so we need to deep copy them.
|
||||
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
||||
osg::ref_ptr<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 if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR)
|
||||
nv.apply(*getGeometry(mLastFrameNumber));
|
||||
else
|
||||
nv.apply(*this);
|
||||
|
||||
nv.popFromNodePath();
|
||||
}
|
||||
|
||||
osg::BoundingBox MorphGeometry::computeBoundingBox() const
|
||||
{
|
||||
bool anyMorphTarget = false;
|
||||
for (unsigned int i=0; i<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)
|
||||
{
|
||||
nv->apply(*getGeometry(mLastFrameNumber));
|
||||
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,81 @@
|
||||
#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 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
|
Loading…
Reference in New Issue