mirror of
				https://github.com/TES3MP/TES3MP.git
				synced 2025-11-03 11:02:35 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1371 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1371 lines
		
	
	
		
			62 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);
 | 
						|
            }
 | 
						|
 | 
						|
            // Set a default DataVariance (used as hint by optimization routines).
 | 
						|
            switch (nifNode->recType)
 | 
						|
            {
 | 
						|
            case Nif::RC_NiTriShape:
 | 
						|
            case Nif::RC_NiAutoNormalParticles:
 | 
						|
            case Nif::RC_NiRotatingParticles:
 | 
						|
                // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
 | 
						|
                // No support for keyframe controllers (just crashes in the original engine).
 | 
						|
                transformNode->setDataVariance(osg::Object::STATIC);
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
                // could have new children attached at any time, or added external keyframe controllers from .kf files
 | 
						|
                transformNode->setDataVariance(osg::Object::DYNAMIC);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            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, 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, 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);
 | 
						|
    }
 | 
						|
 | 
						|
}
 |