diff --git a/.gitignore b/.gitignore index 036331c1b224e..6a193d4d631e9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ TAGS .DS_Store .cproject .project +.vscode *.orig *.idb *.pdb diff --git a/CHANGELOG.md b/CHANGELOG.md index da36fe9bc29ba..63c9b52495e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * GUI * Added support of rendering texture images: [#973](https://github.com/dartsim/dart/pull/973) + * Added OSG shadows: [#978](https://github.com/dartsim/dart/pull/978) * License diff --git a/dart/dynamics/ShapeFrame.cpp b/dart/dynamics/ShapeFrame.cpp index a9d14ada7c15a..c7c355994746d 100644 --- a/dart/dynamics/ShapeFrame.cpp +++ b/dart/dynamics/ShapeFrame.cpp @@ -39,9 +39,11 @@ namespace detail { //============================================================================== VisualAspectProperties::VisualAspectProperties(const Eigen::Vector4d& color, - const bool hidden) + const bool hidden, + const bool shadowed) : mRGBA(color), - mHidden(hidden) + mHidden(hidden), + mShadowed(shadowed) { // Do nothing } diff --git a/dart/dynamics/ShapeFrame.hpp b/dart/dynamics/ShapeFrame.hpp index d81b0ab3d3c26..30f8d64639f9b 100644 --- a/dart/dynamics/ShapeFrame.hpp +++ b/dart/dynamics/ShapeFrame.hpp @@ -67,12 +67,15 @@ class VisualAspect final : void setRGBA(const Eigen::Vector4d& color); DART_COMMON_GET_ASPECT_PROPERTY( Eigen::Vector4d, RGBA ) - // void setRGBA(const Eigen::Vector4d& value); // const Eigen::Vector4d& getRGBA() const; DART_COMMON_SET_GET_ASPECT_PROPERTY( bool, Hidden ) - // void setHidden(const Eigen::Vector4d& value); - // const Eigen::Vector4d& getHidden() const; + // void setHidden(const bool& value); + // const bool& getHidden() const; + + DART_COMMON_SET_GET_ASPECT_PROPERTY( bool, Shadowed ) + // void setShadowed(const bool& value); + // const bool& getShadowed() const; /// Identical to setRGB(const Eigen::Vector3d&) void setColor(const Eigen::Vector3d& color); diff --git a/dart/dynamics/detail/ShapeFrameAspect.hpp b/dart/dynamics/detail/ShapeFrameAspect.hpp index 97caa2abbb5d3..44a1331bebc9a 100644 --- a/dart/dynamics/detail/ShapeFrameAspect.hpp +++ b/dart/dynamics/detail/ShapeFrameAspect.hpp @@ -58,10 +58,14 @@ struct VisualAspectProperties /// True if this shape node should be kept from rendering bool mHidden; + /// True if this shape node should be shadowed + bool mShadowed; + /// Constructor VisualAspectProperties( const Eigen::Vector4d& color = Eigen::Vector4d(0.5, 0.5, 1.0, 1.0), - const bool hidden = false); + const bool hidden = false, + const bool shadowed = true); /// Destructor virtual ~VisualAspectProperties() = default; diff --git a/dart/gui/osg/CMakeLists.txt b/dart/gui/osg/CMakeLists.txt index 9d943f0d81772..2787bd1eeeaa0 100644 --- a/dart/gui/osg/CMakeLists.txt +++ b/dart/gui/osg/CMakeLists.txt @@ -7,7 +7,7 @@ endif() if(DART_BUILD_GUI_OSG) find_package(OpenSceneGraph 3.0 QUIET - COMPONENTS osg osgViewer osgManipulator osgGA osgDB) + COMPONENTS osg osgViewer osgManipulator osgGA osgDB osgShadow) # It seems that OPENSCENEGRAPH_FOUND will inadvertently get set to true when # OpenThreads is found, even if OpenSceneGraph is not installed. This is quite diff --git a/dart/gui/osg/InteractiveFrame.cpp b/dart/gui/osg/InteractiveFrame.cpp index 69cf6f85fac62..4b67da6ca5984 100644 --- a/dart/gui/osg/InteractiveFrame.cpp +++ b/dart/gui/osg/InteractiveFrame.cpp @@ -116,6 +116,8 @@ dart::dynamics::SimpleFrame* InteractiveTool::addShapeFrame( auto shapeFrame = mSimpleFrames.back().get(); shapeFrame->setShape(shape); shapeFrame->createVisualAspect(); + // Disable shadowing for InteractiveTool + shapeFrame->getVisualAspect(true)->setShadowed(false); return shapeFrame; } @@ -230,6 +232,8 @@ dart::dynamics::SimpleFrame* InteractiveFrame::addShapeFrame( auto shapeFrame = mSimpleFrames.back().get(); shapeFrame->setShape(shape); shapeFrame->createVisualAspect(); + // Disable shadowing for InteractiveFrame + shapeFrame->getVisualAspect(true)->setShadowed(false); return shapeFrame; } diff --git a/dart/gui/osg/Viewer.cpp b/dart/gui/osg/Viewer.cpp index 29b261e55fd26..c816ac8f8a1d5 100644 --- a/dart/gui/osg/Viewer.cpp +++ b/dart/gui/osg/Viewer.cpp @@ -380,6 +380,9 @@ void Viewer::addWorldNode(WorldNode* _newWorldNode, bool _active) _newWorldNode->simulate(mSimulating); _newWorldNode->mViewer = this; _newWorldNode->setupViewer(); + // set again the shadow technique to produce warning for ImGuiViewer + if(_newWorldNode->isShadowed()) + _newWorldNode->setShadowTechnique(_newWorldNode->getShadowTechnique()); } //============================================================================== @@ -468,6 +471,15 @@ const ::osg::Group* Viewer::getLightGroup() const return mLightGroup; } +//============================================================================== +const ::osg::ref_ptr<::osg::LightSource>& Viewer::getLightSource(std::size_t index) const +{ + assert(index < 2); + if(index == 0) + return mLightSource1; + return mLightSource2; +} + //============================================================================== void Viewer::setupDefaultLights() { diff --git a/dart/gui/osg/Viewer.hpp b/dart/gui/osg/Viewer.hpp index 30f31eaf1b085..360e77386107e 100644 --- a/dart/gui/osg/Viewer.hpp +++ b/dart/gui/osg/Viewer.hpp @@ -38,6 +38,7 @@ #include #include +#include #include @@ -114,7 +115,6 @@ class ViewerAttachment : public virtual ::osg::Group class Viewer : public osgViewer::Viewer, public dart::common::Subject { public: - /// Constructor for dart::gui::osg::Viewer. This will automatically create the /// default event handler. Viewer(const ::osg::Vec4& clearColor = ::osg::Vec4(0.9,0.9,0.9,1.0)); @@ -197,6 +197,11 @@ class Viewer : public osgViewer::Viewer, public dart::common::Subject /// Get the Group node that contains the LightSources for this Viewer const ::osg::Group* getLightGroup() const; + /// Get one of the LightSources of this Viewer + /// index either 0 or 1 + /// Useful for shadowing techniques + const ::osg::ref_ptr<::osg::LightSource>& getLightSource(std::size_t index = 0) const; + /// Set up the default lighting scheme void setupDefaultLights(); diff --git a/dart/gui/osg/WorldNode.cpp b/dart/gui/osg/WorldNode.cpp index 7b5508438623b..ee7cc5650dc66 100644 --- a/dart/gui/osg/WorldNode.cpp +++ b/dart/gui/osg/WorldNode.cpp @@ -34,8 +34,12 @@ #include +#include +#include + #include "dart/gui/osg/WorldNode.hpp" #include "dart/gui/osg/ShapeFrameNode.hpp" +#include "dart/gui/osg/ImGuiViewer.hpp" #include "dart/simulation/World.hpp" #include "dart/dynamics/Skeleton.hpp" @@ -61,19 +65,40 @@ class WorldNodeCallback : public ::osg::NodeCallback }; //============================================================================== -WorldNode::WorldNode(std::shared_ptr _world) - : mWorld(_world), +WorldNode::WorldNode(std::shared_ptr world, ::osg::ref_ptr shadowTechnique) + : mWorld(world), mSimulating(false), mNumStepsPerCycle(1), - mViewer(nullptr) + mViewer(nullptr), + mNormalGroup(new ::osg::Group) { + // Flags for shadowing; maybe this needs to be global? + constexpr int ReceivesShadowTraversalMask = 0x2; + constexpr int CastsShadowTraversalMask = 0x1; + + // Setup shadows + // Create a ShadowedScene + ::osg::ref_ptr shadowedScene = new osgShadow::ShadowedScene; + shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask); + shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask); + + // set the shadowed group + mShadowedGroup = shadowedScene.get(); + mShadowedGroup->getOrCreateStateSet(); + + // Add normal and shadowed groups + addChild(mNormalGroup); + addChild(mShadowedGroup); + + setShadowTechnique(shadowTechnique); + setUpdateCallback(new WorldNodeCallback); } //============================================================================== -void WorldNode::setWorld(std::shared_ptr _newWorld) +void WorldNode::setWorld(std::shared_ptr newWorld) { - mWorld = _newWorld; + mWorld = newWorld; } //============================================================================== @@ -138,15 +163,15 @@ bool WorldNode::isSimulating() const } //============================================================================== -void WorldNode::simulate(bool _on) +void WorldNode::simulate(bool on) { - mSimulating = _on; + mSimulating = on; } //============================================================================== -void WorldNode::setNumStepsPerCycle(std::size_t _steps) +void WorldNode::setNumStepsPerCycle(std::size_t steps) { - mNumStepsPerCycle = _steps; + mNumStepsPerCycle = steps; } //============================================================================== @@ -193,7 +218,11 @@ void WorldNode::clearUnusedNodes() { NodeMap::iterator it = mFrameToNode.find(frame); ShapeFrameNode* node = it->second; - removeChild(node); + if(!node->getShapeFrame() || !node->getShapeFrame()->hasVisualAspect() || !node->getShapeFrame()->getVisualAspect(true)->getShadowed()) { + mNormalGroup->removeChild(node); + } + else + mShadowedGroup->removeChild(node); mFrameToNode.erase(it); } } @@ -260,6 +289,16 @@ void WorldNode::refreshShapeFrameNode(dart::dynamics::Frame* frame) if(!node) return; + // update the group that ShapeFrameNode should be + if((!node->getShapeFrame()->hasVisualAspect() || !node->getShapeFrame()->getVisualAspect(true)->getShadowed()) && node->getParent(0) != mNormalGroup) { + mShadowedGroup->removeChild(node); + mNormalGroup->addChild(node); + } + else if(node->getShapeFrame()->hasVisualAspect() && node->getShapeFrame()->getVisualAspect(true)->getShadowed() && node->getParent(0) != mShadowedGroup) { + mNormalGroup->removeChild(node); + mShadowedGroup->addChild(node); + } + node->refresh(true); return; } @@ -276,7 +315,52 @@ void WorldNode::refreshShapeFrameNode(dart::dynamics::Frame* frame) ::osg::ref_ptr node = new ShapeFrameNode(frame->asShapeFrame(), this); it->second = node; - addChild(node); + if(!node->getShapeFrame()->hasVisualAspect() || !node->getShapeFrame()->getVisualAspect(true)->getShadowed()) { + mNormalGroup->addChild(node); + } + else + mShadowedGroup->addChild(node); +} + +//============================================================================== +bool WorldNode::isShadowed() const +{ + return mShadowed; +} + +//============================================================================== +void WorldNode::setShadowTechnique(::osg::ref_ptr shadowTechnique) { + if(!shadowTechnique) { + mShadowed = false; + static_cast(mShadowedGroup.get())->setShadowTechnique(nullptr); + } + else { + ImGuiViewer* viewer = mViewer ? dynamic_cast(mViewer) : nullptr; + if(viewer) + dtwarn << "[WorldNode] You are enabling shadows inside an ImGuiViewer. " + << "The ImGui windows may not render properly.\n"; + mShadowed = true; + static_cast(mShadowedGroup.get())->setShadowTechnique(shadowTechnique.get()); + } +} + +//============================================================================== +::osg::ref_ptr WorldNode::getShadowTechnique() const { + if(!mShadowed) + return nullptr; + return static_cast(mShadowedGroup.get())->getShadowTechnique(); +} + +//============================================================================== +::osg::ref_ptr WorldNode::createDefaultShadowTechnique(const Viewer* viewer) { + ::osg::ref_ptr sm = new osgShadow::ShadowMap; + // increase the resolution of default shadow texture for higher quality + int mapres = std::pow(2, 13); + sm->setTextureSize(::osg::Vec2s(mapres,mapres)); + // we are using Light1 because this is the highest one (on up direction) + sm->setLight(viewer->getLightSource(0)); + + return sm; } } // namespace osg diff --git a/dart/gui/osg/WorldNode.hpp b/dart/gui/osg/WorldNode.hpp index 4a272e777c5d5..ca8f39b3abe36 100644 --- a/dart/gui/osg/WorldNode.hpp +++ b/dart/gui/osg/WorldNode.hpp @@ -34,6 +34,7 @@ #define DART_GUI_OSG_WORLDNODE_HPP_ #include +#include #include #include @@ -67,10 +68,11 @@ class WorldNode : public ::osg::Group friend class Viewer; /// Default constructor - explicit WorldNode(std::shared_ptr _world = nullptr); + /// Shadows are disabled by default + explicit WorldNode(std::shared_ptr world = nullptr, ::osg::ref_ptr shadowTechnique = nullptr); /// Set the World that this WorldNode is associated with - void setWorld(std::shared_ptr _newWorld); + void setWorld(std::shared_ptr newWorld); /// Get the World that this WorldNode is associated with std::shared_ptr getWorld() const; @@ -119,16 +121,31 @@ class WorldNode : public ::osg::Group /// Pass in true to take steps between render cycles; pass in false to turn /// off steps between render cycles. - void simulate(bool _on); + void simulate(bool on); /// Set the number of steps to take between each render cycle (only if the /// simulation is not paused) - void setNumStepsPerCycle(std::size_t _steps); + void setNumStepsPerCycle(std::size_t steps); /// Get the number of steps that will be taken between each render cycle (only /// if the simulation is not paused) std::size_t getNumStepsPerCycle() const; + /// Get whether the WorldNode is casting shadows + bool isShadowed() const; + + /// Set the ShadowTechnique + /// If you wish to disable shadows, pass a nullptr + void setShadowTechnique(::osg::ref_ptr shadowTechnique = nullptr); + + /// Get the current ShadowTechnique + /// nullptr is there are no shadows + ::osg::ref_ptr getShadowTechnique() const; + + /// Helper function to create a default ShadowTechnique given a Viewer + /// the default ShadowTechnique is ShadowMap + static ::osg::ref_ptr createDefaultShadowTechnique(const Viewer* viewer); + protected: /// Destructor @@ -173,6 +190,15 @@ class WorldNode : public ::osg::Group /// Viewer that this WorldNode is inside of Viewer* mViewer; + /// OSG group for non-shadowed objects + ::osg::ref_ptr<::osg::Group> mNormalGroup; + + /// OSG group for shadowed objects + ::osg::ref_ptr<::osg::Group> mShadowedGroup; + + /// Whether the shadows are enabled + bool mShadowed; + }; } // namespace osg diff --git a/examples/osgExamples/osgOperationalSpaceControl/osgOperationalSpaceControl.cpp b/examples/osgExamples/osgOperationalSpaceControl/osgOperationalSpaceControl.cpp index 01f0e276f9bce..c80b742215666 100644 --- a/examples/osgExamples/osgOperationalSpaceControl/osgOperationalSpaceControl.cpp +++ b/examples/osgExamples/osgOperationalSpaceControl/osgOperationalSpaceControl.cpp @@ -248,6 +248,41 @@ class ConstraintEventHandler : public ::osgGA::GUIEventHandler dart::sub_ptr mDnD; }; +class ShadowEventHandler : public osgGA::GUIEventHandler +{ +public: + + ShadowEventHandler(OperationalSpaceControlWorld* node, dart::gui::osg::Viewer* viewer) : mNode(node), mViewer(viewer) {} + + bool handle(const osgGA::GUIEventAdapter& ea, + osgGA::GUIActionAdapter&) override + { + if(ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN) + { + if(ea.getKey() == 's' || ea.getKey() == 'S') + { + if(mNode->isShadowed()) + mNode->setShadowTechnique(nullptr); + else + mNode->setShadowTechnique(dart::gui::osg::WorldNode::createDefaultShadowTechnique(mViewer)); + return true; + } + } + + // The return value should be 'true' if the input has been fully handled + // and should not be visible to any remaining event handlers. It should be + // false if the input has not been fully handled and should be viewed by + // any remaining event handlers. + return false; + } + +protected: + + OperationalSpaceControlWorld* mNode; + dart::gui::osg::Viewer* mViewer; + +}; + int main() { dart::simulation::WorldPtr world(new dart::simulation::World); @@ -299,6 +334,7 @@ int main() // Add our custom event handler to the Viewer viewer.addEventHandler(new ConstraintEventHandler(node->dnd)); + viewer.addEventHandler(new ShadowEventHandler(node.get(), &viewer)); // Print out instructions std::cout << viewer.getInstructions() << std::endl;