From 591a35b8d717161bd1974ecc57eb63146efaee05 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 10 Apr 2015 23:16:17 +0200 Subject: [PATCH] *very* early version of the LightManager --- components/CMakeLists.txt | 2 +- components/nifosg/nifloader.cpp | 43 +--- components/sceneutil/attach.cpp | 59 ++--- components/sceneutil/lightmanager.cpp | 309 ++++++++++++++++++++++++++ components/sceneutil/lightmanager.hpp | 105 +++++++++ components/sceneutil/util.cpp | 36 +++ components/sceneutil/util.hpp | 17 ++ components/sceneutil/visitor.hpp | 37 +++ 8 files changed, 542 insertions(+), 66 deletions(-) create mode 100644 components/sceneutil/lightmanager.cpp create mode 100644 components/sceneutil/lightmanager.hpp create mode 100644 components/sceneutil/util.cpp create mode 100644 components/sceneutil/util.hpp create mode 100644 components/sceneutil/visitor.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1debf3495..abce7020c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -39,7 +39,7 @@ add_component_dir (resource ) add_component_dir (sceneutil - clone attach + clone attach lightmanager visitor util ) add_component_dir (nif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9df27df32..a5c9b6e2a 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -41,6 +41,7 @@ #include #include +#include #include "particle.hpp" #include "userdata.hpp" @@ -467,7 +468,9 @@ namespace { } - virtual osg::BoundingBox computeBound(const osg::Drawable& drawable) const + META_Object(NifOsg, StaticBoundingBoxCallback) + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return mBoundingBox; } @@ -497,37 +500,6 @@ namespace mBoundSpheres[bonename] = sphere; } - // based off code in osg::Transform - void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere) const - { - osg::BoundingSphere::vec_type xdash = bsphere._center; - xdash.x() += bsphere._radius; - xdash = xdash*matrix; - - osg::BoundingSphere::vec_type ydash = bsphere._center; - ydash.y() += bsphere._radius; - ydash = ydash*matrix; - - osg::BoundingSphere::vec_type zdash = bsphere._center; - zdash.z() += bsphere._radius; - zdash = zdash*matrix; - - bsphere._center = bsphere._center*matrix; - - xdash -= bsphere._center; - osg::BoundingSphere::value_type len_xdash = xdash.length(); - - ydash -= bsphere._center; - osg::BoundingSphere::value_type len_ydash = ydash.length(); - - zdash -= bsphere._center; - osg::BoundingSphere::value_type len_zdash = zdash.length(); - - bsphere._radius = len_xdash; - if (bsphere._radiusfirst; osg::BoundingSphere bs = it->second; - transformBoundingSphere(bone->getMatrixInSkeletonSpace(), bs); + SceneUtil::transformBoundingSphere(bone->getMatrixInSkeletonSpace(), bs); box.expandBy(bs); } @@ -776,7 +748,6 @@ namespace NifOsg osgAnimation::Bone* bone = new osgAnimation::Bone; transformNode = bone; bone->setMatrix(toMatrix(nifNode->trafo)); - bone->setName(nifNode->name); bone->setInvBindMatrixInSkeletonSpace(osg::Matrixf::inverse(getWorldTransform(nifNode))); } else @@ -788,14 +759,14 @@ namespace NifOsg transformNode->addCullCallback(new BillboardCallback); } + transformNode->setName(nifNode->name); + if (parentNode) parentNode->addChild(transformNode); if (!rootNode) rootNode = transformNode; - // Ignoring name for non-bone nodes for now. We might need it later in isolated cases, e.g. AttachLight. - // 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 diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 6208d0152..47efe986c 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -12,40 +12,39 @@ #include #include +#include "visitor.hpp" + namespace SceneUtil { - class FindByNameVisitor : public osg::NodeVisitor + class NodeMapVisitor : public osg::NodeVisitor { public: - FindByNameVisitor(const std::string& nameToFind) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mNameToFind(nameToFind) - , mFoundNode(NULL) + NodeMapVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} + + void apply(osg::MatrixTransform& trans) { + mMap[trans.getName()] = &trans; + traverse(trans); } - virtual void apply(osg::Node &node) + typedef std::map > NodeMap; + + const NodeMap& getNodeMap() const { - osg::Group* group = node.asGroup(); - if (group && node.getName() == mNameToFind) - { - mFoundNode = group; - return; - } - traverse(node); + return mMap; } - const std::string& mNameToFind; - osg::Group* mFoundNode; + private: + NodeMap mMap; }; - /// Copy the skeleton-space matrix of a "source" bone to a "dest" bone (the bone that the callback is attached to). - /// Must be set on a Bone. + /// Copy the matrix of a "source" node to a "dest" node (the node that the callback is attached to). + /// Must be set on a MatrixTransform. class CopyController : public osg::NodeCallback { public: - CopyController(osgAnimation::Bone* copyFrom) + CopyController(osg::MatrixTransform* copyFrom) : mCopyFrom(copyFrom) { } @@ -66,22 +65,21 @@ namespace SceneUtil if (mCopyFrom) { bone->setMatrix(mCopyFrom->getMatrix()); - bone->setMatrixInSkeletonSpace(mCopyFrom->getMatrixInSkeletonSpace()); } traverse(node, nv); } private: - const osgAnimation::Bone* mCopyFrom; + const osg::MatrixTransform* mCopyFrom; }; class AddCopyControllerVisitor : public osg::NodeVisitor { public: - AddCopyControllerVisitor(const osgAnimation::BoneMap& boneMap) + AddCopyControllerVisitor(const NodeMapVisitor::NodeMap& boneMap) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mBoneMap(boneMap) + , mNodeMap(boneMap) { } @@ -89,19 +87,23 @@ namespace SceneUtil { if (osgAnimation::Bone* bone = dynamic_cast(&node)) { - osgAnimation::BoneMap::const_iterator found = mBoneMap.find(bone->getName()); - if (found != mBoneMap.end()) + NodeMapVisitor::NodeMap::const_iterator found = mNodeMap.find(bone->getName()); + if (found != mNodeMap.end()) { + // add the CopyController at position 0 so it's executed before UpdateBone + osg::ref_ptr old = bone->getUpdateCallback(); bone->setUpdateCallback(new CopyController(found->second.get())); + bone->addUpdateCallback(old); } } } private: - const osgAnimation::BoneMap& mBoneMap; + const NodeMapVisitor::NodeMap& mNodeMap; }; // FIXME: would be more efficient to copy only the wanted nodes instead of deleting unwanted ones later + // copying is kinda cheap though, so don't bother for now class FilterVisitor : public osg::NodeVisitor { public: @@ -137,13 +139,12 @@ namespace SceneUtil { if (osgAnimation::Skeleton* skel = dynamic_cast(toAttach.get())) { - osgAnimation::Skeleton* masterSkel = dynamic_cast(master); - osgAnimation::BoneMapVisitor boneMapVisitor; - masterSkel->accept(boneMapVisitor); + NodeMapVisitor nodeMapVisitor; + master->accept(nodeMapVisitor); // would be more efficient if we could attach the RigGeometry directly to the master skeleton, but currently not possible // due to a difference in binding pose of the two skeletons - AddCopyControllerVisitor visitor(boneMapVisitor.getBoneMap()); + AddCopyControllerVisitor visitor(nodeMapVisitor.getNodeMap()); toAttach->accept(visitor); FilterVisitor filterVisitor(filter); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp new file mode 100644 index 000000000..c49cf77b8 --- /dev/null +++ b/components/sceneutil/lightmanager.cpp @@ -0,0 +1,309 @@ +#include "lightmanager.hpp" + +#include +#include + +#include + +#include + +#include + +#include +#include + +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); + } + }; + + class CullCallback : public osg::NodeCallback + { + public: + CullCallback() + : mLightManager(NULL) + {} + CullCallback(const CullCallback& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) + : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop), mLightManager(copy.mLightManager) + {} + CullCallback(LightManager* lightManager) + : mLightManager(lightManager) + {} + + META_Object(NifOsg, CullCallback) + + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + mLightManager->prepareForCamera(cv->getCurrentCamera()); + + // Possible optimizations: + // - cull list of lights by the camera frustum + // - organize lights in a quad tree + + const std::vector& lights = mLightManager->getLights(); + + if (lights.size()) + { + // we do the intersections in view space + osg::BoundingSphere nodeBound = node->getBound(); + osg::Matrixf mat = *cv->getModelViewMatrix(); + transformBoundingSphere(mat, nodeBound); + + std::vector lightList; + for (unsigned int i=0; i 8) + { + //std::cerr << "More than 8 lights!" << std::endl; + + // TODO: sort lights by certain criteria + + while (lightList.size() > 8) + lightList.pop_back(); + } + + osg::ref_ptr stateset = mLightManager->getLightListStateSet(lightList); + + cv->pushStateSet(stateset); + + traverse(node, nv); + + cv->popStateSet(); + } + else + traverse(node, nv); + } + + private: + LightManager* mLightManager; + }; + + class AttachCullCallbackVisitor : public osg::NodeVisitor + { + public: + AttachCullCallbackVisitor(LightManager* lightManager) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLightManager(lightManager) + { + } + + virtual void apply(osg::Geode &geode) + { + if (!geode.getNumParents()) + return; + + // Not working on a Geode. Drawables are not normal children of the Geode, the traverse() call + // does not traverse the drawables, so the push/pop in the CullCallback has no effect + // Should be no longer an issue with osg trunk + osg::Node* parent = geode.getParent(0); + parent->addCullCallback(new CullCallback(mLightManager)); + } + + private: + LightManager* mLightManager; + }; + + // 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;igetNodePath().size(); ++i) + { + if (LightManager* lightManager = dynamic_cast(nv->getNodePath()[i])) + { + mLightManager = lightManager; + break; + } + } + if (!mLightManager) + throw std::runtime_error("can't find parent LightManager"); + } + + mLightManager->addLight(static_cast(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(node); + lightManager->update(); + + traverse(node, nv); + } + }; + + LightManager::LightManager() + : mLightsInViewSpace(false) + , mDecorated(false) + { + setUpdateCallback(new LightManagerUpdateCallback); + } + + LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) + : osg::Group(copy, copyop) + , mLightsInViewSpace(false) + , mDecorated(copy.mDecorated) + { + + } + + void LightManager::decorateGeodes() + { + AttachCullCallbackVisitor visitor(this); + accept(visitor); + } + + void LightManager::update() + { + mLightsInViewSpace = false; + mLights.clear(); + mStateSetCache.clear(); + + if (!mDecorated) + { + decorateGeodes(); + mDecorated = true; + } + } + + 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::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 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; isecond; + else + { + osg::ref_ptr stateset (new osg::StateSet); + for (unsigned int i=0; igetLight(); + + osg::ref_ptr clonedLight = new LightStateAttribute(*light, osg::CopyOp::DEEP_COPY_ALL); + clonedLight->setPosition(mLights[lightIndex].mWorldMatrix.preMult(light->getPosition())); + + clonedLight->setLightNum(i); + + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(clonedLight, osg::StateAttribute::ON); + stateset->setAssociatedModes(clonedLight, osg::StateAttribute::ON); + } + mStateSetCache.insert(std::make_pair(hash, stateset)); + return stateset; + } + } + + const std::vector& LightManager::getLights() const + { + return mLights; + } + + LightSource::LightSource() + : mRadius(0.f) + { + setUpdateCallback(new CollectLightCallback); + } + +} diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp new file mode 100644 index 000000000..ccb6603a6 --- /dev/null +++ b/components/sceneutil/lightmanager.hpp @@ -0,0 +1,105 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H +#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H + +#include + +#include +#include + +namespace SceneUtil +{ + + /// LightSource managed by a LightManager. + class LightSource : public osg::Node + { + osg::ref_ptr mLight; + + // The activation radius + float mRadius; + + public: + + META_Node(SceneUtil, SceneUtil::LightSource) + + LightSource(); + + LightSource(const LightSource& copy, const osg::CopyOp& copyop) + : osg::Node(copy, copyop) + , mLight(copy.mLight) + , mRadius(copy.mRadius) + { + } + + float getRadius() const + { + return mRadius; + } + + void setRadius(float radius) + { + mRadius = radius; + } + + osg::Light* getLight() + { + return mLight; + } + + void setLight(osg::Light* light) + { + mLight = light; + } + }; + + /// All light sources must be a child of the LightManager node. The LightManager can be anywhere in the scene graph, + /// but would be typically somewhere near the top. + class LightManager : public osg::Group + { + public: + + META_Node(SceneUtil, SceneUtil::LightManager) + + LightManager(); + + LightManager(const LightManager& copy, const osg::CopyOp& copyop); + + // Called automatically by the UpdateCallback + void update(); + + // Called automatically by the LightSource's UpdateCallback + void addLight(LightSource* lightSource, osg::Matrix worldMat); + + void prepareForCamera(osg::Camera* cam); + + void decorateGeodes(); + + struct LightSourceTransform + { + LightSource* mLightSource; + osg::Matrix mWorldMatrix; + osg::BoundingSphere mViewBound; + }; + + const std::vector& getLights() const; + + // Stores indices into the mLights vector + typedef std::vector LightList; + + osg::ref_ptr getLightListStateSet(const LightList& lightList); + + private: + // Lights collected from the scene graph. Only valid during the cull traversal. + std::vector mLights; + + bool mLightsInViewSpace; + + // < Light list hash , StateSet > + typedef std::map > LightStateSetMap; + LightStateSetMap mStateSetCache; + + bool mDecorated; + }; + +} + +#endif diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp new file mode 100644 index 000000000..0d99e762d --- /dev/null +++ b/components/sceneutil/util.cpp @@ -0,0 +1,36 @@ +#include "util.hpp" + +namespace SceneUtil +{ + +void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere) +{ + osg::BoundingSphere::vec_type xdash = bsphere._center; + xdash.x() += bsphere._radius; + xdash = xdash*matrix; + + osg::BoundingSphere::vec_type ydash = bsphere._center; + ydash.y() += bsphere._radius; + ydash = ydash*matrix; + + osg::BoundingSphere::vec_type zdash = bsphere._center; + zdash.z() += bsphere._radius; + zdash = zdash*matrix; + + bsphere._center = bsphere._center*matrix; + + xdash -= bsphere._center; + osg::BoundingSphere::value_type len_xdash = xdash.length(); + + ydash -= bsphere._center; + osg::BoundingSphere::value_type len_ydash = ydash.length(); + + zdash -= bsphere._center; + osg::BoundingSphere::value_type len_zdash = zdash.length(); + + bsphere._radius = len_xdash; + if (bsphere._radius +#include + +namespace SceneUtil +{ + + // Transform a bounding sphere by a matrix + // based off private code in osg::Transform + // TODO: patch osg to make public + void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere); + +} + +#endif diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp new file mode 100644 index 000000000..59c706f11 --- /dev/null +++ b/components/sceneutil/visitor.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H + +#include + +// Commonly used scene graph visitors +namespace SceneUtil +{ + + class FindByNameVisitor : public osg::NodeVisitor + { + public: + FindByNameVisitor(const std::string& nameToFind) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mNameToFind(nameToFind) + , mFoundNode(NULL) + { + } + + virtual void apply(osg::Node &node) + { + osg::Group* group = node.asGroup(); + if (group && node.getName() == mNameToFind) + { + mFoundNode = group; + return; + } + traverse(node); + } + + const std::string& mNameToFind; + osg::Group* mFoundNode; + }; + +} + +#endif