1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-15 17:19:56 +00:00
openmw-tes3mp/components/sceneutil/lightmanager.cpp
scrawl 61aaf0cf70 Attach light lists to the object base nodes instead of each renderable
Apparently that is how Ogre worked (on the SceneNode) so let's roll with it for now. Have not tested yet what MW does.
2015-04-12 18:03:36 +02:00

297 lines
9.4 KiB
C++

#include "lightmanager.hpp"
#include <osg/NodeVisitor>
#include <osg/Geode>
#include <osgUtil/CullVisitor>
#include <components/sceneutil/util.hpp>
#include <boost/functional/hash.hpp>
#include <iostream>
#include <stdexcept>
namespace SceneUtil
{
class LightStateAttribute : public osg::Light
{
public:
LightStateAttribute() {}
LightStateAttribute(const LightStateAttribute& light,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY)
: osg::Light(light,copyop) {}
LightStateAttribute(const osg::Light& light,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY)
: osg::Light(light,copyop) {}
META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT)
virtual void apply(osg::State& state) const
{
osg::Matrix modelViewMatrix = state.getModelViewMatrix();
// FIXME: we shouldn't have to apply the modelview matrix after every light
// this could be solvable by having the LightStateAttribute contain all lights instead of just one.
state.applyModelViewMatrix(state.getInitialViewMatrix());
osg::Light::apply(state);
state.setGlobalDefaultAttribute(this);
state.applyModelViewMatrix(modelViewMatrix);
}
};
// 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)
{
for (unsigned int i=0;i<nv->getNodePath().size(); ++i)
{
if (LightManager* lightManager = dynamic_cast<LightManager*>(nv->getNodePath()[i]))
{
mLightManager = lightManager;
break;
}
}
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()
: mLightsInViewSpace(false)
, mStartLight(0)
{
setUpdateCallback(new LightManagerUpdateCallback);
}
LightManager::LightManager(const LightManager &copy, const osg::CopyOp &copyop)
: osg::Group(copy, copyop)
, mLightsInViewSpace(false)
, mStartLight(copy.mStartLight)
{
}
void LightManager::update()
{
mLightsInViewSpace = false;
mLights.clear();
mStateSetCache.clear();
}
void LightManager::addLight(LightSource* lightSource, osg::Matrix worldMat)
{
LightSourceTransform l;
l.mLightSource = lightSource;
l.mWorldMatrix = worldMat;
mLights.push_back(l);
}
void LightManager::prepareForCamera(osg::Camera *cam)
{
// later on we need to store this per camera
if (!mLightsInViewSpace)
{
for (std::vector<LightSourceTransform>::iterator it = mLights.begin(); it != mLights.end(); ++it)
{
LightSourceTransform& l = *it;
osg::Matrix worldViewMat = l.mWorldMatrix * cam->getViewMatrix();
l.mViewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), l.mLightSource->getRadius());
transformBoundingSphere(worldViewMat, l.mViewBound);
}
mLightsInViewSpace = true;
}
}
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]);
LightStateSetMap::iterator found = mStateSetCache.find(hash);
if (found != mStateSetCache.end())
return found->second;
else
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
for (unsigned int i=0; i<lightList.size();++i)
{
int lightIndex = lightList[i];
osg::Light* light = mLights[lightIndex].mLightSource->getLight();
osg::ref_ptr<LightStateAttribute> clonedLight = new LightStateAttribute(*light, osg::CopyOp::DEEP_COPY_ALL);
clonedLight->setPosition(mLights[lightIndex].mWorldMatrix.preMult(light->getPosition()));
clonedLight->setLightNum(i+mStartLight);
// don't use setAttributeAndModes, that does not support light indices!
stateset->setAttribute(clonedLight, osg::StateAttribute::ON);
stateset->setAssociatedModes(clonedLight, osg::StateAttribute::ON);
//stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
}
mStateSetCache.insert(std::make_pair(hash, stateset));
return stateset;
}
}
const std::vector<LightManager::LightSourceTransform>& LightManager::getLights() const
{
return mLights;
}
void LightManager::setStartLight(int start)
{
mStartLight = start;
}
int LightManager::getStartLight() const
{
return mStartLight;
}
LightSource::LightSource()
: mRadius(0.f)
{
setUpdateCallback(new CollectLightCallback);
}
void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
if (!mLightManager)
{
for (unsigned int i=0;i<nv->getNodePath().size(); ++i)
{
if (LightManager* lightManager = dynamic_cast<LightManager*>(nv->getNodePath()[i]))
{
mLightManager = lightManager;
break;
}
}
if (!mLightManager)
return;
}
mLightManager->prepareForCamera(cv->getCurrentCamera());
// Possible optimizations:
// - cull list of lights by the camera frustum
// - organize lights in a quad tree
const std::vector<LightManager::LightSourceTransform>& lights = mLightManager->getLights();
if (lights.size())
{
static std::map<osg::Node*, osg::ref_ptr<osg::StateSet> > statesets;
std::map<osg::Node*, osg::ref_ptr<osg::StateSet> >::iterator found = statesets.find(node);
osg::ref_ptr<osg::StateSet> stateset;
if (found != statesets.end())
{
stateset = found->second;
}
else{
// we do the intersections in view space
osg::BoundingSphere nodeBound = node->getBound();
osg::Matrixf mat = *cv->getModelViewMatrix();
transformBoundingSphere(mat, nodeBound);
std::vector<int> lightList;
for (unsigned int i=0; i<lights.size(); ++i)
{
const LightManager::LightSourceTransform& l = lights[i];
if (l.mViewBound.intersects(nodeBound))
lightList.push_back(i);
}
if (lightList.empty())
{
statesets[node] = NULL;
traverse(node, nv);
return;
}
unsigned int maxLights = static_cast<unsigned int> (8 - mLightManager->getStartLight());
if (lightList.size() > maxLights)
{
//std::cerr << "More than 8 lights!" << std::endl;
// TODO: sort lights by certain criteria
while (lightList.size() > maxLights)
lightList.pop_back();
}
stateset = mLightManager->getLightListStateSet(lightList);
statesets[node] = stateset;
}
if (stateset)
cv->pushStateSet(stateset);
traverse(node, nv);
if (stateset)
cv->popStateSet();
}
else
traverse(node, nv);
}
}