mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-11 00:39:59 +00:00
9e049894e8
The Material state is now set on the NiTriShape's node rather than the Geode, thus merged with other state like NiTexturingProperties, etc that are typically attached to the NiTriShape. Effectively cuts in half the number of StatSets for a NIF file, resulting in big speedup (~10%) in the Cull and Draw phases.
1355 lines
61 KiB
C++
1355 lines
61 KiB
C++
#include "nifloader.hpp"
|
|
|
|
#include <osg/Matrixf>
|
|
#include <osg/MatrixTransform>
|
|
#include <osg/Geode>
|
|
#include <osg/Geometry>
|
|
#include <osg/Array>
|
|
|
|
// resource
|
|
#include <components/misc/stringops.hpp>
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
#include <components/resource/texturemanager.hpp>
|
|
|
|
// skel
|
|
#include <osgAnimation/MorphGeometry>
|
|
|
|
// particle
|
|
#include <osgParticle/ParticleSystem>
|
|
#include <osgParticle/ParticleSystemUpdater>
|
|
#include <osgParticle/ConstantRateCounter>
|
|
#include <osgParticle/ModularEmitter>
|
|
#include <osgParticle/Shooter>
|
|
#include <osgParticle/BoxPlacer>
|
|
#include <osgParticle/ModularProgram>
|
|
|
|
#include <osg/BlendFunc>
|
|
#include <osg/AlphaFunc>
|
|
#include <osg/Depth>
|
|
#include <osg/PolygonMode>
|
|
#include <osg/FrontFace>
|
|
#include <osg/Stencil>
|
|
#include <osg/Material>
|
|
#include <osg/Texture2D>
|
|
#include <osg/TexEnv>
|
|
#include <osg/TexEnvCombine>
|
|
|
|
#include <components/nif/node.hpp>
|
|
#include <components/sceneutil/util.hpp>
|
|
#include <components/sceneutil/skeleton.hpp>
|
|
#include <components/sceneutil/riggeometry.hpp>
|
|
|
|
#include "particle.hpp"
|
|
#include "userdata.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
void getAllNiNodes(const Nif::Node* node, std::vector<int>& outIndices)
|
|
{
|
|
const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(node);
|
|
if (ninode)
|
|
{
|
|
outIndices.push_back(ninode->recIndex);
|
|
for (unsigned int i=0; i<ninode->children.length(); ++i)
|
|
if (!ninode->children[i].empty())
|
|
getAllNiNodes(ninode->children[i].getPtr(), outIndices);
|
|
}
|
|
}
|
|
|
|
osg::BlendFunc::BlendFuncMode getBlendMode(int mode)
|
|
{
|
|
switch(mode)
|
|
{
|
|
case 0: return osg::BlendFunc::ONE;
|
|
case 1: return osg::BlendFunc::ZERO;
|
|
case 2: return osg::BlendFunc::SRC_COLOR;
|
|
case 3: return osg::BlendFunc::ONE_MINUS_SRC_COLOR;
|
|
case 4: return osg::BlendFunc::DST_COLOR;
|
|
case 5: return osg::BlendFunc::ONE_MINUS_DST_COLOR;
|
|
case 6: return osg::BlendFunc::SRC_ALPHA;
|
|
case 7: return osg::BlendFunc::ONE_MINUS_SRC_ALPHA;
|
|
case 8: return osg::BlendFunc::DST_ALPHA;
|
|
case 9: return osg::BlendFunc::ONE_MINUS_DST_ALPHA;
|
|
case 10: return osg::BlendFunc::SRC_ALPHA_SATURATE;
|
|
default:
|
|
std::cerr<< "Unexpected blend mode: "<< mode << std::endl;
|
|
return osg::BlendFunc::SRC_ALPHA;
|
|
}
|
|
}
|
|
|
|
osg::AlphaFunc::ComparisonFunction getTestMode(int mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case 0: return osg::AlphaFunc::ALWAYS;
|
|
case 1: return osg::AlphaFunc::LESS;
|
|
case 2: return osg::AlphaFunc::EQUAL;
|
|
case 3: return osg::AlphaFunc::LEQUAL;
|
|
case 4: return osg::AlphaFunc::GREATER;
|
|
case 5: return osg::AlphaFunc::NOTEQUAL;
|
|
case 6: return osg::AlphaFunc::GEQUAL;
|
|
case 7: return osg::AlphaFunc::NEVER;
|
|
default:
|
|
std::cerr << "Unexpected blend mode: " << mode << std::endl;
|
|
return osg::AlphaFunc::LEQUAL;
|
|
}
|
|
}
|
|
|
|
osg::Stencil::Function getStencilFunction(int func)
|
|
{
|
|
switch (func)
|
|
{
|
|
case 0: return osg::Stencil::NEVER;
|
|
case 1: return osg::Stencil::LESS;
|
|
case 2: return osg::Stencil::EQUAL;
|
|
case 3: return osg::Stencil::LEQUAL;
|
|
case 4: return osg::Stencil::GREATER;
|
|
case 5: return osg::Stencil::NOTEQUAL;
|
|
case 6: return osg::Stencil::GEQUAL;
|
|
case 7: return osg::Stencil::NEVER; // NifSkope says this is GL_ALWAYS, but in MW it's GL_NEVER
|
|
default:
|
|
std::cerr << "Unexpected stencil function: " << func << std::endl;
|
|
return osg::Stencil::NEVER;
|
|
}
|
|
}
|
|
|
|
osg::Stencil::Operation getStencilOperation(int op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case 0: return osg::Stencil::KEEP;
|
|
case 1: return osg::Stencil::ZERO;
|
|
case 2: return osg::Stencil::REPLACE;
|
|
case 3: return osg::Stencil::INCR;
|
|
case 4: return osg::Stencil::DECR;
|
|
case 5: return osg::Stencil::INVERT;
|
|
default:
|
|
std::cerr << "Unexpected stencil operation: " << op << std::endl;
|
|
return osg::Stencil::KEEP;
|
|
}
|
|
}
|
|
|
|
// Collect all properties affecting the given node that should be applied to an osg::Material.
|
|
void collectMaterialProperties(const Nif::Node* nifNode, std::vector<const Nif::Property*>& out)
|
|
{
|
|
const Nif::PropertyList& props = nifNode->props;
|
|
for (size_t i = 0; i <props.length();++i)
|
|
{
|
|
if (!props[i].empty())
|
|
{
|
|
switch (props[i]->recType)
|
|
{
|
|
case Nif::RC_NiMaterialProperty:
|
|
case Nif::RC_NiVertexColorProperty:
|
|
case Nif::RC_NiSpecularProperty:
|
|
out.push_back(props[i].getPtr());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (nifNode->parent)
|
|
collectMaterialProperties(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)
|
|
{
|
|
const osg::FrameStamp* stamp = nv.getFrameStamp();
|
|
if (!stamp || nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN)
|
|
osg::Group::traverse(nv);
|
|
else
|
|
{
|
|
for (unsigned int i=0; i<getNumChildren(); ++i)
|
|
{
|
|
if (i%2 == stamp->getFrameNumber()%2)
|
|
getChild(i)->accept(nv);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// NodeCallback used to have a transform always oriented towards the camera. Can have translation and scale
|
|
// set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera.
|
|
class BillboardCallback : public osg::NodeCallback
|
|
{
|
|
public:
|
|
BillboardCallback()
|
|
{
|
|
}
|
|
BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop)
|
|
: osg::NodeCallback(copy, copyop)
|
|
{
|
|
}
|
|
|
|
META_Object(NifOsg, BillboardCallback)
|
|
|
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
|
{
|
|
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
|
|
osg::MatrixTransform* billboardNode = dynamic_cast<osg::MatrixTransform*>(node);
|
|
if (billboardNode && cv)
|
|
{
|
|
osg::Matrix modelView = *cv->getModelViewMatrix();
|
|
|
|
// attempt to preserve scale
|
|
float mag[3];
|
|
for (int i=0;i<3;++i)
|
|
{
|
|
mag[i] = std::sqrt(modelView(0,i) * modelView(0,i) + modelView(1,i) * modelView(1,i) + modelView(2,i) * modelView(2,i));
|
|
}
|
|
|
|
modelView.setRotate(osg::Quat());
|
|
modelView(0,0) = mag[0];
|
|
modelView(1,1) = mag[1];
|
|
modelView(2,2) = mag[2];
|
|
|
|
cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF);
|
|
|
|
traverse(node, nv);
|
|
|
|
cv->popModelViewMatrix();
|
|
}
|
|
else
|
|
traverse(node, nv);
|
|
}
|
|
};
|
|
|
|
struct UpdateMorphGeometry : public osg::Drawable::CullCallback
|
|
{
|
|
UpdateMorphGeometry()
|
|
{
|
|
}
|
|
|
|
UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop)
|
|
: osg::Drawable::CullCallback(copy, copyop)
|
|
{
|
|
}
|
|
|
|
META_Object(NifOsg, UpdateMorphGeometry)
|
|
|
|
virtual bool cull(osg::NodeVisitor *, osg::Drawable * drw, osg::State *) const
|
|
{
|
|
osgAnimation::MorphGeometry* geom = static_cast<osgAnimation::MorphGeometry*>(drw);
|
|
if (!geom)
|
|
return false;
|
|
geom->transformSoftwareMethod();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// 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++)
|
|
{
|
|
const std::string &str = tk->list[i].text;
|
|
std::string::size_type pos = 0;
|
|
while(pos < str.length())
|
|
{
|
|
if(::isspace(str[pos]))
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos));
|
|
if(nextpos != std::string::npos)
|
|
{
|
|
do {
|
|
nextpos--;
|
|
} while(nextpos > pos && ::isspace(str[nextpos]));
|
|
nextpos++;
|
|
}
|
|
else if(::isspace(*str.rbegin()))
|
|
{
|
|
std::string::const_iterator last = str.end();
|
|
do {
|
|
--last;
|
|
} while(last != str.begin() && ::isspace(*last));
|
|
nextpos = std::distance(str.begin(), ++last);
|
|
}
|
|
std::string result = str.substr(pos, nextpos-pos);
|
|
textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result)));
|
|
|
|
pos = nextpos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace NifOsg
|
|
{
|
|
|
|
bool Loader::sShowMarkers = false;
|
|
|
|
void Loader::setShowMarkers(bool show)
|
|
{
|
|
sShowMarkers = show;
|
|
}
|
|
|
|
bool Loader::getShowMarkers()
|
|
{
|
|
return sShowMarkers;
|
|
}
|
|
|
|
class LoaderImpl
|
|
{
|
|
public:
|
|
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
|
|
{
|
|
if(nif->numRoots() < 1)
|
|
{
|
|
nif->warn("Found no root nodes");
|
|
return;
|
|
}
|
|
|
|
const Nif::Record *r = nif->getRoot(0);
|
|
assert(r != NULL);
|
|
|
|
if(r->recType != Nif::RC_NiSequenceStreamHelper)
|
|
{
|
|
nif->warn("First root was not a NiSequenceStreamHelper, but a "+
|
|
r->recName+".");
|
|
return;
|
|
}
|
|
const Nif::NiSequenceStreamHelper *seq = static_cast<const Nif::NiSequenceStreamHelper*>(r);
|
|
|
|
Nif::ExtraPtr extra = seq->extra;
|
|
if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData)
|
|
{
|
|
nif->warn("First extra data was not a NiTextKeyExtraData, but a "+
|
|
(extra.empty() ? std::string("nil") : extra->recName)+".");
|
|
return;
|
|
}
|
|
|
|
extractTextKeys(static_cast<const Nif::NiTextKeyExtraData*>(extra.getPtr()), target.mTextKeys);
|
|
|
|
extra = extra->extra;
|
|
Nif::ControllerPtr ctrl = seq->controller;
|
|
for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next))
|
|
{
|
|
if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController)
|
|
{
|
|
nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName);
|
|
continue;
|
|
}
|
|
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
|
|
const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
|
|
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
|
|
|
|
if(key->data.empty())
|
|
continue;
|
|
|
|
osg::ref_ptr<NifOsg::KeyframeController> callback(new NifOsg::KeyframeController(key->data.getPtr()));
|
|
callback->setFunction(boost::shared_ptr<NifOsg::ControllerFunction>(new NifOsg::ControllerFunction(key)));
|
|
|
|
target.mKeyframeControllers[strdata->string] = callback;
|
|
}
|
|
}
|
|
|
|
static osg::ref_ptr<osg::Node> load(Nif::NIFFilePtr nif, Resource::TextureManager* textureManager)
|
|
{
|
|
if (nif->numRoots() < 1)
|
|
nif->fail("Found no root nodes");
|
|
|
|
const Nif::Record* r = nif->getRoot(0);
|
|
|
|
const Nif::Node* nifNode = dynamic_cast<const Nif::Node*>(r);
|
|
if (nifNode == NULL)
|
|
nif->fail("First root was not a node, but a " + r->recName);
|
|
|
|
osg::ref_ptr<TextKeyMapHolder> textkeys (new TextKeyMapHolder);
|
|
|
|
osg::ref_ptr<osg::Node> created = handleNode(nifNode, NULL, textureManager, std::map<int, int>(), 0, 0, false, &textkeys->mTextKeys);
|
|
|
|
if (nif->getUseSkinning())
|
|
{
|
|
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
|
skel->addChild(created);
|
|
created = skel;
|
|
}
|
|
created->getOrCreateUserDataContainer()->addUserObject(textkeys);
|
|
|
|
return created;
|
|
}
|
|
|
|
static void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::map<int, int>& boundTextures, int animflags)
|
|
{
|
|
const Nif::PropertyList& props = nifNode->props;
|
|
for (size_t i = 0; i <props.length();++i)
|
|
{
|
|
if (!props[i].empty())
|
|
handleProperty(props[i].getPtr(), applyTo, composite, textureManager, boundTextures, animflags);
|
|
}
|
|
}
|
|
|
|
static void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags)
|
|
{
|
|
bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay;
|
|
if (autoPlay)
|
|
toSetup->setSource(boost::shared_ptr<SceneUtil::ControllerSource>(new SceneUtil::FrameTimeSource));
|
|
|
|
toSetup->setFunction(boost::shared_ptr<ControllerFunction>(new ControllerFunction(ctrl)));
|
|
}
|
|
|
|
static osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager,
|
|
std::map<int, int> boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
|
|
{
|
|
osg::ref_ptr<osg::MatrixTransform> transformNode = new osg::MatrixTransform(nifNode->trafo.toMatrix());
|
|
|
|
if (nifNode->recType == Nif::RC_NiBillboardNode)
|
|
{
|
|
transformNode->addCullCallback(new BillboardCallback);
|
|
}
|
|
|
|
transformNode->setName(nifNode->name);
|
|
|
|
if (parentNode)
|
|
parentNode->addChild(transformNode);
|
|
|
|
if (!rootNode)
|
|
rootNode = transformNode;
|
|
|
|
// UserData used for a variety of features:
|
|
// - finding the correct emitter node for a particle system
|
|
// - establishing connections to the animated collision shapes, which are handled in a separate loader
|
|
// - finding a random child NiNode in NiBspArrayController
|
|
// - storing the previous 3x3 rotation and scale values for when a KeyframeController wants to
|
|
// change only certain elements of the 4x4 transform
|
|
transformNode->getOrCreateUserDataContainer()->addUserObject(
|
|
new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation));
|
|
|
|
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->extra)
|
|
{
|
|
if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys)
|
|
{
|
|
const Nif::NiTextKeyExtraData *tk = static_cast<const Nif::NiTextKeyExtraData*>(e.getPtr());
|
|
extractTextKeys(tk, *textKeys);
|
|
}
|
|
else if(e->recType == Nif::RC_NiStringExtraData)
|
|
{
|
|
const Nif::NiStringExtraData *sd = static_cast<const Nif::NiStringExtraData*>(e.getPtr());
|
|
// String markers may contain important information
|
|
// affecting the entire subtree of this obj
|
|
if(sd->string == "MRK" && !Loader::getShowMarkers())
|
|
{
|
|
// Marker objects. These meshes are only visible in the editor.
|
|
skipMeshes = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nifNode->recType == Nif::RC_NiBSAnimationNode)
|
|
animflags |= nifNode->flags;
|
|
if (nifNode->recType == Nif::RC_NiBSParticleNode)
|
|
particleflags |= nifNode->flags;
|
|
|
|
// Hide collision shapes, but don't skip the subgraph
|
|
// We still need to animate the hidden bones so the physics system can access them
|
|
if (nifNode->recType == Nif::RC_RootCollisionNode)
|
|
{
|
|
skipMeshes = true;
|
|
// Leave mask for UpdateVisitor enabled
|
|
transformNode->setNodeMask(0x1);
|
|
}
|
|
|
|
// We can skip creating meshes for hidden nodes if they don't have a VisController that
|
|
// might make them visible later
|
|
if (nifNode->flags & Nif::NiNode::Flag_Hidden)
|
|
{
|
|
bool hasVisController = false;
|
|
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
hasVisController = (ctrl->recType == Nif::RC_NiVisController);
|
|
|
|
if (!hasVisController)
|
|
skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes
|
|
|
|
// now hide this node, but leave the mask for UpdateVisitor enabled so that KeyframeController works
|
|
transformNode->setNodeMask(0x1);
|
|
}
|
|
|
|
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
|
|
|
|
applyNodeProperties(nifNode, transformNode, composite, textureManager, boundTextures, animflags);
|
|
|
|
if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes)
|
|
{
|
|
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
|
|
if (triShape->skin.empty())
|
|
handleTriShape(triShape, transformNode, composite, boundTextures, animflags);
|
|
else
|
|
handleSkinnedTriShape(triShape, transformNode, composite, boundTextures, animflags);
|
|
|
|
if (!nifNode->controller.empty())
|
|
handleMeshControllers(nifNode, composite, boundTextures, animflags);
|
|
}
|
|
|
|
if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles)
|
|
handleParticleSystem(nifNode, transformNode, composite, animflags, particleflags, rootNode);
|
|
|
|
if (composite->getNumControllers() > 0)
|
|
transformNode->addUpdateCallback(composite);
|
|
|
|
if (!nifNode->controller.empty())
|
|
handleNodeControllers(nifNode, transformNode, animflags);
|
|
|
|
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
|
if(ninode)
|
|
{
|
|
const Nif::NodeList &children = ninode->children;
|
|
for(size_t i = 0;i < children.length();++i)
|
|
{
|
|
if(!children[i].empty())
|
|
{
|
|
handleNode(children[i].getPtr(), transformNode, textureManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return transformNode;
|
|
}
|
|
|
|
static void handleMeshControllers(const Nif::Node *nifNode, SceneUtil::CompositeStateSetUpdater* composite, const std::map<int, int> &boundTextures, int animflags)
|
|
{
|
|
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
{
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
if (ctrl->recType == Nif::RC_NiUVController)
|
|
{
|
|
const Nif::NiUVController *uvctrl = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
|
|
std::set<int> texUnits;
|
|
for (std::map<int, int>::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it)
|
|
texUnits.insert(it->first);
|
|
|
|
osg::ref_ptr<UVController> ctrl = new UVController(uvctrl->data.getPtr(), texUnits);
|
|
setupController(uvctrl, ctrl, animflags);
|
|
composite->addController(ctrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handleNodeControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode, int animflags)
|
|
{
|
|
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
{
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
if (ctrl->recType == Nif::RC_NiKeyframeController)
|
|
{
|
|
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
|
|
if(!key->data.empty())
|
|
{
|
|
osg::ref_ptr<KeyframeController> callback(new KeyframeController(key->data.getPtr()));
|
|
|
|
setupController(key, callback, animflags);
|
|
transformNode->addUpdateCallback(callback);
|
|
}
|
|
}
|
|
else if (ctrl->recType == Nif::RC_NiVisController)
|
|
{
|
|
const Nif::NiVisController* visctrl = static_cast<const Nif::NiVisController*>(ctrl.getPtr());
|
|
|
|
osg::ref_ptr<VisController> callback(new VisController(visctrl->data.getPtr()));
|
|
setupController(visctrl, callback, animflags);
|
|
transformNode->addUpdateCallback(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handleMaterialControllers(const Nif::Property *materialProperty, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, int animflags)
|
|
{
|
|
for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
{
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
if (ctrl->recType == Nif::RC_NiAlphaController)
|
|
{
|
|
const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr());
|
|
osg::ref_ptr<AlphaController> ctrl(new AlphaController(alphactrl->data.getPtr()));
|
|
setupController(alphactrl, ctrl, animflags);
|
|
composite->addController(ctrl);
|
|
}
|
|
else if (ctrl->recType == Nif::RC_NiMaterialColorController)
|
|
{
|
|
const Nif::NiMaterialColorController* matctrl = static_cast<const Nif::NiMaterialColorController*>(ctrl.getPtr());
|
|
osg::ref_ptr<MaterialColorController> ctrl(new MaterialColorController(matctrl->data.getPtr()));
|
|
setupController(matctrl, ctrl, animflags);
|
|
composite->addController(ctrl);
|
|
}
|
|
else
|
|
std::cerr << "Unexpected material controller " << ctrl->recType << std::endl;
|
|
}
|
|
}
|
|
|
|
static void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, osg::StateSet *stateset, int animflags)
|
|
{
|
|
for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
{
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
if (ctrl->recType == Nif::RC_NiFlipController)
|
|
{
|
|
const Nif::NiFlipController* flipctrl = static_cast<const Nif::NiFlipController*>(ctrl.getPtr());
|
|
std::vector<osg::ref_ptr<osg::Texture2D> > textures;
|
|
for (unsigned int i=0; i<flipctrl->mSources.length(); ++i)
|
|
{
|
|
Nif::NiSourceTexturePtr st = flipctrl->mSources[i];
|
|
if (st.empty())
|
|
continue;
|
|
|
|
// inherit wrap settings from the target slot
|
|
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
|
|
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP;
|
|
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP;
|
|
if (inherit)
|
|
{
|
|
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
|
|
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
|
|
}
|
|
|
|
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS());
|
|
osg::ref_ptr<osg::Texture2D> texture = textureManager->getTexture2D(filename, wrapS, wrapT);
|
|
textures.push_back(texture);
|
|
}
|
|
osg::ref_ptr<FlipController> callback(new FlipController(flipctrl, textures));
|
|
setupController(ctrl.getPtr(), callback, animflags);
|
|
composite->addController(callback);
|
|
}
|
|
else
|
|
std::cerr << "Unexpected texture controller " << ctrl->recName << std::endl;
|
|
}
|
|
}
|
|
|
|
static void handleParticlePrograms(Nif::ExtraPtr affectors, Nif::ExtraPtr colliders, osg::Group *attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf)
|
|
{
|
|
osgParticle::ModularProgram* program = new osgParticle::ModularProgram;
|
|
attachTo->addChild(program);
|
|
program->setParticleSystem(partsys);
|
|
program->setReferenceFrame(rf);
|
|
for (; !affectors.empty(); affectors = affectors->extra)
|
|
{
|
|
if (affectors->recType == Nif::RC_NiParticleGrowFade)
|
|
{
|
|
const Nif::NiParticleGrowFade *gf = static_cast<const Nif::NiParticleGrowFade*>(affectors.getPtr());
|
|
program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime));
|
|
}
|
|
else if (affectors->recType == Nif::RC_NiGravity)
|
|
{
|
|
const Nif::NiGravity* gr = static_cast<const Nif::NiGravity*>(affectors.getPtr());
|
|
program->addOperator(new GravityAffector(gr));
|
|
}
|
|
else if (affectors->recType == Nif::RC_NiParticleColorModifier)
|
|
{
|
|
const Nif::NiParticleColorModifier *cl = static_cast<const Nif::NiParticleColorModifier*>(affectors.getPtr());
|
|
const Nif::NiColorData *clrdata = cl->data.getPtr();
|
|
program->addOperator(new ParticleColorAffector(clrdata));
|
|
}
|
|
else if (affectors->recType == Nif::RC_NiParticleRotation)
|
|
{
|
|
// unused
|
|
}
|
|
else
|
|
std::cerr << "Unhandled particle modifier " << affectors->recName << std::endl;
|
|
}
|
|
for (; !colliders.empty(); colliders = colliders->extra)
|
|
{
|
|
if (colliders->recType == Nif::RC_NiPlanarCollider)
|
|
{
|
|
const Nif::NiPlanarCollider* planarcollider = static_cast<const Nif::NiPlanarCollider*>(colliders.getPtr());
|
|
program->addOperator(new PlanarCollider(planarcollider));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors.
|
|
static void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl)
|
|
{
|
|
const Nif::NiAutoNormalParticlesData *particledata = NULL;
|
|
if(nifNode->recType == Nif::RC_NiAutoNormalParticles)
|
|
particledata = static_cast<const Nif::NiAutoNormalParticles*>(nifNode)->data.getPtr();
|
|
else if(nifNode->recType == Nif::RC_NiRotatingParticles)
|
|
particledata = static_cast<const Nif::NiRotatingParticles*>(nifNode)->data.getPtr();
|
|
else
|
|
return;
|
|
|
|
int i=0;
|
|
for (std::vector<Nif::NiParticleSystemController::Particle>::const_iterator it = partctrl->particles.begin();
|
|
i<particledata->activeCount && it != partctrl->particles.end(); ++it, ++i)
|
|
{
|
|
const Nif::NiParticleSystemController::Particle& particle = *it;
|
|
|
|
ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime));
|
|
|
|
osgParticle::Particle* created = partsys->createParticle(&particletemplate);
|
|
created->setLifeTime(std::max(0.f, particle.lifespan));
|
|
|
|
// Note this position and velocity is not correct for a particle system with absolute reference frame,
|
|
// which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager.
|
|
created->setVelocity(particle.velocity);
|
|
created->setPosition(particledata->vertices.at(particle.vertex));
|
|
|
|
osg::Vec4f partcolor (1.f,1.f,1.f,1.f);
|
|
if (particle.vertex < int(particledata->colors.size()))
|
|
partcolor = particledata->colors.at(particle.vertex);
|
|
|
|
float size = particledata->sizes.at(particle.vertex) * partctrl->size;
|
|
|
|
created->setSizeRange(osgParticle::rangef(size, size));
|
|
}
|
|
|
|
osg::BoundingBox box;
|
|
box.expandBy(osg::BoundingSphere(osg::Vec3(0,0,0), particledata->radius));
|
|
partsys->setInitialBound(box);
|
|
}
|
|
|
|
static osg::ref_ptr<Emitter> handleParticleEmitter(const Nif::NiParticleSystemController* partctrl)
|
|
{
|
|
std::vector<int> targets;
|
|
if (partctrl->recType == Nif::RC_NiBSPArrayController)
|
|
{
|
|
getAllNiNodes(partctrl->emitter.getPtr(), targets);
|
|
}
|
|
|
|
osg::ref_ptr<Emitter> emitter = new Emitter(targets);
|
|
|
|
osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter;
|
|
if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust)
|
|
counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate);
|
|
else
|
|
counter->setNumberOfParticlesPerSecondToCreate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2));
|
|
|
|
emitter->setCounter(counter);
|
|
|
|
ParticleShooter* shooter = new ParticleShooter(partctrl->velocity - partctrl->velocityRandom*0.5f,
|
|
partctrl->velocity + partctrl->velocityRandom*0.5f,
|
|
partctrl->horizontalDir, partctrl->horizontalAngle,
|
|
partctrl->verticalDir, partctrl->verticalAngle,
|
|
partctrl->lifetime, partctrl->lifetimeRandom);
|
|
emitter->setShooter(shooter);
|
|
|
|
osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer;
|
|
placer->setXRange(-partctrl->offsetRandom.x(), partctrl->offsetRandom.x());
|
|
placer->setYRange(-partctrl->offsetRandom.y(), partctrl->offsetRandom.y());
|
|
placer->setZRange(-partctrl->offsetRandom.z(), partctrl->offsetRandom.z());
|
|
|
|
emitter->setPlacer(placer);
|
|
return emitter;
|
|
}
|
|
|
|
static void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, int particleflags, osg::Node* rootNode)
|
|
{
|
|
osg::ref_ptr<ParticleSystem> partsys (new ParticleSystem);
|
|
partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT);
|
|
|
|
const Nif::NiParticleSystemController* partctrl = NULL;
|
|
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
|
{
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController)
|
|
partctrl = static_cast<Nif::NiParticleSystemController*>(ctrl.getPtr());
|
|
}
|
|
if (!partctrl)
|
|
{
|
|
std::cerr << "No particle controller found " << std::endl;
|
|
return;
|
|
}
|
|
|
|
osgParticle::ParticleProcessor::ReferenceFrame rf = (particleflags & Nif::NiNode::ParticleFlag_LocalSpace)
|
|
? osgParticle::ParticleProcessor::RELATIVE_RF
|
|
: osgParticle::ParticleProcessor::ABSOLUTE_RF;
|
|
|
|
// HACK: ParticleSystem has no setReferenceFrame method
|
|
if (rf == osgParticle::ParticleProcessor::ABSOLUTE_RF)
|
|
{
|
|
partsys->getOrCreateUserDataContainer()->addDescription("worldspace");
|
|
}
|
|
|
|
handleParticleInitialState(nifNode, partsys, partctrl);
|
|
|
|
partsys->setQuota(partctrl->numParticles);
|
|
|
|
partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size));
|
|
partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f)));
|
|
partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f));
|
|
|
|
partsys->setFreezeOnCull(true);
|
|
|
|
osg::ref_ptr<Emitter> emitter = handleParticleEmitter(partctrl);
|
|
emitter->setParticleSystem(partsys);
|
|
emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF);
|
|
|
|
// Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph.
|
|
// This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order.
|
|
// If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster.
|
|
|
|
FindRecIndexVisitor find (partctrl->emitter->recIndex);
|
|
rootNode->accept(find);
|
|
if (!find.mFound)
|
|
{
|
|
std::cerr << "can't find emitter node, wrong node order?" << std::endl;
|
|
return;
|
|
}
|
|
osg::Group* emitterNode = find.mFound;
|
|
|
|
// Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node
|
|
// actually causes the emitter to stop firing. Convenient, because MW behaves this way too!
|
|
emitterNode->addChild(emitter);
|
|
|
|
osg::ref_ptr<ParticleSystemController> callback(new ParticleSystemController(partctrl));
|
|
setupController(partctrl, callback, animflags);
|
|
emitter->setUpdateCallback(callback);
|
|
|
|
// affectors must be attached *after* the emitter in the scene graph for correct update order
|
|
// attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct
|
|
// localToWorldMatrix for transforming to particle space
|
|
handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf);
|
|
|
|
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
|
geode->addDrawable(partsys);
|
|
|
|
std::vector<const Nif::Property*> materialProps;
|
|
collectMaterialProperties(nifNode, materialProps);
|
|
applyMaterialProperties(parentNode, materialProps, composite, true, animflags);
|
|
|
|
// Particles don't have normals, so can't be diffuse lit.
|
|
osg::Material* mat = static_cast<osg::Material*>(parentNode->getStateSet()->getAttribute(osg::StateAttribute::MATERIAL));
|
|
if (mat)
|
|
{
|
|
osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK);
|
|
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,diffuse.a()));
|
|
mat->setColorMode(osg::Material::AMBIENT);
|
|
}
|
|
|
|
// particle system updater (after the emitters and affectors in the scene graph)
|
|
// I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way
|
|
osg::ref_ptr<osgParticle::ParticleSystemUpdater> updater = new osgParticle::ParticleSystemUpdater;
|
|
updater->addParticleSystem(partsys);
|
|
parentNode->addChild(updater);
|
|
|
|
if (rf == osgParticle::ParticleProcessor::RELATIVE_RF)
|
|
parentNode->addChild(geode);
|
|
else
|
|
{
|
|
osg::MatrixTransform* trans = new osg::MatrixTransform;
|
|
trans->setUpdateCallback(new InverseWorldMatrix);
|
|
trans->addChild(geode);
|
|
parentNode->addChild(trans);
|
|
}
|
|
}
|
|
|
|
static void triShapeToGeometry(const Nif::NiTriShape *triShape, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::map<int, int>& boundTextures, int animflags)
|
|
{
|
|
const Nif::NiTriShapeData* data = triShape->data.getPtr();
|
|
|
|
{
|
|
geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0]));
|
|
if (!data->normals.empty())
|
|
geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX);
|
|
}
|
|
|
|
for (std::map<int, int>::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it)
|
|
{
|
|
int textureStage = it->first;
|
|
int uvSet = it->second;
|
|
if (uvSet >= (int)data->uvlist.size())
|
|
{
|
|
// Occurred in "ascendedsleeper.nif", but only for hidden Shadow nodes, apparently
|
|
//std::cerr << "Warning: using an undefined UV set " << uvSet << " on TriShape " << triShape->name << std::endl;
|
|
continue;
|
|
}
|
|
|
|
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), &data->uvlist[uvSet][0]), osg::Array::BIND_PER_VERTEX);
|
|
}
|
|
|
|
if (!data->colors.empty())
|
|
geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX);
|
|
|
|
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
|
|
data->triangles.size(),
|
|
(unsigned short*)&data->triangles[0]));
|
|
|
|
// osg::Material properties are handled here for two reasons:
|
|
// - if there are no vertex colors, we need to disable colorMode.
|
|
// - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them
|
|
// above the actual renderable would be tedious.
|
|
std::vector<const Nif::Property*> materialProps;
|
|
collectMaterialProperties(triShape, materialProps);
|
|
applyMaterialProperties(parentNode, materialProps, composite, !data->colors.empty(), animflags);
|
|
}
|
|
|
|
static void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::map<int, int>& boundTextures, int animflags)
|
|
{
|
|
osg::ref_ptr<osg::Geometry> geometry;
|
|
if(!triShape->controller.empty())
|
|
{
|
|
Nif::ControllerPtr ctrl = triShape->controller;
|
|
do {
|
|
if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active)
|
|
{
|
|
geometry = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()));
|
|
|
|
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
|
|
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
|
setupController(ctrl.getPtr(), morphctrl, animflags);
|
|
geometry->setUpdateCallback(morphctrl);
|
|
break;
|
|
}
|
|
} while(!(ctrl=ctrl->next).empty());
|
|
}
|
|
|
|
if (!geometry.get())
|
|
geometry = new osg::Geometry;
|
|
|
|
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
|
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags);
|
|
|
|
geode->addDrawable(geometry);
|
|
|
|
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<osg::Geode> geode2 = static_cast<osg::Geode*>(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES));
|
|
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
|
|
frameswitch->addChild(geode);
|
|
frameswitch->addChild(geode2);
|
|
parentNode->addChild(frameswitch);
|
|
}
|
|
else
|
|
parentNode->addChild(geode);
|
|
}
|
|
|
|
static osg::ref_ptr<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher)
|
|
{
|
|
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);
|
|
|
|
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
|
if (!morphs.size())
|
|
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]);
|
|
}
|
|
|
|
morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box));
|
|
|
|
return morphGeom;
|
|
}
|
|
|
|
static void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite,
|
|
const std::map<int, int>& boundTextures, int animflags)
|
|
{
|
|
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
|
|
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
|
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags);
|
|
|
|
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
|
rig->setSourceGeometry(geometry);
|
|
|
|
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
|
|
|
// Assign bone weights
|
|
osg::ref_ptr<SceneUtil::RigGeometry::InfluenceMap> map (new SceneUtil::RigGeometry::InfluenceMap);
|
|
|
|
const Nif::NiSkinData *data = skin->data.getPtr();
|
|
const Nif::NodeList &bones = skin->bones;
|
|
for(size_t i = 0;i < bones.length();i++)
|
|
{
|
|
std::string boneName = bones[i].getPtr()->name;
|
|
|
|
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);
|
|
influence.mWeights.insert(indexWeight);
|
|
}
|
|
influence.mInvBindMatrix = data->bones[i].trafo.toMatrix();
|
|
influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius);
|
|
|
|
map->mMap.insert(std::make_pair(boneName, influence));
|
|
}
|
|
rig->setInfluenceMap(map);
|
|
|
|
geode->addDrawable(rig);
|
|
|
|
// 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::Geode* geode2 = static_cast<osg::Geode*>(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|
|
|
osg::CopyOp::DEEP_COPY_DRAWABLES));
|
|
|
|
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
|
|
frameswitch->addChild(geode);
|
|
frameswitch->addChild(geode2);
|
|
|
|
parentNode->addChild(frameswitch);
|
|
}
|
|
|
|
|
|
static void handleProperty(const Nif::Property *property,
|
|
osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::map<int, int>& boundTextures, int animflags)
|
|
{
|
|
switch (property->recType)
|
|
{
|
|
case Nif::RC_NiStencilProperty:
|
|
{
|
|
const Nif::NiStencilProperty* stencilprop = static_cast<const Nif::NiStencilProperty*>(property);
|
|
osg::FrontFace* frontFace = new osg::FrontFace;
|
|
switch (stencilprop->data.drawMode)
|
|
{
|
|
case 1:
|
|
frontFace->setMode(osg::FrontFace::CLOCKWISE);
|
|
break;
|
|
case 0:
|
|
case 2:
|
|
default:
|
|
frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE);
|
|
break;
|
|
}
|
|
|
|
osg::StateSet* stateset = node->getOrCreateStateSet();
|
|
stateset->setAttribute(frontFace, osg::StateAttribute::ON);
|
|
stateset->setMode(GL_CULL_FACE, stencilprop->data.drawMode == 3 ? osg::StateAttribute::OFF
|
|
: osg::StateAttribute::ON);
|
|
|
|
if (stencilprop->data.enabled != 0)
|
|
{
|
|
osg::Stencil* stencil = new osg::Stencil;
|
|
stencil->setFunction(getStencilFunction(stencilprop->data.compareFunc), stencilprop->data.stencilRef, stencilprop->data.stencilMask);
|
|
stencil->setStencilFailOperation(getStencilOperation(stencilprop->data.failAction));
|
|
stencil->setStencilPassAndDepthFailOperation(getStencilOperation(stencilprop->data.zFailAction));
|
|
stencil->setStencilPassAndDepthPassOperation(getStencilOperation(stencilprop->data.zPassAction));
|
|
|
|
stateset->setAttributeAndModes(stencil, osg::StateAttribute::ON);
|
|
}
|
|
break;
|
|
}
|
|
case Nif::RC_NiWireframeProperty:
|
|
{
|
|
const Nif::NiWireframeProperty* wireprop = static_cast<const Nif::NiWireframeProperty*>(property);
|
|
osg::PolygonMode* mode = new osg::PolygonMode;
|
|
mode->setMode(osg::PolygonMode::FRONT_AND_BACK, wireprop->flags == 0 ? osg::PolygonMode::FILL
|
|
: osg::PolygonMode::LINE);
|
|
node->getOrCreateStateSet()->setAttributeAndModes(mode, osg::StateAttribute::ON);
|
|
break;
|
|
}
|
|
case Nif::RC_NiZBufferProperty:
|
|
{
|
|
const Nif::NiZBufferProperty* zprop = static_cast<const Nif::NiZBufferProperty*>(property);
|
|
// VER_MW doesn't support a DepthFunction according to NifSkope
|
|
osg::Depth* depth = new osg::Depth;
|
|
depth->setWriteMask((zprop->flags>>1)&1);
|
|
node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON);
|
|
break;
|
|
}
|
|
// OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed
|
|
case Nif::RC_NiMaterialProperty:
|
|
case Nif::RC_NiVertexColorProperty:
|
|
case Nif::RC_NiSpecularProperty:
|
|
{
|
|
// Handled in handleTriShape so we know whether vertex colors are available
|
|
break;
|
|
}
|
|
case Nif::RC_NiAlphaProperty:
|
|
{
|
|
const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(property);
|
|
osg::BlendFunc* blendfunc = new osg::BlendFunc;
|
|
osg::StateSet* stateset = node->getOrCreateStateSet();
|
|
if (alphaprop->flags&1)
|
|
{
|
|
blendfunc->setFunction(getBlendMode((alphaprop->flags>>1)&0xf),
|
|
getBlendMode((alphaprop->flags>>5)&0xf));
|
|
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON);
|
|
|
|
bool noSort = (alphaprop->flags>>13)&1;
|
|
if (!noSort)
|
|
{
|
|
stateset->setNestRenderBins(false);
|
|
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::OFF);
|
|
stateset->setNestRenderBins(false);
|
|
stateset->setRenderingHint(osg::StateSet::OPAQUE_BIN);
|
|
}
|
|
|
|
osg::AlphaFunc* alphafunc = new osg::AlphaFunc;
|
|
if((alphaprop->flags>>9)&1)
|
|
{
|
|
alphafunc->setFunction(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f);
|
|
stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::ON);
|
|
}
|
|
else
|
|
stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::OFF);
|
|
break;
|
|
}
|
|
case Nif::RC_NiTexturingProperty:
|
|
{
|
|
const Nif::NiTexturingProperty* texprop = static_cast<const Nif::NiTexturingProperty*>(property);
|
|
osg::StateSet* stateset = node->getOrCreateStateSet();
|
|
for (int i=0; i<Nif::NiTexturingProperty::NumTextures; ++i)
|
|
{
|
|
if (texprop->textures[i].inUse)
|
|
{
|
|
if (i != Nif::NiTexturingProperty::BaseTexture
|
|
&& i != Nif::NiTexturingProperty::GlowTexture
|
|
&& i != Nif::NiTexturingProperty::DarkTexture
|
|
&& i != Nif::NiTexturingProperty::DetailTexture)
|
|
{
|
|
std::cerr << "Warning: unhandled texture stage " << i << std::endl;
|
|
continue;
|
|
}
|
|
|
|
const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i];
|
|
if(tex.texture.empty())
|
|
{
|
|
std::cerr << "Warning: texture layer " << i << " is in use but empty " << std::endl;
|
|
continue;
|
|
}
|
|
const Nif::NiSourceTexture *st = tex.texture.getPtr();
|
|
if (!st->external)
|
|
{
|
|
std::cerr << "Warning: unhandled internal texture " << std::endl;
|
|
continue;
|
|
}
|
|
|
|
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS());
|
|
|
|
unsigned int clamp = static_cast<unsigned int>(tex.clamp);
|
|
int wrapT = (clamp) & 0x1;
|
|
int wrapS = (clamp >> 1) & 0x1;
|
|
|
|
osg::Texture2D* texture2d = textureManager->getTexture2D(filename,
|
|
wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP,
|
|
wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP);
|
|
|
|
stateset->setTextureAttributeAndModes(i, texture2d, osg::StateAttribute::ON);
|
|
|
|
if (i == Nif::NiTexturingProperty::GlowTexture)
|
|
{
|
|
osg::TexEnv* texEnv = new osg::TexEnv;
|
|
texEnv->setMode(osg::TexEnv::ADD);
|
|
stateset->setTextureAttributeAndModes(i, texEnv, osg::StateAttribute::ON);
|
|
}
|
|
else if (i == Nif::NiTexturingProperty::DarkTexture)
|
|
{
|
|
osg::TexEnv* texEnv = new osg::TexEnv;
|
|
texEnv->setMode(osg::TexEnv::MODULATE);
|
|
stateset->setTextureAttributeAndModes(i, texEnv, osg::StateAttribute::ON);
|
|
}
|
|
else if (i == Nif::NiTexturingProperty::DetailTexture)
|
|
{
|
|
osg::TexEnvCombine* texEnv = new osg::TexEnvCombine;
|
|
texEnv->setScale_RGB(2.f);
|
|
texEnv->setCombine_Alpha(GL_MODULATE);
|
|
texEnv->setOperand0_Alpha(GL_SRC_ALPHA);
|
|
texEnv->setOperand1_Alpha(GL_SRC_ALPHA);
|
|
texEnv->setSource0_Alpha(GL_PREVIOUS_ARB);
|
|
texEnv->setSource1_Alpha(GL_TEXTURE);
|
|
texEnv->setCombine_RGB(GL_MODULATE);
|
|
texEnv->setOperand0_RGB(GL_SRC_COLOR);
|
|
texEnv->setOperand1_RGB(GL_SRC_COLOR);
|
|
texEnv->setSource0_RGB(GL_PREVIOUS_ARB);
|
|
texEnv->setSource1_RGB(GL_TEXTURE);
|
|
stateset->setTextureAttributeAndModes(i, texEnv, osg::StateAttribute::ON);
|
|
}
|
|
|
|
boundTextures[i] = tex.uvSet;
|
|
}
|
|
else if (boundTextures.find(i) != boundTextures.end())
|
|
{
|
|
stateset->setTextureAttributeAndModes(i, new osg::Texture2D, osg::StateAttribute::OFF);
|
|
boundTextures.erase(i);
|
|
}
|
|
handleTextureControllers(texprop, composite, textureManager, stateset, animflags);
|
|
}
|
|
break;
|
|
}
|
|
// unused by mw
|
|
case Nif::RC_NiShadeProperty:
|
|
case Nif::RC_NiDitherProperty:
|
|
case Nif::RC_NiFogProperty:
|
|
{
|
|
break;
|
|
}
|
|
default:
|
|
std::cerr << "Unhandled " << property->recName << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void applyMaterialProperties(osg::Node* node, const std::vector<const Nif::Property*>& properties, SceneUtil::CompositeStateSetUpdater* composite,
|
|
bool hasVertexColors, int animflags)
|
|
{
|
|
osg::StateSet* stateset = node->getOrCreateStateSet();
|
|
|
|
int specFlags = 0; // Specular is disabled by default, even if there's a specular color in the NiMaterialProperty
|
|
osg::Material* mat = new osg::Material;
|
|
mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF);
|
|
|
|
// NIF material defaults don't match OpenGL defaults
|
|
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
|
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
|
|
|
for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it)
|
|
{
|
|
const Nif::Property* property = *it;
|
|
switch (property->recType)
|
|
{
|
|
case Nif::RC_NiSpecularProperty:
|
|
{
|
|
specFlags = property->flags;
|
|
break;
|
|
}
|
|
case Nif::RC_NiMaterialProperty:
|
|
{
|
|
const Nif::NiMaterialProperty* matprop = static_cast<const Nif::NiMaterialProperty*>(property);
|
|
|
|
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha));
|
|
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f));
|
|
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f));
|
|
|
|
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f));
|
|
mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness);
|
|
|
|
if (!matprop->controller.empty())
|
|
handleMaterialControllers(matprop, node, composite, animflags);
|
|
|
|
break;
|
|
}
|
|
case Nif::RC_NiVertexColorProperty:
|
|
{
|
|
const Nif::NiVertexColorProperty* vertprop = static_cast<const Nif::NiVertexColorProperty*>(property);
|
|
if (!hasVertexColors)
|
|
break;
|
|
switch (vertprop->flags)
|
|
{
|
|
case 0:
|
|
mat->setColorMode(osg::Material::OFF);
|
|
break;
|
|
case 1:
|
|
mat->setColorMode(osg::Material::EMISSION);
|
|
break;
|
|
case 2:
|
|
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (specFlags == 0)
|
|
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f));
|
|
|
|
stateset->setAttributeAndModes(mat, osg::StateAttribute::ON);
|
|
}
|
|
|
|
};
|
|
|
|
osg::ref_ptr<osg::Node> Loader::load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager)
|
|
{
|
|
return LoaderImpl::load(file, textureManager);
|
|
}
|
|
|
|
void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target)
|
|
{
|
|
LoaderImpl::loadKf(kf, target);
|
|
}
|
|
|
|
}
|