mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 09:56:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			391 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "lightmanager.hpp"
 | 
						|
 | 
						|
#include <stdexcept>
 | 
						|
 | 
						|
#include <osg/NodeVisitor>
 | 
						|
#include <osg/Geode>
 | 
						|
 | 
						|
#include <osgUtil/CullVisitor>
 | 
						|
 | 
						|
#include <components/sceneutil/util.hpp>
 | 
						|
 | 
						|
#include <boost/functional/hash.hpp>
 | 
						|
 | 
						|
namespace SceneUtil
 | 
						|
{
 | 
						|
 | 
						|
    // Resets the modelview matrix to just the view matrix before applying lights.
 | 
						|
    class LightStateAttribute : public osg::StateAttribute
 | 
						|
    {
 | 
						|
    public:
 | 
						|
        LightStateAttribute() : mIndex(0) {}
 | 
						|
        LightStateAttribute(unsigned int index, const std::vector<osg::ref_ptr<osg::Light> >& lights) : mIndex(index), mLights(lights) {}
 | 
						|
 | 
						|
        LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY)
 | 
						|
            : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {}
 | 
						|
 | 
						|
        unsigned int getMember() const
 | 
						|
        {
 | 
						|
            return mIndex;
 | 
						|
        }
 | 
						|
 | 
						|
        virtual bool getModeUsage(ModeUsage & usage) const
 | 
						|
        {
 | 
						|
            for (unsigned int i=0; i<mLights.size(); ++i)
 | 
						|
                usage.usesMode(GL_LIGHT0 + mIndex + i);
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        virtual int compare(const StateAttribute &sa) const
 | 
						|
        {
 | 
						|
            throw std::runtime_error("LightStateAttribute::compare: unimplemented");
 | 
						|
        }
 | 
						|
 | 
						|
        META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT)
 | 
						|
 | 
						|
        virtual void apply(osg::State& state) const
 | 
						|
        {
 | 
						|
            osg::Matrix modelViewMatrix = state.getModelViewMatrix();
 | 
						|
 | 
						|
            state.applyModelViewMatrix(state.getInitialViewMatrix());
 | 
						|
 | 
						|
            for (unsigned int i=0; i<mLights.size(); ++i)
 | 
						|
            {
 | 
						|
                mLights[i]->setLightNum(i+mIndex);
 | 
						|
                mLights[i]->apply(state);
 | 
						|
            }
 | 
						|
 | 
						|
            state.applyModelViewMatrix(modelViewMatrix);
 | 
						|
        }
 | 
						|
 | 
						|
    private:
 | 
						|
        unsigned int mIndex;
 | 
						|
 | 
						|
        std::vector<osg::ref_ptr<osg::Light> > mLights;
 | 
						|
    };
 | 
						|
 | 
						|
    LightManager* findLightManager(const osg::NodePath& path)
 | 
						|
    {
 | 
						|
        for (unsigned int i=0;i<path.size(); ++i)
 | 
						|
        {
 | 
						|
            if (LightManager* lightManager = dynamic_cast<LightManager*>(path[i]))
 | 
						|
                return lightManager;
 | 
						|
        }
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    // Set on a LightSource. Adds the light source to its light manager for the current frame.
 | 
						|
    // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager.
 | 
						|
    class CollectLightCallback : public osg::NodeCallback
 | 
						|
    {
 | 
						|
    public:
 | 
						|
        CollectLightCallback()
 | 
						|
            : mLightManager(0) { }
 | 
						|
 | 
						|
        CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop)
 | 
						|
            : osg::NodeCallback(copy, copyop)
 | 
						|
            , mLightManager(0) { }
 | 
						|
 | 
						|
        META_Object(SceneUtil, SceneUtil::CollectLightCallback)
 | 
						|
 | 
						|
        virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
 | 
						|
        {
 | 
						|
            if (!mLightManager)
 | 
						|
            {
 | 
						|
                mLightManager = findLightManager(nv->getNodePath());
 | 
						|
 | 
						|
                if (!mLightManager)
 | 
						|
                    throw std::runtime_error("can't find parent LightManager");
 | 
						|
            }
 | 
						|
 | 
						|
            mLightManager->addLight(static_cast<LightSource*>(node), osg::computeLocalToWorld(nv->getNodePath()));
 | 
						|
 | 
						|
            traverse(node, nv);
 | 
						|
        }
 | 
						|
 | 
						|
    private:
 | 
						|
        LightManager* mLightManager;
 | 
						|
    };
 | 
						|
 | 
						|
    // Set on a LightManager. Clears the data from the previous frame.
 | 
						|
    class LightManagerUpdateCallback : public osg::NodeCallback
 | 
						|
    {
 | 
						|
    public:
 | 
						|
        LightManagerUpdateCallback()
 | 
						|
            { }
 | 
						|
 | 
						|
        LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop)
 | 
						|
            : osg::NodeCallback(copy, copyop)
 | 
						|
            { }
 | 
						|
 | 
						|
        META_Object(SceneUtil, SceneUtil::LightManagerUpdateCallback)
 | 
						|
 | 
						|
        virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
 | 
						|
        {
 | 
						|
            LightManager* lightManager = static_cast<LightManager*>(node);
 | 
						|
            lightManager->update();
 | 
						|
 | 
						|
            traverse(node, nv);
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    LightManager::LightManager()
 | 
						|
        : mStartLight(0)
 | 
						|
        , mLightingMask(~0u)
 | 
						|
    {
 | 
						|
        setUpdateCallback(new LightManagerUpdateCallback);
 | 
						|
    }
 | 
						|
 | 
						|
    LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op)
 | 
						|
        : osg::Group(copy, copyop)
 | 
						|
        , mStartLight(copy.mStartLight)
 | 
						|
        , mLightingMask(copy.mLightingMask)
 | 
						|
    {
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    void LightManager::setLightingMask(unsigned int mask)
 | 
						|
    {
 | 
						|
        mLightingMask = mask;
 | 
						|
    }
 | 
						|
 | 
						|
    unsigned int LightManager::getLightingMask() const
 | 
						|
    {
 | 
						|
        return mLightingMask;
 | 
						|
    }
 | 
						|
 | 
						|
    void LightManager::update()
 | 
						|
    {
 | 
						|
        mLights.clear();
 | 
						|
        mLightsInViewSpace.clear();
 | 
						|
 | 
						|
        // do an occasional cleanup for orphaned lights
 | 
						|
        if (mStateSetCache.size() > 5000)
 | 
						|
            mStateSetCache.clear();
 | 
						|
    }
 | 
						|
 | 
						|
    void LightManager::addLight(LightSource* lightSource, osg::Matrixf worldMat)
 | 
						|
    {
 | 
						|
        LightSourceTransform l;
 | 
						|
        l.mLightSource = lightSource;
 | 
						|
        l.mWorldMatrix = worldMat;
 | 
						|
        lightSource->getLight()->setPosition(osg::Vec4f(worldMat.getTrans().x(),
 | 
						|
                                                        worldMat.getTrans().y(),
 | 
						|
                                                        worldMat.getTrans().z(), 1.f));
 | 
						|
        mLights.push_back(l);
 | 
						|
    }
 | 
						|
 | 
						|
    osg::ref_ptr<osg::StateSet> LightManager::getLightListStateSet(const LightList &lightList)
 | 
						|
    {
 | 
						|
        // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists)
 | 
						|
        size_t hash = 0;
 | 
						|
        for (unsigned int i=0; i<lightList.size();++i)
 | 
						|
            boost::hash_combine(hash, lightList[i]->mLightSource->getId());
 | 
						|
 | 
						|
        LightStateSetMap::iterator found = mStateSetCache.find(hash);
 | 
						|
        if (found != mStateSetCache.end())
 | 
						|
            return found->second;
 | 
						|
        else
 | 
						|
        {
 | 
						|
 | 
						|
            std::vector<osg::ref_ptr<osg::Light> > lights;
 | 
						|
            for (unsigned int i=0; i<lightList.size();++i)
 | 
						|
                lights.push_back(lightList[i]->mLightSource->getLight());
 | 
						|
 | 
						|
            osg::ref_ptr<LightStateAttribute> attr = new LightStateAttribute(mStartLight, lights);
 | 
						|
 | 
						|
            osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
 | 
						|
 | 
						|
            // don't use setAttributeAndModes, that does not support light indices!
 | 
						|
            stateset->setAttribute(attr, osg::StateAttribute::ON);
 | 
						|
            stateset->setAssociatedModes(attr, osg::StateAttribute::ON);
 | 
						|
 | 
						|
            mStateSetCache.insert(std::make_pair(hash, stateset));
 | 
						|
            return stateset;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const std::vector<LightManager::LightSourceTransform>& LightManager::getLights() const
 | 
						|
    {
 | 
						|
        return mLights;
 | 
						|
    }
 | 
						|
 | 
						|
    const std::vector<LightManager::LightSourceViewBound>& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix)
 | 
						|
    {
 | 
						|
        osg::observer_ptr<osg::Camera> camPtr (camera);
 | 
						|
        std::map<osg::observer_ptr<osg::Camera>, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr);
 | 
						|
 | 
						|
        if (it == mLightsInViewSpace.end())
 | 
						|
        {
 | 
						|
            it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first;
 | 
						|
 | 
						|
            for (std::vector<LightSourceTransform>::iterator lightIt = mLights.begin(); lightIt != mLights.end(); ++lightIt)
 | 
						|
            {
 | 
						|
                osg::Matrixf worldViewMat = lightIt->mWorldMatrix * (*viewMatrix);
 | 
						|
                osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), lightIt->mLightSource->getRadius());
 | 
						|
                transformBoundingSphere(worldViewMat, viewBound);
 | 
						|
 | 
						|
                LightSourceViewBound l;
 | 
						|
                l.mLightSource = lightIt->mLightSource;
 | 
						|
                l.mViewBound = viewBound;
 | 
						|
                it->second.push_back(l);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return it->second;
 | 
						|
    }
 | 
						|
 | 
						|
    void LightManager::setStartLight(int start)
 | 
						|
    {
 | 
						|
        mStartLight = start;
 | 
						|
    }
 | 
						|
 | 
						|
    int LightManager::getStartLight() const
 | 
						|
    {
 | 
						|
        return mStartLight;
 | 
						|
    }
 | 
						|
 | 
						|
    static int sLightId = 0;
 | 
						|
 | 
						|
    LightSource::LightSource()
 | 
						|
        : mRadius(0.f)
 | 
						|
    {
 | 
						|
        setUpdateCallback(new CollectLightCallback);
 | 
						|
        mId = sLightId++;
 | 
						|
    }
 | 
						|
 | 
						|
    LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op)
 | 
						|
        : osg::Node(copy, copyop)
 | 
						|
        , mLight(copy.mLight)
 | 
						|
        , mRadius(copy.mRadius)
 | 
						|
    {
 | 
						|
        mId = sLightId++;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right)
 | 
						|
    {
 | 
						|
        return left->mViewBound.center().length2() - left->mViewBound.radius2()/4.f < right->mViewBound.center().length2() - right->mViewBound.radius2()/4.f;
 | 
						|
    }
 | 
						|
 | 
						|
    void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv)
 | 
						|
    {
 | 
						|
        osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
 | 
						|
 | 
						|
        if (!mLightManager)
 | 
						|
        {
 | 
						|
            mLightManager = findLightManager(nv->getNodePath());
 | 
						|
            if (!mLightManager)
 | 
						|
            {
 | 
						|
                traverse(node, nv);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask()))
 | 
						|
        {
 | 
						|
            traverse(node, nv);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        // Possible optimizations:
 | 
						|
        // - cull list of lights by the camera frustum
 | 
						|
        // - organize lights in a quad tree
 | 
						|
 | 
						|
 | 
						|
        // update light list if necessary
 | 
						|
        // makes sure we don't update it more than once per frame when rendering with multiple cameras
 | 
						|
        if (mLastFrameNumber != nv->getTraversalNumber())
 | 
						|
        {
 | 
						|
            mLastFrameNumber = nv->getTraversalNumber();
 | 
						|
 | 
						|
            // Don't use Camera::getViewMatrix, that one might be relative to another camera!
 | 
						|
            const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix();
 | 
						|
            const std::vector<LightManager::LightSourceViewBound>& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix);
 | 
						|
 | 
						|
            // we do the intersections in view space
 | 
						|
            osg::BoundingSphere nodeBound = node->getBound();
 | 
						|
            osg::Matrixf mat = *cv->getModelViewMatrix();
 | 
						|
            transformBoundingSphere(mat, nodeBound);
 | 
						|
 | 
						|
            mLightList.clear();
 | 
						|
            for (unsigned int i=0; i<lights.size(); ++i)
 | 
						|
            {
 | 
						|
                const LightManager::LightSourceViewBound& l = lights[i];
 | 
						|
                if (l.mViewBound.intersects(nodeBound))
 | 
						|
                    mLightList.push_back(&l);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (mLightList.size())
 | 
						|
        {
 | 
						|
            unsigned int maxLights = static_cast<unsigned int> (8 - mLightManager->getStartLight());
 | 
						|
 | 
						|
            osg::StateSet* stateset = NULL;
 | 
						|
 | 
						|
            if (mLightList.size() > maxLights)
 | 
						|
            {
 | 
						|
                // remove lights culled by this camera
 | 
						|
                LightManager::LightList lightList = mLightList;
 | 
						|
                for (LightManager::LightList::iterator it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; )
 | 
						|
                {
 | 
						|
                    osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack();
 | 
						|
 | 
						|
                    osg::BoundingSphere bs = (*it)->mViewBound;
 | 
						|
                    bs._radius = bs._radius*2;
 | 
						|
                    osg::CullingSet& cullingSet = stack.front();
 | 
						|
                    if (cullingSet.isCulled(bs))
 | 
						|
                    {
 | 
						|
                        it = lightList.erase(it);
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                        ++it;
 | 
						|
                }
 | 
						|
 | 
						|
                if (lightList.size() > maxLights)
 | 
						|
                {
 | 
						|
                    // sort by proximity to camera, then get rid of furthest away lights
 | 
						|
                    std::sort(lightList.begin(), lightList.end(), sortLights);
 | 
						|
                    while (lightList.size() > maxLights)
 | 
						|
                        lightList.pop_back();
 | 
						|
                }
 | 
						|
                stateset = mLightManager->getLightListStateSet(lightList);
 | 
						|
            }
 | 
						|
            else
 | 
						|
                stateset = mLightManager->getLightListStateSet(mLightList);
 | 
						|
 | 
						|
 | 
						|
            cv->pushStateSet(stateset);
 | 
						|
 | 
						|
            traverse(node, nv);
 | 
						|
 | 
						|
            cv->popStateSet();
 | 
						|
        }
 | 
						|
        else
 | 
						|
            traverse(node, nv);
 | 
						|
    }
 | 
						|
 | 
						|
    void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic,
 | 
						|
                        float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue)
 | 
						|
    {
 | 
						|
        bool quadratic = useQuadratic && (!outQuadInLin || isExterior);
 | 
						|
 | 
						|
        float quadraticAttenuation = 0;
 | 
						|
        float linearAttenuation = 0;
 | 
						|
        if (quadratic)
 | 
						|
        {
 | 
						|
            float r = radius * quadraticRadiusMult;
 | 
						|
            quadraticAttenuation = quadraticValue / std::pow(r, 2);
 | 
						|
        }
 | 
						|
        if (useLinear)
 | 
						|
        {
 | 
						|
            float r = radius * linearRadiusMult;
 | 
						|
            linearAttenuation = linearValue / r;
 | 
						|
        }
 | 
						|
 | 
						|
        light->setLinearAttenuation(linearAttenuation);
 | 
						|
        light->setQuadraticAttenuation(quadraticAttenuation);
 | 
						|
        light->setConstantAttenuation(0.f);
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
}
 |