diff --git a/CHANGELOG.md b/CHANGELOG.md index bb54b2b31bcf8..a1b8092370776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * GUI * Reorganized OpenGL and GLUT files: [#1088](https://github.com/dartsim/dart/pull/1088) + * Added the RealTimeWorldNode to display simulations at real-time rates: [#1216](https://github.com/dartsim/dart/pull/1216) * Misc diff --git a/dart/gui/osg/CMakeLists.txt b/dart/gui/osg/CMakeLists.txt index 2c9340c08c0d1..30616a9740819 100644 --- a/dart/gui/osg/CMakeLists.txt +++ b/dart/gui/osg/CMakeLists.txt @@ -88,3 +88,8 @@ install( DESTINATION include/dart/gui/osg COMPONENT headers ) + +dart_format_add( + RealTimeWorldNode.hpp + RealTimeWorldNode.cpp +) diff --git a/dart/gui/osg/ImGuiHandler.cpp b/dart/gui/osg/ImGuiHandler.cpp index 99a395c626a26..435b88b0e5ae2 100644 --- a/dart/gui/osg/ImGuiHandler.cpp +++ b/dart/gui/osg/ImGuiHandler.cpp @@ -175,7 +175,7 @@ ImGuiHandler::ImGuiHandler() mMouseWheel{0.0f}, mFontTexture{0u} { - // Do nothing + ImGui::CreateContext(); } //============================================================================== diff --git a/dart/gui/osg/RealTimeWorldNode.cpp b/dart/gui/osg/RealTimeWorldNode.cpp new file mode 100644 index 0000000000000..c91ed5104cc3f --- /dev/null +++ b/dart/gui/osg/RealTimeWorldNode.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2011-2019, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dart/gui/osg/RealTimeWorldNode.hpp" +#include "dart/common/Console.hpp" +#include "dart/simulation/World.hpp" + +namespace dart { +namespace gui { +namespace osg { + +//============================================================================== +RealTimeWorldNode::RealTimeWorldNode( + const std::shared_ptr& world, + const ::osg::ref_ptr& shadower, + const double targetFrequency, + const double targetRealTimeFactor) + : WorldNode(world, shadower), + mFirstRefresh(true), + mTargetRealTimeLapse(1.0 / targetFrequency), + mTargetSimTimeLapse(targetRealTimeFactor * mTargetRealTimeLapse), + mLastRealTimeFactor(0.0), + mLowestRealTimeFactor(std::numeric_limits::infinity()), + mHighestRealTimeFactor(0.0) +{ + // Do nothing +} + +//============================================================================== +void RealTimeWorldNode::setTargetFrequency(double targetFrequency) +{ + const double targetRTF = mTargetSimTimeLapse / mTargetRealTimeLapse; + mTargetRealTimeLapse = 1.0 / targetFrequency; + mTargetSimTimeLapse = targetRTF * mTargetRealTimeLapse; +} + +//============================================================================== +double RealTimeWorldNode::getTargetFrequency() const +{ + return 1.0 / mTargetRealTimeLapse; +} + +//============================================================================== +void RealTimeWorldNode::setTargetRealTimeFactor(double targetRTF) +{ + mTargetSimTimeLapse = targetRTF * mTargetRealTimeLapse; +} + +//============================================================================== +double RealTimeWorldNode::getTargetRealTimeFactor() const +{ + return mTargetSimTimeLapse / mTargetRealTimeLapse; +} + +//============================================================================== +double RealTimeWorldNode::getLastRealTimeFactor() const +{ + return mLastRealTimeFactor; +} + +//============================================================================== +double RealTimeWorldNode::getLowestRealTimeFactor() const +{ + return mLowestRealTimeFactor; +} + +//============================================================================== +double RealTimeWorldNode::getHighestRealTimeFactor() const +{ + return mHighestRealTimeFactor; +} + +//============================================================================== +void RealTimeWorldNode::refresh() +{ + customPreRefresh(); + clearChildUtilizationFlags(); + + if (mNumStepsPerCycle != 1) + { + dtwarn << "[RealTimeWorldNode] The number of steps per cycle has been set " + << "to [" << mNumStepsPerCycle << "], but this value is ignored by " + << "the RealTimeWorldNode::refresh() function. Use the function " + << "RealTimeWorldNode::setTargetRealTimeFactor(double) to change " + << "the simulation speed.\n"; + mNumStepsPerCycle = 1; + } + + if (mWorld && mSimulating) + { + if (mFirstRefresh) + { + mRefreshTimer.setStartTick(); + mFirstRefresh = false; + } + + const double startSimTime = mWorld->getTime(); + const double simTimeStep = mWorld->getTimeStep(); + + while (mRefreshTimer.time_s() < mTargetRealTimeLapse) + { + const double nextSimTimeLapse + = mWorld->getTime() - startSimTime + simTimeStep; + + if (nextSimTimeLapse <= mTargetSimTimeLapse) + { + customPreStep(); + mWorld->step(); + customPostStep(); + } + } + + mLastRealTimeFactor + = (mWorld->getTime() - startSimTime) / mTargetRealTimeLapse; + mLowestRealTimeFactor + = std::min(mLastRealTimeFactor, mLowestRealTimeFactor); + mHighestRealTimeFactor + = std::max(mLastRealTimeFactor, mHighestRealTimeFactor); + + mRefreshTimer.setStartTick(); + } + else + { + mFirstRefresh = true; + } + + refreshSkeletons(); + refreshSimpleFrames(); + + clearUnusedNodes(); + + customPostRefresh(); +} + +} // namespace osg +} // namespace gui +} // namespace dart diff --git a/dart/gui/osg/RealTimeWorldNode.hpp b/dart/gui/osg/RealTimeWorldNode.hpp new file mode 100644 index 0000000000000..b28bdac3d95b2 --- /dev/null +++ b/dart/gui/osg/RealTimeWorldNode.hpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2011-2019, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_GUI_OSG_REALTIMEWORLDNODE_HPP_ +#define DART_GUI_OSG_REALTIMEWORLDNODE_HPP_ + +#include + +#include "dart/gui/osg/WorldNode.hpp" + +namespace dart { +namespace gui { +namespace osg { + +class RealTimeWorldNode : public WorldNode +{ +public: + /// Construct a world node that will attempt to run a simulation with close + /// to real-time playback. If a simulation is too computationally expensive, + /// the simulation might not be able to keep up with real time. + /// + /// \param[in] world + /// The world to simulate + /// + /// \param[in] targetFrequency + /// The expected refresh rate. The actual refresh rate may depend on your + /// monitor and your computer's display settings. + /// + /// \param[in] targetRealTimeFactor + /// This factor at which the simulation should run. A value of 1.0 (default) + /// means it will try to run at real time. A value 2.0 means the simulation + /// will try to run at double real time speed (fast-forward). A value of 0.5 + /// means the simulation will try to run at half of real-time speed (slowed + /// down). + /// + /// \param[in] shadowTech + /// The shading technique to use when rendering this world. + RealTimeWorldNode( + const std::shared_ptr& world = nullptr, + const ::osg::ref_ptr& shadower = nullptr, + double targetFrequency = 60.0, + double targetRealTimeFactor = 1.0); + + /// Set the target refresh rate frequency + void setTargetFrequency(double targetFrequency); + + /// Get the target refresh rate frequency + double getTargetFrequency() const; + + /// Set the target real time factor + void setTargetRealTimeFactor(double targetRTF); + + /// Get the target real time factor + double getTargetRealTimeFactor() const; + + /// Get the real time factor that was achieved in the last refresh cycle. This + /// may be smaller than target real time factor, because the simulation may + /// not have had time to reach it. + double getLastRealTimeFactor() const; + + /// Get the lowest real time factor that has been hit during the simulation. + double getLowestRealTimeFactor() const; + + /// Get the highest real time factor that has been hit during the simulation. + double getHighestRealTimeFactor() const; + + /// Turn the text displaying the real time factor on or off + void setRealTimeFactorDisplay(bool on); + + // Documentation inherited + void refresh() override; + +protected: + /// Reset each time the simulation is paused + bool mFirstRefresh; + + /// Keeps track of the time between refreshes + ::osg::Timer mRefreshTimer; + + /// The target for how much time should elapse between refreshes + double mTargetRealTimeLapse; + + /// The target for how much simulation time should elapse between refreshes + double mTargetSimTimeLapse; + + /// The RTF that was achieved in the last refresh cycle + double mLastRealTimeFactor; + + /// The lowest RTF that has been achieved + double mLowestRealTimeFactor; + + /// The highest RTF that has been achieved + double mHighestRealTimeFactor; +}; + +} // namespace osg +} // namespace gui +} // namespace dart + +#endif // DART_GUI_OSG_REALTIMEWORLDNODE_HPP_ diff --git a/dart/gui/osg/render/MeshShapeNode.cpp b/dart/gui/osg/render/MeshShapeNode.cpp index 4a571dcc7bfdf..c2c6d16a80e42 100644 --- a/dart/gui/osg/render/MeshShapeNode.cpp +++ b/dart/gui/osg/render/MeshShapeNode.cpp @@ -378,6 +378,12 @@ std::vector MeshShapeNode::getTextureImagePaths( if (index < mTextureImageArrays.size()) return mTextureImageArrays[index]; + // We sometimes use this value for meshes that do not have material + // information, since assimp does not seem to have a built-in way to express + // that case. + if (index == std::numeric_limits::max()) + return {}; + if (!mTextureImageArrays.empty()) { dtwarn << "[MeshShapeNode::getTextureImageSet] Attempting to access " diff --git a/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.cpp b/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.cpp index 1714ed33e31d4..0e3714497fb8c 100644 --- a/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.cpp +++ b/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.cpp @@ -36,7 +36,7 @@ AtlasSimbiconWorldNode::AtlasSimbiconWorldNode( const dart::simulation::WorldPtr& world, const dart::dynamics::SkeletonPtr& atlas) - : dart::gui::osg::WorldNode(world), + : dart::gui::osg::RealTimeWorldNode(world), mExternalForce(Eigen::Vector3d::Zero()), mForceDuration(0.0) { diff --git a/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.hpp b/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.hpp index 9613faeb04ec1..31d7bdd04fb83 100644 --- a/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.hpp +++ b/examples/osgExamples/osgAtlasSimbicon/AtlasSimbiconWorldNode.hpp @@ -39,7 +39,7 @@ #include "Controller.hpp" -class AtlasSimbiconWorldNode : public dart::gui::osg::WorldNode +class AtlasSimbiconWorldNode : public dart::gui::osg::RealTimeWorldNode { public: /// Constructor diff --git a/examples/osgExamples/osgAtlasSimbicon/main.cpp b/examples/osgExamples/osgAtlasSimbicon/main.cpp index a9165b59235ca..834413b2760b1 100644 --- a/examples/osgExamples/osgAtlasSimbicon/main.cpp +++ b/examples/osgExamples/osgAtlasSimbicon/main.cpp @@ -61,7 +61,6 @@ int main() // Wrap a WorldNode around it osg::ref_ptr node = new AtlasSimbiconWorldNode(world, atlas); - node->setNumStepsPerCycle(20); // Create a Viewer and set it up with the WorldNode dart::gui::osg::ImGuiViewer viewer; diff --git a/examples/osgExamples/osgBoxStacking/osgBoxStacking.cpp b/examples/osgExamples/osgBoxStacking/osgBoxStacking.cpp index 95e36c2dbde42..b485b54b9af46 100644 --- a/examples/osgExamples/osgBoxStacking/osgBoxStacking.cpp +++ b/examples/osgExamples/osgBoxStacking/osgBoxStacking.cpp @@ -112,11 +112,11 @@ dynamics::SkeletonPtr createFloor() } //============================================================================== -class CustomWorldNode : public dart::gui::osg::WorldNode +class CustomWorldNode : public dart::gui::osg::RealTimeWorldNode { public: explicit CustomWorldNode(const dart::simulation::WorldPtr& world = nullptr) - : dart::gui::osg::WorldNode(world) + : dart::gui::osg::RealTimeWorldNode(world) { // Set up the customized WorldNode } @@ -417,9 +417,6 @@ int main() // Wrap a WorldNode around it osg::ref_ptr node = new CustomWorldNode(world); - // Run 20 simulation steps per one rendering frame - node->setNumStepsPerCycle(20); - // Create a Viewer and set it up with the WorldNode dart::gui::osg::ImGuiViewer viewer; viewer.addWorldNode(node); diff --git a/examples/osgExamples/osgEmpty/osgEmpty.cpp b/examples/osgExamples/osgEmpty/osgEmpty.cpp index 14f4d6dffcf56..214ca313df141 100644 --- a/examples/osgExamples/osgEmpty/osgEmpty.cpp +++ b/examples/osgExamples/osgEmpty/osgEmpty.cpp @@ -34,12 +34,12 @@ #include //============================================================================== -class CustomWorldNode : public dart::gui::osg::WorldNode +class CustomWorldNode : public dart::gui::osg::RealTimeWorldNode { public: CustomWorldNode(const dart::simulation::WorldPtr& world = nullptr) - : dart::gui::osg::WorldNode(world) + : dart::gui::osg::RealTimeWorldNode(world) { // Set up the customized WorldNode } diff --git a/examples/osgExamples/osgSoftBodies/osgSoftBodies.cpp b/examples/osgExamples/osgSoftBodies/osgSoftBodies.cpp index 6f67612934036..1ed1b536e3620 100644 --- a/examples/osgExamples/osgSoftBodies/osgSoftBodies.cpp +++ b/examples/osgExamples/osgSoftBodies/osgSoftBodies.cpp @@ -38,12 +38,12 @@ using namespace dart::dynamics; -class RecordingWorld : public dart::gui::osg::WorldNode +class RecordingWorld : public dart::gui::osg::RealTimeWorldNode { public: RecordingWorld(const dart::simulation::WorldPtr& world) - : dart::gui::osg::WorldNode(world) + : dart::gui::osg::RealTimeWorldNode(world) { grabTimeSlice(); mCurrentIndex = 0; @@ -218,7 +218,6 @@ int main() osg::ref_ptr node = new RecordingWorld(world); node->simulate(true); - node->setNumStepsPerCycle(15); dart::gui::osg::Viewer viewer; viewer.addWorldNode(node); diff --git a/examples/osgExamples/osgTinkertoy/TinkertoyWorldNode.hpp b/examples/osgExamples/osgTinkertoy/TinkertoyWorldNode.hpp index 2807b4a104c53..06c19f95607d1 100644 --- a/examples/osgExamples/osgTinkertoy/TinkertoyWorldNode.hpp +++ b/examples/osgExamples/osgTinkertoy/TinkertoyWorldNode.hpp @@ -57,12 +57,12 @@ const double MinForceCoeff = 10.0; const double ForceIncrement = 10.0; //============================================================================== -class TinkertoyWorldNode : public dart::gui::osg::WorldNode +class TinkertoyWorldNode : public dart::gui::osg::RealTimeWorldNode { public: TinkertoyWorldNode(const dart::simulation::WorldPtr& world) - : dart::gui::osg::WorldNode(world), + : dart::gui::osg::RealTimeWorldNode(world), mForceCoeff(DefaultForceCoeff), mWasSimulating(false) { diff --git a/examples/osgExamples/osgTinkertoy/main.cpp b/examples/osgExamples/osgTinkertoy/main.cpp index 54b8297accf16..9dced77514fd3 100644 --- a/examples/osgExamples/osgTinkertoy/main.cpp +++ b/examples/osgExamples/osgTinkertoy/main.cpp @@ -173,7 +173,6 @@ int main() // Create the OSG Node that represents the world osg::ref_ptr node = new TinkertoyWorldNode(world); - node->setNumStepsPerCycle(20); // Create the viewer dart::gui::osg::ImGuiViewer viewer;